Repro (4 lines)
const V = { marker: 7 };
const o = {};
(function (t) { t.prototype = V; })(o);
console.log(Object.prototype.hasOwnProperty.call(o, "prototype"), o.prototype);
node : true { marker: 7 }
perry: false undefined
The write is silently dropped — no throw, no warning. o simply never gains the property: hasOwnProperty is false, Object.keys(o) does not list it, and Object.getOwnPropertyDescriptor(o, "prototype") returns undefined.
What distinguishes the failing shape
Eleven variants, all writing an own prototype onto a plain object. Only the parameter write fails:
| shape |
perry |
function f(){const o={};o.prototype=V;return o} (local) |
ok |
const o={};o.prototype=V (same scope) |
ok |
{prototype:V} object literal |
ok |
o["prototype"]=V |
ok |
Object.defineProperty(o,"prototype",…) |
ok |
{...{prototype:V}} / Object.assign({},{prototype:V}) |
ok |
(function(t){t.prototype=V})(o) — write through a parameter |
DROPPED |
A second failing shape, same family, where the receiver is a local whose shape has been made dynamic first:
function wide(n) {
const o = {};
for (let k = 0; k < n; k++) o["p" + k] = k; // dynamic keys
o.prototype = { marker: 7 };
return o;
}
console.log(wide(0).prototype); // perry: undefined node: { marker: 7 }
This one fails for every n, including n = 0 (the loop never runs). So it is not about property count or overflow slots — the mere presence of the dynamic-key write is enough to change how the later o.prototype = … is compiled.
The unifying pattern in both: when the receiver's static type is not known to be a plain object, x.prototype = v appears to be routed to the function/class prototype slot rather than to an ordinary own property, and for a plain object that store goes nowhere.
Why this matters beyond the fixture
This is the mechanism family behind #9341 (cc --help down on main). The diagnostic there shows the failing call is Object.defineProperty(undefined, "getContentType", …), where the undefined is z in axios's
static accessor(q){ … let z = this.prototype; … xp5(z, A) … }
i.e. a .prototype access on a receiver of imprecise static type yielding undefined instead of the prototype. The read side and the write side above are the same fragility. #9341's trigger is a regression inside 83754818e..main (this write-drop reproduces identically on the green 83754818e build, so it is not itself the regression) — but a .prototype access that silently answers undefined on an imprecisely-typed receiver is the machinery that regression is landing on, and hardening it would make that whole class of failure loud instead of silent.
At minimum, a store that cannot be honoured should not be discarded in silence.
Found while bisecting #9341; verified pre-existing on 83754818e (#9242) and a03be729c (#9336) — byte-identical failure on both.
Repro (4 lines)
The write is silently dropped — no throw, no warning.
osimply never gains the property:hasOwnPropertyisfalse,Object.keys(o)does not list it, andObject.getOwnPropertyDescriptor(o, "prototype")returnsundefined.What distinguishes the failing shape
Eleven variants, all writing an own
prototypeonto a plain object. Only the parameter write fails:function f(){const o={};o.prototype=V;return o}(local)const o={};o.prototype=V(same scope){prototype:V}object literalo["prototype"]=VObject.defineProperty(o,"prototype",…){...{prototype:V}}/Object.assign({},{prototype:V})(function(t){t.prototype=V})(o)— write through a parameterA second failing shape, same family, where the receiver is a local whose shape has been made dynamic first:
This one fails for every
n, includingn = 0(the loop never runs). So it is not about property count or overflow slots — the mere presence of the dynamic-key write is enough to change how the latero.prototype = …is compiled.The unifying pattern in both: when the receiver's static type is not known to be a plain object,
x.prototype = vappears to be routed to the function/class prototype slot rather than to an ordinary own property, and for a plain object that store goes nowhere.Why this matters beyond the fixture
This is the mechanism family behind #9341 (
cc --helpdown on main). The diagnostic there shows the failing call isObject.defineProperty(undefined, "getContentType", …), where theundefinediszin axios'si.e. a
.prototypeaccess on a receiver of imprecise static type yieldingundefinedinstead of the prototype. The read side and the write side above are the same fragility. #9341's trigger is a regression inside83754818e..main(this write-drop reproduces identically on the green83754818ebuild, so it is not itself the regression) — but a.prototypeaccess that silently answersundefinedon an imprecisely-typed receiver is the machinery that regression is landing on, and hardening it would make that whole class of failure loud instead of silent.At minimum, a store that cannot be honoured should not be discarded in silence.
Found while bisecting #9341; verified pre-existing on
83754818e(#9242) anda03be729c(#9336) — byte-identical failure on both.