Repro (single evaluation — no factory chaining needed)
function mkc(P?: any): any {
class D extends (P ?? Object) {
constructor(def: any) { super(def); (this as any).def = def; }
}
return D;
}
const A = mkc();
console.log("ok " + (new A({ t: 1 }) as any).def.t);
node : ok 1
perry: TypeError: Cannot read properties of undefined (reading 't') rc=1
this.def is written immediately after super(def) and reads back
undefined. One evaluation of the factory; no chaining.
What narrows it
| variant |
perry |
class D extends Object { constructor(def) { super(def); this.def = def } } — static heritage |
ok, node-identical |
class D extends (P ?? Object) { … } inside a factory — dynamic heritage |
this.def is undefined |
| dynamic heritage, no explicit constructor |
ok |
So the trigger is a dynamic (runtime-value) heritage that resolves to an
uncallable builtin, combined with an explicit constructor that calls
super(...) and then assigns to this. The identical class with a static
extends Object works, which is why this is not simply "extends Object is
broken".
Suspected site
js_fetch_or_value_super in
crates/perry-runtime/src/object/global_this/fetch_globals.rs. A dynamic
heritage that resolves to a builtin in is_uncallable_builtin_super_parent's
list (Object is in it) takes this arm:
let new_target = crate::object::class_constructor_ref_value(cid);
return crate::object::js_new_function_construct_with_new_target(
parent_val, args_ptr, args_len, new_target,
);
which returns a fresh object. The caller
(crates/perry-codegen/src/expr/this_super_call.rs) then feeds that return
through js_ctor_return_override(current_this, parent_result, 0) and stores the
result back into the this slot. The subsequent this.def = def therefore
appears to land somewhere other than the object new hands back. The static
extends Object path does not go through this arm, which matches the table
above.
This is a suspicion from reading the two sites, not a bisected root cause — the
observable is the table.
Relationship to #9406
Found while validating #9406. The chained form of this program used to
SIGSEGV; #9406 fixed the crash, and the program now reaches this pre-existing
wrong value instead:
const A = mkc(); const B = mkc(A);
console.log("A " + (new A({ t: 1 }) as any).def); // node: [object Object] — perry: undefined
Verified as pre-existing: identical failure text on e284cabc3 (#9406's merge
base) and on 23a211383, so it predates that window entirely.
Environment
perrymaster, Node oracle 26.5.1 (.node-version).
Repro (single evaluation — no factory chaining needed)
this.defis written immediately aftersuper(def)and reads backundefined. One evaluation of the factory; no chaining.What narrows it
class D extends Object { constructor(def) { super(def); this.def = def } }— static heritageclass D extends (P ?? Object) { … }inside a factory — dynamic heritagethis.defis undefinedSo the trigger is a dynamic (runtime-value) heritage that resolves to an
uncallable builtin, combined with an explicit
constructorthat callssuper(...)and then assigns tothis. The identical class with a staticextends Objectworks, which is why this is not simply "extends Objectisbroken".
Suspected site
js_fetch_or_value_superincrates/perry-runtime/src/object/global_this/fetch_globals.rs. A dynamicheritage that resolves to a builtin in
is_uncallable_builtin_super_parent'slist (
Objectis in it) takes this arm:which returns a fresh object. The caller
(
crates/perry-codegen/src/expr/this_super_call.rs) then feeds that returnthrough
js_ctor_return_override(current_this, parent_result, 0)and stores theresult back into the
thisslot. The subsequentthis.def = defthereforeappears to land somewhere other than the object
newhands back. The staticextends Objectpath does not go through this arm, which matches the tableabove.
This is a suspicion from reading the two sites, not a bisected root cause — the
observable is the table.
Relationship to #9406
Found while validating #9406. The chained form of this program used to
SIGSEGV; #9406 fixed the crash, and the program now reaches this pre-existing
wrong value instead:
Verified as pre-existing: identical failure text on
e284cabc3(#9406's mergebase) and on
23a211383, so it predates that window entirely.Environment
perrymaster, Node oracle 26.5.1 (.node-version).