ReviewOS

zig-utils/zig-js

Frontend: derive arguments usage during parsing

#555
Closed chris opened this 9 days ago ·
9 days ago

Parent: #496 Related: #493, #465, #481

Problem

Every ordinary function/method calls ast.sourceMayUseArguments after parsing. That helper scans the complete captured function source once for arguments and again for eval. In a nested function chain, every outer source range contains every inner range, so attacker-controlled nesting causes quadratic post-parse source work. The text scan also treats comments, strings, identifier substrings, and nested ordinary functions as uses, needlessly materializing the arguments exotic object and restricting bytecode/JIT eligibility.

Scope

  • Freeze parser-only nested-function growth rows with exact depth, source range, AST topology, uses_arguments flags, and checksum validation.
  • Add anti-specialization and semantic controls for comments/strings/substrings, an innermost ordinary-function use, an arrow-inherited use, methods, generators/async functions, and conservative direct-eval detection.
  • Track arguments/eval identifier references while parsing the current non-arrow function scope. Nested ordinary functions/methods start fresh state; arrows inherit their nearest ordinary function state.
  • Remove complete-source rescans without changing retained Function.prototype.toString source bytes or arguments-object semantics.

No-workaround rules

  • No globally forcing uses_arguments true or false, skipping arguments materialization, weakening direct-eval conservatism, omitting retained source, benchmark/source recognition, hidden preprocessing, or reduced nesting at larger rows.
  • Do not touch stacks, queues, jobs, Promise/microtask, or Worker lifecycle code.

Acceptance

  • Benchmark source, base/variant/control identities, depths, checksums, warmup, jobs, and timed boundary land before the runtime change.
  • Function argument-use derivation adds no post-parse source or AST pass and performs O(1) work per relevant identifier while preserving nested ordinary/arrow scope rules.
  • Comments, strings, identifier substrings, and nested ordinary functions do not force an outer arguments object; real arguments references and conservative eval identifiers do.
  • Function declarations/expressions, methods/accessors, generators, async functions, arrow defaults/bodies, direct eval, and Function.prototype.toString retain exact semantics.
  • OOM/fault behavior remains atomic and no per-function tracking allocation is introduced.
  • Focused frontend Debug/TSan, full units, forced tree-walker/required-bytecode witnesses, and exact arguments/eval/function test262 pass without regressions.
  • Seven-pair exact-parent evidence publishes wall, CPU, allocations/bytes, peak RSS, instructions/cycles/energy availability, thermal state, and dispersion with no discarded samples.

Security boundary

Source length, nesting depth, token spelling, and comment/string contents are attacker-controlled. The fix must remove repeated range scans without misclassifying a real dynamic arguments access or allowing direct eval to observe a missing arguments object.

9 days ago

Benchmark-first commit landed on main as 6ce71243: bench(frontend): add nested function scope rows (#555).

Frozen rows:

  • ordinary-function nesting at depths 256, 512, and 1,024;
  • a 1,024-depth anti-specialization control with identifier-binding, string, and comment decoys containing eval/arguments text;
  • a 1,024-depth chain whose innermost ordinary function really references arguments;
  • 1,024 nested arrows inheriting a real arguments reference from their nearest ordinary owner.

Source construction is outside ten one-job warmups and the scored parser boundary. Each job performs real lexing/parsing/metadata construction and validates exact AST topology, depth, names, retained function-source starts/lengths, safe uses_arguments facts, leaf behavior, and checksum. Frozen one-job checksums are 1879613, 7565629, 30389849, 64044633, 30405224, and 2647668.

The clean parent rows expose the causal growth: retired instructions are approximately 14.4M / 51.8M / 196.6M at depths 256/512/1,024. The benchmark publication self-tests and 18/18 representative matrix structural gates pass. No parser/runtime fix is included in this commit.

9 days ago

Runtime implementation landed on main as 00a401d7: perf(parser): derive arguments use in scope (#555).

The parser now carries one stack-local flag for the active ordinary function scope. Ordinary functions and methods replace that flag; arrows, including parameter defaults, inherit it. Only arguments/eval IdentifierReferences and object shorthand mark the scope. Function names, bindings, non-computed property names, member property names, comments, strings, and identifier substrings do not.

This removes the complete-source scans and adds no tracking allocation or cleanup path. Retained function source slices remain unchanged for Function.prototype.toString.

Validation:

  • full units: 1,627 passed, 1 expected skip, 0 failed, 0 leaked;
  • focused frontend Debug and TSan: 12/12 passed;
  • affected test262 subtrees: 2,470 → 2,470 passes, 0 → 0 failures;
  • a dedicated forced-tier witness returns identical bits under the tree walker and required bytecode, with one compiled template, zero arguments-policy rejections, and zero fallbacks;
  • all six frozen benchmark rows retain their exact checksums and allocation counts/bytes.

The exact seven-pair parent/candidate evidence is the remaining acceptance item.

9 days ago

Completed and pushed directly to main.

Commits:

  • 6ce71243 — bench(frontend): add nested function scope rows (#555)
  • 00a401d7 — perf(parser): derive arguments use in scope (#555)
  • 59638193 — docs(frontend): publish arguments scope evidence (#555)

The parser now derives uses_arguments during the owning ordinary function's parse with one stack-local flag. Nested ordinary functions/methods isolate state; arrows and their defaults inherit it. Complete function-source rescans are gone, real arguments and conservative eval references remain safe, and decoy text no longer blocks arguments-object elision or bytecode eligibility.

Exact-parent evidence: docs/.data/exact-parent-frontend-arguments-use-2026-08-14.md.

Results:

  • ordinary depth-256/512/1,024 instructions: 14.0M/51.6M/197.0M → 2.54M/5.03M/10.15M;
  • depth-1,024 retired instructions: -94.85%; diagnostic wall median: -96.73%;
  • 1,024-arrow control: 0.9967x instructions;
  • allocations and allocated bytes: exactly unchanged in all six rows;
  • full units: 1,627 passed, 1 expected skip, 0 failures/leaks;
  • affected exact-parent test262: 2,470 → 2,470 passes, zero failures/flips;
  • performance schema, exact-parent/benchmark self-tests, and 245-file docs link check all pass.

All six performance rows contain seven order-balanced pairs with no discarded samples. Battery-powered and sub-millisecond wall data is labeled diagnostic; the stable retired-instruction growth is the causal evidence.

Sign in to comment on this issue.