ReviewOS

zig-utils/zig-js

Class fields: direct eval loses new.target permission in global static initializers

#917
Closed glennmichael123 opened this 9 days ago ·
9 days ago

Related: #916, #915. Parent: #899.

Confirmed on 92043c5-equivalent production binaries in forced tree-walker and VM modes: class C { static x = eval("new.target"); } throws SyntaxError instead of initializing x to undefined. The same failure occurs through immediate/escaped arrows and a static private field. A 13-case differential against actual system JSC gives 9/13 agreement in both modes; all four mismatches are these static-field cases. Instance fields, fields nested inside a constructor, computed-name scope, indirect eval rejection, and caller restoration controls agree.

Root cause: evalClass marks static field initialization with in_field_initializer, but leaves direct_eval_new_target_allowed inherited from the outer context. At global scope it remains false. The equivalent constructor scope happens to supply true, making acceptance incorrectly depend on where the class is defined. Arrow closures capture the same false permission.

ECMA-262 ClassFieldDefinitionEvaluation creates a non-lexical initializer function, and DefineField calls it. The initializer therefore has a new.target binding whose value is undefined. The existing in_field_initializer value masking already supplies undefined; the missing permission must be scoped and restored on success/throw, without leaking into computed keys or indirect eval.

Acceptance: regression tests across both tiers for public/private fields, direct/nested eval, escaped arrows, and normal/abrupt context restoration; preserve existing super()/arguments initializer restrictions; targeted class/eval corpus with exact denominators. No global admission relaxation or workaround.

9 days ago

Landed directly on main as b7d93a9, authored/committed as glennmichael123 gtorregosa@gmail.com, with no coauthor trailers.

The shared static-field evaluator now supplies and restores the missing direct-eval permission. No parser acceptance shortcut or VM-only workaround was added. Eighteen regression cases exercise both tree-walker and required VM mode, including escaped arrows, private fields, nested eval, computed names, indirect eval, arguments/super restrictions, and abrupt restoration.

Verification: 73 selected production-root unit tests passed, zero failures/skips/leaks; actual-system-JSC differential 9/13→13/13 in each tier. Exact before/after corpus: new.target 14/14, eval-code 347/347, class statements 4367/4367, class expressions 4059/4059. Total 8787 pass and zero failures on each side; zero test262 flips/regressions in those subtrees. Full unfiltered unit suite remains outside this 3 GiB run budget. The temporary local compilation filter was removed and is not part of the commit. Peak observed job footprint: 2317 MiB for the initial selected-unit build; corrected full verification wrapper 2018 MiB.

Sign in to comment on this issue.