LibJS: Preserve strict unresolvable assignment references

Resolve strict global-looking assignment targets before evaluating their
right-hand side instead of lowering them directly to SetGlobal. Preserve
the original unresolvable reference for PutValue so a RHS-created global
cannot hide the ReferenceError.

Add a runtime test for that ordering and update the bytecode expectation
for a strict top-level assignment that now uses SetResolvedBinding.
This commit is contained in:
Andreas Kling 2026-05-22 02:19:46 +02:00 committed by Andreas Kling
parent 8b64f0079b
commit 19c1cae030
3 changed files with 22 additions and 9 deletions

View file

@ -2884,10 +2884,12 @@ fn emit_resolve_binding_for_identifier_assignment(
) -> Option<ResolvedBinding> {
let arena = generator.arena.clone();
let ident = &arena.identifiers[id];
if ident.is_local() || ident.is_global {
if ident.is_local() || (ident.is_global && !generator.strict) {
return None;
}
// In strict mode, a global-looking identifier may still be unresolvable. Preserve that reference across RHS
// evaluation so PutValue throws even if the RHS creates a global property with the same name.
let identifier = generator.intern_identifier_id(ident.name);
if generator.environment_coordinate_for_identifier(identifier).is_some() {
return None;

View file

@ -1,5 +1,5 @@
$75ea5b81 tagged-template-if-register-order.js:2:1
Registers: 10
$73266a71 tagged-template-if-register-order.js:2:1
Registers: 11
Blocks: 3
Constants:
[0] = String("use strict")
@ -20,11 +20,12 @@ block0:
[ c0] JumpFalse condition:reg6, target:block2
block1:
[ d0] GetGlobal dst:reg5, `x`
[ e0] GetById dst:reg9, base:reg5, `toUpperCase` (x.toUpperCase)
[ f8] Call dst:reg7, callee:reg9, this_value:reg5, x.toUpperCase
[ 118] SetGlobal `y`, src:reg7
[ 128] Mov dst:reg8, src:reg7
[ d0] ResolveBinding dst:reg7, `y`
[ e0] GetGlobal dst:reg9, `x`
[ f0] GetById dst:reg10, base:reg9, `toUpperCase` (x.toUpperCase)
[ 108] Call dst:reg5, callee:reg10, this_value:reg9, x.toUpperCase
[ 128] SetResolvedBinding environment:reg7, `y`, src:reg5
[ 138] Mov dst:reg8, src:reg5
block2:
[ 138] End value:reg8
[ 148] End value:reg8

View file

@ -16,6 +16,16 @@ test("assignment to function call in strict mode is a SyntaxError", () => {
expect("'use strict'; foo() = 'foo'").not.toEval();
});
test("strict assignment to unresolvable reference preserves initial reference", () => {
delete globalThis.__test_unresolvable_assignment;
expect(() => {
"use strict";
__test_unresolvable_assignment = (globalThis.__test_unresolvable_assignment = 5);
}).toThrowWithMessage(ReferenceError, "'__test_unresolvable_assignment' is not defined");
expect(globalThis.__test_unresolvable_assignment).toBe(5);
delete globalThis.__test_unresolvable_assignment;
});
test("assignment to inline function call", () => {
expect(() => {
(function () {})() = "foo";