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
A dynamically-keyed property read on a primitive string returns undefined (const k="charAt"; "abc"[k]), while the literal key and the call form work #9815
Reading a method off a primitive string with a dynamically-keyed member
expression returns undefined. The same read with a literal key works, and calling through the dynamic key works — so only the value read is wrong, which
is what makes it easy to miss and nasty in practice.
constk="charAt";consts="abcde";typeofs[k]// perry: "undefined" node: "function"s[k](1)// perry: "b" node: "b" (call form is fine)typeofs["charAt"]// (literal key — not affected, see below)typeofString.prototype[k]// perry: "function" node: "function"String.prototype[k].call(s,1)// perry: "b" node: "b"
Measured on a compiled binary from 1d63fa91f (runtime-only branch), against node on the same source. 46-case probe, one differing line — this one.
Why it matters more than the one-liner suggests
Every "take the method, then call it" shape breaks, and that is the ordinary way
bundled and generic code dispatches:
for(constnameof["trim","toUpperCase"]){constfn=s[name];// undefined in perryfn.call(s);// TypeError: fn.call is not a function}if(typeofs[name]==="function"){/* never taken */}constm=s[name]?.bind(s);// silently undefined
I hit it writing a probe that enumerated String.prototype and called each
method as recv[k].apply(recv, args): all 79 methods threw TypeError in
perry where node returned a value, purely because recv[k] read as undefined.
That is the realistic blast radius — any reflective loop over a primitive's
methods.
Where it is not
String.prototype[k] (dynamic key, object receiver) is fine.
s[k](...) (dynamic key, call position) is fine — the call goes through js_native_call_method, which resolves the name on the prototype.
So the defect is specific to a property GET on a primitive receiver with a
non-literal key returning without consulting the primitive's prototype.
What I have not established
Whether the literal-key value read (typeof s["charAt"], no call) is affected,
whether number/boolean/bigint receivers behave the same, and whether a
user-assigned String.prototype.foo is affected as well. My probe covered the
dynamic-key case only; those three are the first things to add to a fixture.
Falsifier for whoever takes it
typeof s[k] must be "function" and s[k] === String.prototype[k] must be true for a primitive s and a variable k — and the fix must not slow the
call form, which already resolves correctly through a different path. If a
change makes the read work but routes the call through the read, check the
primitive-method dispatch cost before landing: that path is the subject of #9810.
Provenance
Found while measuring #9800 (built-in callee receives the primitive, not a ToObject wrapper). It is not that change: the divergence is identical with #9800's env gate on and off, so it predates it and is independent of it.
Activity
proggeramlug commentedon Sep 6, 2026
Fixed by #9818, landed on
mainvia merge train #9866. The train merged as its own branch, so the close-keyword never fired — closing manually.