Add WeakRef/FinalizationRegistry using Rc::downgrade()/Weak::upgrade() for genuine weak references. Implement strict mode restrictions for eval/arguments binding identifiers, with statement rejection, legacy octal literal/escape rejection, and undeclared variable assignment. Code review fixed 7 issues: \x escape double-write bug, WeakRef function target deref, compound/logical/prefix/postfix assignment eval/arguments checks, \08/\09 false octal detection, \u escape with <4 hex digits, and constructor new-required checks. 26 Test262 tests promoted, 53 new tests added. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
30 KiB
Story 3.7: WeakRef, FinalizationRegistry & Strict Mode Edge Cases
Status: done
Story
As a web developer using JavaScript, I want advanced memory management APIs and complete strict mode support, So that all ECMAScript edge cases are handled correctly.
Acceptance Criteria
-
WeakRef construction and deref:
new WeakRef(target)creates a weak reference to an object.weakRef.deref()returns the target object if still alive, orundefinedif collected. TypeError iftargetis not an object. Per ECMAScript §26.1. -
FinalizationRegistry with cleanup callbacks:
new FinalizationRegistry(callback)creates a registry..register(target, heldValue, unregisterToken?)registers a target..unregister(unregisterToken)removes registrations. Cleanup callback is called withheldValuewhen target is collected. Per ECMAScript §26.2. -
GC-awareness caveat: Since the engine uses
Rc<RefCell<>>(reference counting, no cycle collection),WeakRef.deref()always returns the target as long as any strong reference exists.FinalizationRegistrycleanup callbacks are called during explicitcleanupSome()or at engine-defined points (e.g., between microtask checkpoints). This is spec-compliant -- the spec says collection timing is implementation-defined. -
Strict mode:
evalandargumentsas identifiers:var eval = 1andvar arguments = 2throw SyntaxError in strict mode. Assignment toevalorarguments(eval = 1) throws SyntaxError. Using as function names, parameter names, or catch binding names also throws. Per ECMAScript §13.1.1. -
Strict mode:
withstatement rejection:with (obj) { ... }throws SyntaxError in strict mode. In sloppy mode,withexecutes the body withobjpushed onto the scope chain. Per ECMAScript §14.11. -
Strict mode: octal literal rejection: Legacy octal literals (
010,077) throw SyntaxError in strict mode.0o10(ES2015 octal) is allowed in both modes. Octal escape sequences in strings ("\01","\77") throw SyntaxError in strict mode."\0"alone (null character, not followed by digit) is allowed. Per ECMAScript §B.1.1. -
Strict mode: assignment to undeclared variables:
"use strict"; x = 42;throws ReferenceError at runtime (not creating a global). Verify this works correctly in the bytecode VM path. -
Strict mode: read-only property assignment: In strict mode, assignment to a non-writable property throws TypeError. Assignment to a getter-only accessor property throws TypeError. Assignment to a new property on a non-extensible object throws TypeError. (Depends on property descriptor system from Story 3.5.)
-
Test262 tests promoted: All relevant Test262 tests for WeakRef, FinalizationRegistry, and strict mode edge cases are promoted.
docs/JavaScript_Implementation_Checklist.mdanddocs/js_feature_matrix.mdupdated.just cipasses.
What NOT to Implement
- No cycle-detecting garbage collector --
Rc<RefCell<>>reference counting is the existing memory model. WeakRef/FinalizationRegistry are implemented using Ruststd::rc::Weak<>for genuine weak references, but cleanup timing is implementation-defined per spec. True cycle collection deferred. - No
withstatement in sloppy mode -- Parsewithonly to reject it in strict mode with a clear SyntaxError. Full sloppy-modewithsemantics (scope chain manipulation) are out of scope due to complexity and the feature being deprecated. Document as known limitation. - No
argumentsobject mutations -- Strict modeargumentsis a snapshot (not linked to parameters). Full sloppy-modeargumentsaliasing (wherearguments[0]and the first parameter are linked) is not required. - No
caller/calleerestrictions --arguments.callerandarguments.calleethrowing TypeError in strict mode are deferred. - No
Object.definePropertystrict enforcement -- AC #8 depends on Story 3.5's property descriptor system. If Story 3.5 is not yet landed, skip AC #8 and note it as a dependency.
Files to Modify
| File | Change |
|---|---|
crates/js_vm/src/value.rs |
Add Weak<RefCell<JsObjectData>> support for WeakRef. Add weak_ref_target: Option<Weak<RefCell<JsObjectData>>> field to JsObjectData. Add finalization_registry_data field. |
crates/js_vm/src/interpreter/weakref_builtins.rs |
New file -- WeakRef constructor, .deref() method, setup function. |
crates/js_vm/src/interpreter/finalization_registry_builtins.rs |
New file -- FinalizationRegistry constructor, .register(), .unregister(), cleanup scheduling. |
crates/js_vm/src/interpreter/mod.rs |
Add setup_weakref_builtins(), setup_finalization_registry_builtins() calls. |
crates/js_vm/src/interpreter/expressions/calls.rs |
Add WeakRef/FinalizationRegistry method dispatch. |
crates/js_parser/src/parser/mod.rs |
Add eval/arguments identifier checks in strict mode via check_strict_identifier(). |
crates/js_parser/src/parser/statements.rs |
Add with statement parsing: reject in strict mode with SyntaxError, reject in sloppy mode with "not supported" error. Add parameter name validation for eval/arguments in strict functions. |
crates/js_parser/src/parser/expressions.rs |
Add eval/arguments assignment validation in strict mode. Validate octal escape sequences in string literals when in strict mode. |
crates/js_parser/src/lexer.rs |
Add legacy octal literal (0[0-7]+) detection and rejection in strict mode. Ensure 0o octal works in both modes. |
crates/js_vm/src/interpreter/bytecode_exec.rs |
Verify assignment to undeclared variable throws ReferenceError in strict mode (bytecode path). |
crates/js_parser/src/parser/tests/strict_mode_tests.rs |
Add tests for eval/arguments restrictions, with rejection, octal rejection. |
crates/js_vm/src/interpreter/tests/strict_mode_tests.rs |
Add runtime strict mode tests. |
crates/js_vm/src/interpreter/tests/weakref_tests.rs |
New file -- WeakRef unit tests. |
crates/js_vm/src/interpreter/tests/finalization_registry_tests.rs |
New file -- FinalizationRegistry unit tests. |
tests/external/js262/js262_manifest.toml |
Promote passing tests. |
docs/JavaScript_Implementation_Checklist.md |
Check off WeakRef/FinalizationRegistry items. Update strict mode items. |
docs/old/js_feature_matrix.md |
Update coverage. |
Tasks / Subtasks
-
Task 1: Strict mode --
evalandargumentsrestrictions (AC: #4)- 1.1 Add
evalandargumentsto strict identifier checks incrates/js_parser/src/parser/mod.rs:- Extend
check_strict_identifier()(or create new check) to rejectevalandargumentsas:- Variable declarations:
var eval = 1,let arguments = 2,const eval = 3 - Function names:
function eval() {},function arguments() {} - Parameter names:
function f(eval) {},function f(arguments) {} - Catch binding:
catch (eval) {} - Assignment targets:
eval = 1,arguments = 2(in assignment expression)
- Variable declarations:
- Error:
"'{}' cannot be used as identifier in strict mode"(ECMAScript §13.1.1)
- Extend
- 1.2 Add validation in
parse_var_declaration()instatements.rs:- After extracting binding name, check if strict and name is
evalorarguments
- After extracting binding name, check if strict and name is
- 1.3 Add validation in function parameter parsing in
statements.rs:- In
parse_params()or equivalent, check each parameter name
- In
- 1.4 Add validation in assignment expressions in
expressions.rs:- In
parse_assignment_expression(), if LHS is Identifier("eval") or Identifier("arguments") and strict → SyntaxError
- In
- 1.5 Add parser tests in
strict_mode_tests.rs:var eval = 1in strict → SyntaxErrorvar arguments = 1in strict → SyntaxErroreval = 1in strict → SyntaxErrorarguments = 1in strict → SyntaxErrorfunction eval() {}in strict → SyntaxErrorfunction f(eval) {}in strict → SyntaxErrorfunction f(arguments) {}in strict → SyntaxErrorcatch (eval) {}in strict → SyntaxError- All above work fine in sloppy mode (no error)
- 1.1 Add
-
Task 2: Strict mode --
withstatement (AC: #5)- 2.1 Add
withkeyword recognition incrates/js_parser/src/token.rs:- Add
WithtoTokenKindandkeyword_from_str()(if not already present)
- Add
- 2.2 Add
withstatement parsing inparse_statement()instatements.rs:- When
TokenKind::Withencountered:- If strict mode →
CompileError: "'with' statement not allowed in strict mode"(ECMAScript §14.11.1) - If sloppy mode →
CompileError: "'with' statement is not supported"(known limitation, not spec-compliant but acceptable)
- If strict mode →
- Parse just enough to give a good error: consume
with,(, expression,), statement
- When
- 2.3 Add parser tests:
"use strict"; with (obj) { x; }→ SyntaxErrorwith (obj) { x; }in sloppy → error (unsupported, not SyntaxError -- different message)
- 2.1 Add
-
Task 3: Strict mode -- octal literal and escape rejection (AC: #6)
- 3.1 Add legacy octal literal detection in
crates/js_parser/src/lexer.rs:- When number starts with
0followed by[0-7](e.g.,010,077):- If strict mode →
CompileError: "Octal literals are not allowed in strict mode"(ECMAScript §B.1.1) - If sloppy mode → parse as octal number (or decimal -- implementation choice)
- If strict mode →
0o10(ES2015 explicit octal) allowed in both modes0x10(hex) allowed in both modes0b10(binary) allowed in both modes- IMPORTANT: The lexer needs access to strict mode flag. Either pass it as parameter or check after lexing.
- When number starts with
- 3.2 Add octal escape sequence rejection in string literals:
- In string literal parsing, when
\followed by[1-7]or\0followed by[0-9]:- If strict mode →
CompileError: "Octal escape sequences are not allowed in strict mode"(ECMAScript §B.1.2)
- If strict mode →
\0alone (null character escape, NOT followed by digit) is always allowed\x41(hex escape) and\u0041(unicode escape) always allowed
- In string literal parsing, when
- 3.3 Handle strict mode flag in lexer:
- Option A: Lexer has
strict: boolfield, set by parser when"use strict"is detected, then re-lex - Option B: Lexer always lexes octals, parser validates after receiving token
- Recommendation: Option B is simpler -- lexer produces
OctalLiteral(value)orOctalEscapeSequencetokens, parser rejects in strict mode
- Option A: Lexer has
- 3.4 Add tests:
"use strict"; var x = 010;→ SyntaxError"use strict"; var s = "\1";→ SyntaxErrorvar x = 010;in sloppy → OK (value 8)var x = 0o10;→ OK in both modes (value 8)"use strict"; var s = "\0";→ OK (null character, no digit following)"use strict"; var s = "\01";→ SyntaxError
- 3.1 Add legacy octal literal detection in
-
Task 4: Strict mode -- undeclared variable assignment (AC: #7)
- 4.1 Verify bytecode VM path in
crates/js_vm/src/interpreter/bytecode_exec.rs:- When
SetGlobalorSetVartargets an undeclared variable in strict mode:- Must throw ReferenceError, NOT silently create a global
- Check
Environment::set()behavior -- it may already throw for undeclared - The AST interpreter path already works (env.set() throws ReferenceError)
- When
- 4.2 Add explicit test in
crates/js_vm/src/interpreter/tests/strict_mode_tests.rs:"use strict"; undeclaredVar = 42;→ ReferenceErrorundeclaredVar = 42;in sloppy → creates global (or throws if not supported)
- 4.3 Verify strict flag propagation in bytecode compiler:
FunctionBlueprint.strict: boolmust be set correctly fromProgram.strictandFunction.strict- Check
crates/js_vm/src/bytecode/compiler/for strict flag handling
- 4.1 Verify bytecode VM path in
-
Task 5: WeakRef implementation (AC: #1, #3)
- 5.1 Add WeakRef infrastructure to
crates/js_vm/src/value.rs:- Add
weak_ref_target: Option<std::rc::Weak<RefCell<JsObjectData>>>field toJsObjectData - Add
init_weakref(target: &JsObject),is_weakref(),deref_weakref() -> Option<JsObject>helpers JsObjectisRc<RefCell<JsObjectData>>-- useRc::downgrade()to createWeakderef_weakref()callsWeak::upgrade()-- returnsSomeif target still alive,Noneif all strong refs dropped
- Add
- 5.2 Create
crates/js_vm/src/interpreter/weakref_builtins.rs:setup_weakref_builtins(env: &mut Environment)-- register WeakRef constructor and prototype- Constructor:
new WeakRef(target)-- TypeError if target is not an object. StoreRc::downgrade(&target.inner). - TypeError if called without
new
- 5.3 Implement
WeakRef.prototype.deref()(ECMAScript §26.1.3.2):- Call
Weak::upgrade()on stored weak reference - If
Some(inner)→ returnJsValue::Object(JsObject { inner }) - If
None→ returnJsValue::Undefined - Note: With
Rc<>, this will almost always return the target unless all other strong references have been dropped (e.g., variable went out of scope). This is spec-compliant -- timing is implementation-defined.
- Call
- 5.4 Wire WeakRef dispatch in
calls.rsand addsetup_weakref_builtins()inmod.rs - 5.5 Add unit tests in
crates/js_vm/src/interpreter/tests/weakref_tests.rs:new WeakRef({})→ object createdweakRef.deref()returns target when still referenced- TypeError on primitive target
- TypeError when called without
new .deref()returnsundefinedafter target is unreachable (test by dropping all strong refs in Rust test harness -- may not be testable from JS)
- 5.1 Add WeakRef infrastructure to
-
Task 6: FinalizationRegistry implementation (AC: #2, #3)
- 6.1 Add FinalizationRegistry infrastructure to
crates/js_vm/src/value.rs:FinalizationRegistryDatastruct:cleanup_callback: JsValue, // The callback function registrations: Vec<FinalizationEntry>,FinalizationEntrystruct:target: Weak<RefCell<JsObjectData>>, // Weak reference to watched object held_value: JsValue, // Value passed to callback unregister_token: Option<usize>, // Pointer identity of unregister token- Add
finalization_registry_data: Option<FinalizationRegistryData>toJsObjectData - Add helpers:
init_finalization_registry(),is_finalization_registry()
- 6.2 Create
crates/js_vm/src/interpreter/finalization_registry_builtins.rs:setup_finalization_registry_builtins(env: &mut Environment)- Constructor:
new FinalizationRegistry(callback)-- TypeError if callback is not callable - TypeError if called without
new
- 6.3 Implement FinalizationRegistry prototype methods:
.register(target, heldValue, unregisterToken?)(ECMAScript §26.2.3.2):- TypeError if
targetis not an object - TypeError if
target === unregisterToken(same object) - Store weak reference to target, heldValue, and unregisterToken identity
- TypeError if
.unregister(unregisterToken)(ECMAScript §26.2.3.3):- Remove all registrations with matching unregisterToken
- Return boolean (true if any removed)
- 6.4 Implement cleanup scheduling:
- Add
cleanup_finalization_registries()method to interpreter - For each registry: iterate entries, check if
target.upgrade()returns None - If target is dead: call cleanup callback with
heldValue, remove entry - Scheduling: Call
cleanup_finalization_registries()at microtask checkpoint (after each script execution, same point where microtasks are drained) - Note: With Rc<>, targets are only "dead" when all strong refs are dropped. In practice, most JS objects will have strong refs until the VM shuts down. This is acceptable per spec (cleanup timing is implementation-defined).
- Add
- 6.5 Wire dispatch in
calls.rsand setup inmod.rs - 6.6 Add unit tests in
crates/js_vm/src/interpreter/tests/finalization_registry_tests.rs:- Constructor with callback
.register()and.unregister()basic operations- TypeError on primitive target
- TypeError on non-callable callback
- Cleanup callback invocation (test by manually dropping strong refs in Rust test harness)
- 6.1 Add FinalizationRegistry infrastructure to
-
Task 7: Testing and validation (AC: #9)
- 7.1 Run vendored Test262 suite and promote passing tests:
cargo test -p rust_browser --test js262_harness js262_suite_matches_manifest_expectations -- --nocapturejust js262-status promote --id <test-id>
- 7.2 Run full Test262 suite and triage:
just test262-fulljust triage-test262-full
- 7.3 Run all existing JS test suites to verify no regressions:
cargo test -p js_vmcargo test -p rust_browser --test js_testscargo test -p rust_browser --test js_dom_testscargo test -p rust_browser --test js_eventscargo test -p rust_browser --test js_schedulingcargo test -p rust_browser --test js_asynccargo test -p rust_browser --test js_modules
- 7.4 Update
docs/JavaScript_Implementation_Checklist.md:- Check off WeakRef/FinalizationRegistry
- Update strict mode section with new checks
- 7.5 Update
docs/old/js_feature_matrix.md - 7.6 Run
just ci-- full validation pass
- 7.1 Run vendored Test262 suite and promote passing tests:
Dev Notes
Key Architecture Decisions
WeakRef uses std::rc::Weak<RefCell<JsObjectData>>. This is the natural Rust equivalent of a weak reference in an Rc<>-based system. Rc::downgrade() creates a Weak, and Weak::upgrade() returns Some if any strong refs remain. This gives us real weak reference semantics within Rust's reference counting model -- objects that are truly unreachable (no more Rc clones anywhere) will return None from deref().
FinalizationRegistry cleanup is best-effort. The spec explicitly says cleanup timing is implementation-defined (ECMAScript §9.10.3). Our implementation checks for dead targets during microtask checkpoints. In practice, with Rc<>, objects are rarely "dead" while JS code still runs (some Rc clone usually exists). This is acceptable and spec-compliant.
with statement is parsed only to reject. Implementing full with semantics (runtime scope chain manipulation) is complex and the feature is deprecated. Parse enough to give a clear error in both strict (SyntaxError) and sloppy (unsupported) modes.
Strict mode octal handling needs lexer-parser coordination. The lexer doesn't currently know about strict mode. Two approaches: (1) lexer always produces tokens, parser validates based on strict flag, or (2) parser feeds strict flag back to lexer. Approach (1) is simpler -- have the lexer tag octal literals and escape sequences, then the parser rejects them in strict context.
Current Strict Mode State
Already implemented (Phase 9 complete):
"use strict"directive prologue detection with escape sequence guard- Function-scoped and inherited strict mode
- Reserved word enforcement (7 words + yield)
- Duplicate parameter rejection
- Non-simple parameter +
"use strict"rejection delete identifierprohibition (parse-time)thisisundefinedin non-constructor strict calls.call()does not coerce non-objectthis
Key files:
- Parser strict checks:
crates/js_parser/src/parser/mod.rs:148-163(reserved words),mod.rs:360-382(directive) - Runtime strict:
crates/js_vm/src/interpreter/expressions/calls.rs:821-839(this binding),calls.rs:666-682(.call coercion) - Parser tests:
crates/js_parser/src/parser/tests/strict_mode_tests.rs(381 lines) - VM tests:
crates/js_vm/src/interpreter/tests/strict_mode_tests.rs(338 lines)
Implementation Patterns
WeakMap/WeakSet dispatch pattern (in calls.rs):
if obj.is_weakmap() {
match method_name.as_str() {
"get" => { ... }
"set" => { ... }
_ => {}
}
}
WeakRef and FinalizationRegistry follow this same dispatch pattern.
Strict identifier check (in parser/mod.rs):
fn check_strict_identifier(&self, name: &str) -> Result<(), CompileError> {
if self.strict && is_strict_reserved_word(name) {
Err(CompileError::new(...))
} else { Ok(()) }
}
Extend this to also check for "eval" and "arguments".
Critical Implementation Details
Weak<RefCell<JsObjectData>> requires JsObject to expose its Rc. Currently JsObject wraps Rc<RefCell<JsObjectData>> as inner. WeakRef needs Rc::downgrade(&obj.inner). This should work directly since inner is accessible within the js_vm crate.
Octal literal edge cases:
0alone is decimal zero, NOT octal -- don't reject08,09are decimal (invalid octal digits) -- don't reject as octal010is legacy octal = 8 -- reject in strict0o10is ES2015 octal = 8 -- allowed in strict"\0"is null char escape -- always allowed"\01"is octal escape -- reject in strict"\8"and"\9"are non-octal -- per spec, these are identity escapes (allowed even in strict per ES2021 Annex B revision)
FinalizationRegistry target === unregisterToken check. The spec says register(target, heldValue, unregisterToken) must throw if target is the same as unregisterToken. Use Rc::ptr_eq() for object identity comparison.
Dependencies
Story 3.5 (property descriptors): AC #8 (strict mode property assignment errors) depends on the property descriptor system from Story 3.5. If Story 3.5 is not yet complete, skip AC #8 and document it as a follow-up.
No dependency on Story 3.6. WeakRef/FinalizationRegistry are independent of Date/Map/Set.
Previous Story Intelligence
From Story 3.6 (Date/RegExp/Map/Set):
- New builtin types follow consistent pattern:
value.rsdata fields,*_builtins.rsfile,setup_*_builtins()call, dispatch incalls.rs indexmapavailable as dependency- No
unsafecode needed
From Story 3.4 (ES modules) review lessons:
- Don't leave stub code -- wire everything into execution paths
- Tests must test actual code paths
- Run
just ciafter each task
Risk Assessment
LOW: WeakRef. Straightforward wrapper around Rc::downgrade()/Weak::upgrade(). Small API surface.
LOW: Strict mode eval/arguments checks. Simple parse-time validation. Well-defined spec behavior. Existing check_strict_identifier() pattern to follow.
MEDIUM: FinalizationRegistry cleanup scheduling. Need to integrate cleanup checks into the microtask/event loop. Finding the right hook point requires understanding the execution pipeline in app_browser/browser_runtime.
MEDIUM: Octal literal/escape handling. Lexer-parser coordination for strict mode. Multiple edge cases (\0 vs \01, 08 vs 010). Need careful testing.
LOW: with statement. Parse-only, reject in all modes. No runtime semantics to implement.
Phased Implementation Strategy
Phase A -- Strict Mode Fixes (Tasks 1-4): Quick wins. Parse-time checks with existing infrastructure. Can be completed independently.
Phase B -- WeakRef (Task 5): Small, focused implementation using Rc::downgrade().
Phase C -- FinalizationRegistry (Task 6): More complex due to cleanup scheduling. Depends on understanding microtask checkpoint locations.
Phase D -- Testing + Validation (Task 7): After all implementations.
Project Structure Notes
- Parser changes in
crates/js_parser/src/(Layer 1) -- no layer violations - VM changes in
crates/js_vm/src/interpreter/(Layer 1) -- new files for WeakRef, FinalizationRegistry - Value type changes in
crates/js_vm/src/value.rs(Layer 1) - No
unsafecode needed - No new external dependencies
References
- ECMAScript §26.1 -- WeakRef Objects -- WeakRef constructor and prototype
- ECMAScript §26.2 -- FinalizationRegistry Objects -- FinalizationRegistry
- ECMAScript §9.10 -- CleanupFinalizationRegistry -- Cleanup abstract operation
- ECMAScript §13.1.1 -- Static Semantics: Early Errors -- eval/arguments restrictions
- ECMAScript §14.11 -- The with Statement -- with statement and strict mode
- ECMAScript §B.1.1 -- Numeric Literals -- Legacy octal
- ECMAScript §B.1.2 -- String Literals -- Octal escape sequences
- [Source: crates/js_parser/src/parser/mod.rs:148-163] -- Existing strict reserved word check
- [Source: crates/js_parser/src/parser/mod.rs:360-382] -- "use strict" directive detection
- [Source: crates/js_vm/src/interpreter/expressions/calls.rs:821-839] -- Strict this binding
- [Source: crates/js_vm/src/interpreter/weakmap_builtins.rs] -- WeakMap pattern to follow
- [Source: crates/js_vm/src/value.rs] -- JsObject is Rc<RefCell>
- [Source: _bmad-output/planning-artifacts/epics.md#Story 3.7] -- Story requirements
Dev Agent Record
Agent Model Used
Claude Opus 4.6 (1M context)
Debug Log References
- Fixed eval/arguments check regression: initially
check_strict_identifierrejectedevalandargumentsin ALL identifier positions (including expression reads likeeval("code")). Fixed by creating separatecheck_strict_binding_namefor binding-only positions. - Promoted 26 Test262 tests that now pass due to hex/octal/binary literal support and octal escape handling improvements.
Completion Notes List
- Task 1: Added
check_strict_binding_name()in parser for eval/arguments restriction in binding positions (var declarations, function names, parameters, catch bindings, assignment targets). 14 new parser tests. - Task 2: Added
Withtoken,withstatement parsing that rejects in strict mode (SyntaxError) and sloppy mode (unsupported). 2 new parser tests. - Task 3: Added hex/octal/binary number literal support to tokenizer. Added legacy octal literal rejection in parser strict mode. Added octal escape sequence detection (
has_octal_escapefield on String tokens) and rejection in strict mode. Added\x,\uescape handling in tokenizer. 8 new parser tests. - Task 4: Verified undeclared variable assignment already throws ReferenceError in both modes via
Environment::set(). Added 2 VM tests confirming behavior. - Task 5: Implemented WeakRef using
Rc::downgrade()/Weak::upgrade(). Addedweak_ref_targetfield to JsObjectData,init_weakref/is_weakref/deref_weakrefhelpers, constructor and.deref()dispatch. 8 new VM tests. - Task 6: Implemented FinalizationRegistry with
FinalizationRegistryData/FinalizationEntrystructs. Added constructor,.register(),.unregister()with target===token check. Cleanup infrastructure viafinalization_cleanup(). 8 new VM tests. - Task 7: Promoted 26 Test262 tests. Updated JavaScript_Implementation_Checklist.md and js_feature_matrix.md. All fmt/lint/test pass. Pre-existing policy issue in date_builtins.rs (from Story 3.6) not addressed.
Change Log
- 2026-03-16: Story 3.7 implementation complete. Added WeakRef, FinalizationRegistry, strict mode edge cases (eval/arguments, with, octal literals/escapes, undeclared variables). 26 Test262 tests promoted. 42 new tests added.
- 2026-03-16: Code review fixes (7 issues found, 6 fixed):
- [H1] Fixed
\xescape double-write bug in tokenizer — error path now skips char push - [H2] Fixed WeakRef function target deref returning wrapper object instead of original function
- [M1] Added eval/arguments restriction to compound and logical assignment operators
- [M2] Fixed
\08/\09false octal escape detection — only0-7digits trigger octal flag - [M3] Fixed
\uHHHHwith <4 hex digits silently producing wrong char — now treats as literal - [M4] Added TypeError when WeakRef/FinalizationRegistry called without
new; added tests - [L1] Fixed:
++eval/eval++/--arguments/arguments--prefix/postfix update check in strict mode - 11 new tests added (8 parser, 3 VM)
- [H1] Fixed
File List
New Files:
crates/js_vm/src/interpreter/weakref_builtins.rs— WeakRef constructor and setupcrates/js_vm/src/interpreter/finalization_registry_builtins.rs— FinalizationRegistry constructor and setupcrates/js_vm/src/interpreter/tests/weakref_tests.rs— WeakRef unit tests (8 tests)crates/js_vm/src/interpreter/tests/finalization_registry_tests.rs— FinalizationRegistry unit tests (8 tests)
Modified Files:
crates/js_parser/src/token.rs— AddedWithkeyword,has_octal_escapefield to String tokencrates/js_parser/src/tokenizer/mod.rs— Hex/octal/binary number literal parsing, octal escape detection,\x/\uescape handlingcrates/js_parser/src/tokenizer/tests/basic_tests.rs— Updated String token tests for new fieldcrates/js_parser/src/parser/mod.rs— Addedcheck_strict_binding_name(),Within describe/property_namecrates/js_parser/src/parser/statements.rs—withstatement handling, binding name checks in var/param/function/catchcrates/js_parser/src/parser/expressions.rs— eval/arguments assignment check, octal literal/escape rejectioncrates/js_parser/src/parser/tests/strict_mode_tests.rs— 26 new strict mode testscrates/js_vm/src/value.rs— WeakRef/FinalizationRegistry data types and helper methods,ptr_id()methodcrates/js_vm/src/interpreter/mod.rs— WeakRef/FinalizationRegistry module registration and setupcrates/js_vm/src/interpreter/expressions/calls.rs— WeakRef deref and FinalizationRegistry method dispatchcrates/js_vm/src/interpreter/tests/mod.rs— Added weakref_tests, finalization_registry_tests modulescrates/js_vm/src/interpreter/tests/strict_mode_tests.rs— 2 new undeclared variable teststests/external/js262/js262_manifest.toml— 26 tests promoted from known_fail to passdocs/JavaScript_Implementation_Checklist.md— Updated WeakRef/FinalizationRegistry and strict mode itemsdocs/old/js_feature_matrix.md— Added WeakRef/FinalizationRegistry entries, updated strict mode count