Skip to content

gc: native-method-call arguments (map.set(a, b)) are not precise roots -- aborts under precise-roots-only #6970

Description

@proggeramlug

Found while fixing #6951. Same bug class (an evaluated argument held in an SSA register across a later argument's collection), different lowering path — the native-method dispatch, not the variadic accumulator #6951 fixed.

This one aborts (exit 134), so it is the loudest of the group.

Repro

let sink: unknown[] = [];
function churn(n: number): number {
  let acc = 0;
  for (let i = 0; i < n; i++) {
    sink.push({ i: i, s: "x" + (i & 255), a: [i, i + 1] });
    if (sink.length > 4096) { acc = (acc + sink.length) | 0; sink = []; }
  }
  return acc | 0;
}
function fresh(k: number): string { return "f" + k + "-" + (k * 7); }
const N = 420000;
// Repeat 6x: use-after-free, needs the freed block recycled.
{ const m = new Map<unknown, unknown>(); m.set(fresh(0), churn(N)); console.log([...m.keys()][0], [...m.values()][0]); }
{ const m = new Map<unknown, unknown>(); m.set(fresh(1), churn(N)); console.log([...m.keys()][0], [...m.values()][0]); }
{ const m = new Map<unknown, unknown>(); m.set(fresh(2), churn(N)); console.log([...m.keys()][0], [...m.values()][0]); }
{ const m = new Map<unknown, unknown>(); m.set(fresh(3), churn(N)); console.log([...m.keys()][0], [...m.values()][0]); }
{ const m = new Map<unknown, unknown>(); m.set(fresh(4), churn(N)); console.log([...m.keys()][0], [...m.values()][0]); }
{ const m = new Map<unknown, unknown>(); m.set(fresh(5), churn(N)); console.log([...m.keys()][0], [...m.values()][0]); }
$ PERRY_CONSERVATIVE_STACK_SCAN=off PERRY_GC_HEAP_LIMIT=8 ./repro
... ; exit 134 (abort)

Byte-exact vs node 26.5.0 under the default configuration, so this is purely a rooting gap that conservative stack scanning is currently hiding (gc_check_trigger forces the scan on both automatic arms; production resolves Auto -> SkipDisabled).

Fix shape

The map.set(key, value) lowering evaluates the receiver and each argument into SSA registers and holds them across the remaining argument evaluations and the runtime call. crates/perry-codegen/src/expr/temp_root.rs (added by #6951) has the primitive — lower_exprs_rooted / lower_operand_pair_rooted root each already-evaluated value across the later ones and return re-read values plus a guard for temp_root_release.

Related: #6951, #6968, #6969, #6971, #6950.

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