Symptom
A user class method whose name shadows a String.prototype char-access
method — charAt, charCodeAt, or codePointAt — is mis-lowered to the
String builtin instead of the user's method. The receiver (a class instance) is
coerced to "[object Object]", so:
class Buf {
buffer = "hello"; pos = 0;
charAt(n: number) { return this.buffer[this.pos + n]; }
}
const b = new Buf();
console.log(b.charAt(0) + b.charAt(1)); // node: "he" perry: "[o"
"[o" is "[object Object]"[0] + "[object Object]"[1] — String.prototype.charAt
run on the stringified receiver.
Cause
try_lower_property_get_method_call (crates/perry-codegen/src/lower_call/property_get.rs)
takes the static String-method fast path for a builtin-named method on a
non-provably-string receiver, gated by an arity heuristic
(string_only_method_arity_ok, added in #5271 so a user trim(value, schema)
with a mismatched arg count falls through to runtime dispatch). But the
char-access methods ignore surplus args per spec, so that gate returns true
for any arg count — the escape hatch never fires for them, and a user
charAt(n) on a class instance is forced onto lower_string_method.
slice / indexOf are unaffected (they are excluded from the string-only set
because they also exist on arrays); only charAt / charCodeAt / codePointAt
hit this.
Impact
The yaml package's tokenizer is built on Lexer.charAt(n) { return this.buffer[this.pos + n]; }
and calls this.charAt(0) as its scanner. Mis-dispatched, it reads garbage, so
the lexer's *lex state machine (while (next && this.hasChars(1)) next = yield* this.parseNext(next))
never advances this.pos and spins forever at 100% CPU — hanging yaml.parse()
of any non-trivial input, and any large esbuild-bundled CLI app that parses
YAML during module init.
Fix
Don't take the static String path when the receiver's statically-known class
(or an ancestor) defines its own method of that name — a user method wins over
a String builtin. A genuine string receiver is unaffected (it has no known
class), and the runtime jsval.is_string() arm still services Any-typed
strings.
PR incoming with an e2e regression suite (user charAt/charCodeAt/codePointAt
methods in plain + generator + class contexts, plus a real-string control).
Symptom
A user class method whose name shadows a
String.prototypechar-accessmethod —
charAt,charCodeAt, orcodePointAt— is mis-lowered to theString builtin instead of the user's method. The receiver (a class instance) is
coerced to
"[object Object]", so:"[o"is"[object Object]"[0]+"[object Object]"[1]—String.prototype.charAtrun on the stringified receiver.
Cause
try_lower_property_get_method_call(crates/perry-codegen/src/lower_call/property_get.rs)takes the static String-method fast path for a builtin-named method on a
non-provably-string receiver, gated by an arity heuristic
(
string_only_method_arity_ok, added in #5271 so a usertrim(value, schema)with a mismatched arg count falls through to runtime dispatch). But the
char-access methods ignore surplus args per spec, so that gate returns
truefor any arg count — the escape hatch never fires for them, and a user
charAt(n)on a class instance is forced ontolower_string_method.slice/indexOfare unaffected (they are excluded from the string-only setbecause they also exist on arrays); only
charAt/charCodeAt/codePointAthit this.
Impact
The
yamlpackage's tokenizer is built onLexer.charAt(n) { return this.buffer[this.pos + n]; }and calls
this.charAt(0)as its scanner. Mis-dispatched, it reads garbage, sothe lexer's
*lexstate machine (while (next && this.hasChars(1)) next = yield* this.parseNext(next))never advances
this.posand spins forever at 100% CPU — hangingyaml.parse()of any non-trivial input, and any large esbuild-bundled CLI app that parses
YAML during module init.
Fix
Don't take the static String path when the receiver's statically-known class
(or an ancestor) defines its own method of that name — a user method wins over
a String builtin. A genuine string receiver is unaffected (it has no known
class), and the runtime
jsval.is_string()arm still services Any-typedstrings.
PR incoming with an e2e regression suite (user
charAt/charCodeAt/codePointAtmethods in plain + generator + class contexts, plus a real-string control).