Skip to content

interpreter: 'can't delete locked array' after reading an element of a scoped array<array<string>> - lock leak in a fused at permutation, host-dependent #3923

Description

@borisbat

Running the file below with bin/daslang (Release, macOS arm64, master as of 2026-09-01) throws EXCEPTION: can't delete locked array at daslib/builtin.das:1499 at the end of main, where the scoped rows is deleted. Two observations that make it look like a lock-count leak in a fused at permutation rather than a logic error:

  • Replacing the two let lines with a direct call eq("fam_one", rows[1][0]) passes; so does string interpolation "{rows[1][0]}". Reading into a let local fails.
  • On the extended-checks linux lane (Release, gcc; run 33573527004) the comprehension form [for (k in range(1, n - 1)); cells[k]] over a scoped array failed the same way inside dastest/tests/test_review_gate.das, while it passed on macOS - the failing shape differs by host.

Debug builds bypass the fused permutations (the 5-tier indexing note in CLAUDE.md), which fits. The gates in PR 3922 avoid the shape - iterate the outer array, single-index the element, clone what is kept - pending this.

options gen2
require strings
require daslib/strings_boost

def markdown_table_rows(text : string; section : string) : array<array<string>> {
    var out : array<array<string>>
    var in_section = false
    var in_table = false
    for (raw in split(text, "\n")) {
        let line = strip(raw)
        if (line |> starts_with("## ")) {
            break if (in_table)
            in_section = line == "## {section}"
            continue
        }
        continue if (!in_section)
        if (!(line |> starts_with("|"))) {
            break if (in_table)
            continue
        }
        in_table = true
        continue if (line |> starts_with("|---") || line |> starts_with("| ---"))
        // value iteration with a counter, never index access: indexing a scoped array from a
        // range loop or a comprehension leaks its lock on some hosts, and the next scoped
        // delete fails with "can't delete locked array" (interpreter issue, repro on file)
        var inscope raw_cells <- split(line, "|")
        let n_cells = length(raw_cells)
        continue if (n_cells < 3)
        var row : array<string>
        row |> reserve(n_cells - 2)
        var idx = 0
        for (cell in raw_cells) { // nolint:STYLE027 - the comprehension form is the lock leak
            if (idx > 0 && idx < n_cells - 1) {
                row |> push(strip(replace(cell, "`", "")))
            }
            idx++
        }
        out |> emplace(row)
    }
    return <- out
}

def eq(a : string; b : string) : bool {
    return a == b
}

[export]
def main {
    var inscope rows <- markdown_table_rows("## INTERP\n\n| Benchmark | A (m1) | B (m2) |\n|---|---:|---:|\n| `fam_one` | 1.5 | - |\n| `fam_two` | 2.0 | 3.5 |\n", "INTERP")
    let a = rows[1][0]
    let b = rows[1][2]
    print("local: {eq("fam_one", a)} {eq("-", b)}\n")
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions