You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
gc: constructor arguments (new C(a, b)) are not precise roots across the instance allocation #6969
Found while fixing #6951, which fixed the variadic argument accumulator (console.log, spread/rest lists) and the concat/literal families. Constructor arguments are the same bug class at a different site and are not covered by that fix.
Repro
letsink: unknown[]=[];functionchurn(n: number): number{letacc=0;for(leti=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=[];}}returnacc|0;}functionfresh(k: number): string{return"f"+k+"-"+(k*7);}classPair{a: unknown;b: unknown;constructor(a: unknown,b: unknown){this.a=a;this.b=b;}toString(): string{return"<"+this.a+","+this.b+">";}}constN=420000;// Repeat 6x: the failure is a use-after-free and needs the block recycled.{console.log(String(newPair(fresh(0),churn(N))));}{console.log(String(newPair(fresh(1),churn(N))));}{console.log(String(newPair(fresh(2),churn(N))));}{console.log(String(newPair(fresh(3),churn(N))));}{console.log(String(newPair(fresh(4),churn(N))));}{console.log(String(newPair(fresh(5),churn(N))));}
PERRY_CONSERVATIVE_STACK_SCAN=off PERRY_GC_HEAP_LIMIT=8 ./repro prints wrong output (pre-#6951 it segfaulted; post-#6951 it is a silent DIFF because the surrounding concat is now rooted).
Why
lower_call/new.rs lowers all constructor arguments into lowered_args up front, then allocates the instance and calls the constructor:
%r10 is live across two collection points with no root.
Fix shape
crates/perry-codegen/src/expr/temp_root.rs (added by #6951) has the primitive: lower_exprs_rooted roots each already-evaluated value across the later ones and hands back re-read values plus a guard. The obstacle is purely structural — lower_new is ~1700 lines with ~20 return paths and lowered_args is consumed at a dozen of them, so the temp_root_release has to be placed at each. A statement-boundary truncate (mark at statement entry, truncate at statement exit) would make this and the sibling sites mechanical; that is the design question to settle first.
Activity
fix(gc): root constructor arguments across the instance allocation (#…
fix(gc): root constructor args as they are lowered, not after the loop (
test(gc): pin the operand-rooting contract for #6969/#6970/#6971
docs: changelog fragment for #6969/#6970/#6971
fix(gc): root the MapSet receiver before the key is lowered, and refr…
fix(gc): root constructor arguments across the instance allocation (#…
fix(gc): root constructor args as they are lowered, not after the loop (
test(gc): pin the operand-rooting contract for #6969/#6970/#6971
docs: changelog fragment for #6969/#6970/#6971
fix(gc): root the MapSet receiver before the key is lowered, and refr…
fix(gc): operand temporaries are precise roots — collection methods, …
fix(gc): root builtin.rs's constructor arguments across each other's …
fix(gc): root builtin.rs's constructor arguments across each other's …
gc: fix the phi-edge checker false positives and the static-dispatch …
gc: fix root-dominance phi false positives + 3 of 5 real hits, lower …