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
Sloppy f.caller = v on a plain function throws: the closure poison pill ignores the assignment Throw flag #9525
// .cts, sloppyfunctionf(){}f.caller=2;// node: silent no-op, no own property Perry: TypeErrorf.arguments=2;// node: silent no-op Perry: TypeError// .cts, "use strict"functiong(){}g.caller=2;// node: TypeError Perry: TypeError (correct)classD{}D.caller=2;// node: TypeError in BOTH modes Perry: TypeError (correct)
Function.prototype.caller / .arguments are the ECMAScript poison-pill
accessors. For a class constructor (a strict function) V8 throws in both
modes, which Perry matches. For a plain non-strict function declaration,
node's observable behaviour is OrdinarySet returning false: a silent no-op
in sloppy code, a TypeError in strict code, and no own property either way.
Perry throws in both.
Root cause
crates/perry-runtime/src/object/field_set_by_name/write_helpers.rs (the
closure store path):
// ECMAScript "poison pill" — assigning `caller`/`arguments` on any// strict-mode function (Perry compiles everything strict: declarations,// expressions, bound and built-in closures, arrows) throws via the// %ThrowTypeError% accessor's missing setter.ifmatches!(name_str,"caller" | "arguments")
&& !crate::closure::closure_has_own_dynamic_prop(obj asusize, name_str){crate::fs::validate::throw_type_error_with_code(...);}
The throw is unconditional. The comment's premise — "Perry compiles everything
strict" — is what #9423/#9458 established is not true of a .cts script, and
it is exactly the same shape as #9394/#9422/#9459: a rejected [[Set]] throwing
without consulting the assignment's Throw flag. The class-constructor site
(field_set_by_name.rs, "Writing .caller / .arguments on a class
constructor") is correct as-is and must keep throwing unconditionally.
Scope
This is a RUNTIME store path, not a codegen routing question: it reproduces
identically through the computed-key route (f[k] = v with k = "caller"),
which never touches the name-keyed PropertySet lowering. So it is neither
caused nor fixed by #9459, which verified that js_put_value_set reaches both
poison-pill sites and left the runtime side alone.
Fix shape
Carry the assignment's Throw flag to the closure store path and make the
poison-pill rejection conditional on it, leaving the class-constructor site
unconditional. The entry that needs the flag is the closure branch of js_object_set_field_by_name; js_put_value_set already has strict and is
what both spellings reach.
Verification bar
test-files/test_gap_9459_property_set_strictness.cts already asserts the
ordinary-object and class-constructor receiver paths in both modes and carries a
comment naming this gap where the plain-function case belongs. Add the function
receiver there, both arms, with hasOwnProperty asserted so a fix that stores
instead of no-opping is caught.
Found while fixing #9459 (a CodeRabbit review of PR #9519 asked whether the
sloppy tail bypassed the poison pill; it does not — this is the residual).
Activity
fix(codegen): the sloppy tail stops excluding `caller`/`arguments` (P…
fix(runtime): honor sloppy function poison-pill stores (PerryTS#9525)
fix(runtime): honor sloppy function poison-pill stores (#9525)