LibJS: Fix destructured primitive string const loops

Mark binding-pattern identifiers with their declaration kind so local
destructuring assignments use the normal TDZ and const assignment path.
This makes local const destructuring match the environment-backed path.

Also teach GetById to expose primitive string virtual index properties
before boxing, matching StringGetOwnProperty and GetByValue. Together
these fix the SpiderMonkey for-in/of const declaration coverage and the
lexical destructuring TDZ test.
This commit is contained in:
Andreas Kling 2026-05-21 14:55:11 +02:00 committed by Andreas Kling
parent b42aa88189
commit bf530ad2af
21 changed files with 286 additions and 235 deletions

View file

@ -14,6 +14,7 @@
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
#include <LibJS/Runtime/FunctionObject.h>
#include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/Shape.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/Runtime/Value.h>
@ -91,6 +92,15 @@ ALWAYS_INLINE ThrowCompletionOr<Value> get_by_id(VM& vm, GetBaseIdentifier get_b
}
}
auto const& property_name = get_property_name();
if (base_value.is_string()) {
// https://tc39.es/ecma262/#sec-stringgetownproperty
// String exotic objects expose virtual own properties for canonical string indexes.
auto string_value = TRY(base_value.as_string().get(vm, property_name));
if (string_value.has_value())
return *string_value;
}
auto base_obj = TRY(base_object_for_get(vm, base_value, get_base_identifier, get_property_name));
if constexpr (mode == GetByIdMode::Length) {
@ -152,7 +162,7 @@ ALWAYS_INLINE ThrowCompletionOr<Value> get_by_id(VM& vm, GetBaseIdentifier get_b
prototype_chain_validity = shape.prototype()->shape().prototype_chain_validity();
CacheableGetPropertyMetadata cacheable_metadata;
auto value = TRY(base_obj->internal_get(get_property_name(), this_value, &cacheable_metadata));
auto value = TRY(base_obj->internal_get(property_name, this_value, &cacheable_metadata));
// If internal_get() caused object's shape change, we can no longer be sure
// that collected metadata is valid, e.g. if getter in prototype chain added

View file

@ -7012,6 +7012,9 @@ fn assign_to_for_in_of_lhs(generator: &mut Generator, lhs: &ForInOfLhs, value: &
// =============================================================================
/// Whether we are initializing a new binding or setting an existing one.
// https://tc39.es/ecma262/#sec-runtime-semantics-bindinginitialization
// https://tc39.es/ecma262/#sec-runtime-semantics-destructuringassignmentevaluation
// Binding patterns initialize lexical declarations, while assignment patterns perform PutValue.
#[derive(Clone, Copy)]
enum BindingMode {
/// `const` or `let` declarations: emit InitializeLexicalBinding.
@ -7063,8 +7066,17 @@ fn emit_set_variable_with_mode(
let arena = generator.arena.clone();
let ident = &arena.identifiers[id];
if ident.is_local() {
let local = generator.resolve_local(ident.local_index, ident.local_type.unwrap());
generator.emit_mov(&local, value);
match mode {
BindingMode::InitializeLexical => {
let local = generator.resolve_local(ident.local_index, ident.local_type.unwrap());
generator.emit_mov(&local, value);
generator.mark_local_initialized(ident.local_index);
}
BindingMode::Set => {
emit_tdz_check_if_needed(generator, id);
emit_set_variable(generator, id, value);
}
}
} else {
let id = generator.intern_identifier_id(ident.name);
match mode {

View file

@ -212,8 +212,6 @@ impl Parser<'_> {
.zip(bound_names.iter())
.map(|(n, (_, id))| (n.as_slice(), Some(*id)))
.collect();
// NOTE: Binding pattern identifiers don't get declaration_kind,
// matching C++ behavior where only simple identifiers do.
let Self {
scope_collector, arena, ..
} = self;
@ -221,7 +219,7 @@ impl Parser<'_> {
&entries,
declaration_line,
declaration_column,
None,
Some(DeclarationKind::Var),
&mut arena.identifiers,
&arena.strings,
&mut arena.scopes,
@ -230,17 +228,13 @@ impl Parser<'_> {
let refs: Vec<&[u16]> = name_strs.iter().map(|n| n.as_slice()).collect();
self.scope_collector
.add_lexical_declaration(&refs, declaration_line, declaration_column);
// Register each binding pattern identifier for scope analysis
// so they get is_local() annotations.
// NOTE: C++ does not pass declaration_kind for binding pattern identifiers,
// only for simple identifier declarations.
let Self {
scope_collector, arena, ..
} = self;
for (_name, id) in &bound_names {
scope_collector.register_identifier(
*id,
None,
Some(kind),
&mut arena.identifiers,
&arena.strings,
&mut arena.scopes,

View file

@ -4,10 +4,10 @@ Program (script) @6:1
│ ├─ BindingPattern (object)
│ │ ├─ entry
│ │ │ └─ name
│ │ │ └─ Identifier "foo" [global] @6:7
│ │ │ └─ Identifier "foo" [global] (let) @6:7
│ │ └─ entry
│ │ └─ name
│ │ └─ Identifier "bar" [global] @6:12
│ │ └─ Identifier "bar" [global] (let) @6:12
│ └─ ObjectExpression @6:20
├─ VariableDeclaration (let) @7:1
│ └─ VariableDeclarator @7:1
@ -16,23 +16,23 @@ Program (script) @6:1
│ │ │ ├─ name
│ │ │ │ └─ Identifier "x" [global] @7:7
│ │ │ └─ alias
│ │ │ └─ Identifier "aliased" [global] @7:10
│ │ │ └─ Identifier "aliased" [global] (let) @7:10
│ │ └─ entry
│ │ ├─ name
│ │ │ └─ Identifier "y" [global] @7:19
│ │ └─ alias
│ │ └─ Identifier "also" [global] @7:22
│ │ └─ Identifier "also" [global] (let) @7:22
│ └─ ObjectExpression @7:31
├─ VariableDeclaration (let) @8:1
│ └─ VariableDeclarator @8:1
│ ├─ BindingPattern (array)
│ │ ├─ entry
│ │ │ └─ alias
│ │ │ └─ Identifier "first" [global] @8:7
│ │ │ └─ Identifier "first" [global] (let) @8:7
│ │ ├─ Elision
│ │ └─ entry
│ │ └─ alias
│ │ └─ Identifier "third" [global] @8:16
│ │ └─ Identifier "third" [global] (let) @8:16
│ └─ ArrayExpression @8:26
├─ VariableDeclaration (let) @9:1
│ └─ VariableDeclarator @9:1
@ -44,26 +44,26 @@ Program (script) @6:1
│ │ │ └─ BindingPattern (object)
│ │ │ └─ entry
│ │ │ └─ name
│ │ │ └─ Identifier "inner" [global] @9:17
│ │ │ └─ Identifier "inner" [global] (let) @9:17
│ │ └─ entry (rest)
│ │ └─ name
│ │ └─ Identifier "rest" [global] @9:29
│ │ └─ Identifier "rest" [global] (let) @9:29
│ └─ ObjectExpression @9:38
├─ VariableDeclaration (let) @10:1
│ └─ VariableDeclarator @10:1
│ ├─ BindingPattern (array)
│ │ ├─ entry
│ │ │ └─ alias
│ │ │ └─ Identifier "a" [global] @10:7
│ │ │ └─ Identifier "a" [global] (let) @10:7
│ │ └─ entry
│ │ └─ alias
│ │ └─ BindingPattern (array)
│ │ ├─ entry
│ │ │ └─ alias
│ │ │ └─ Identifier "b" [global] @10:12
│ │ │ └─ Identifier "b" [global] (let) @10:12
│ │ └─ entry
│ │ └─ alias
│ │ └─ Identifier "c" [global] @10:15
│ │ └─ Identifier "c" [global] (let) @10:15
│ └─ ArrayExpression @10:23
└─ FunctionDeclaration "destructured" @12:1
├─ parameters

View file

@ -6,12 +6,12 @@ Program (script) @1:1
│ │ ├─ name
│ │ │ └─ Identifier "foo" [global] @1:7
│ │ └─ alias
│ │ └─ Identifier "a" [global] @1:12
│ │ └─ Identifier "a" [global] (let) @1:12
│ └─ entry
│ ├─ name
│ │ └─ Identifier "1" [global] @1:15
│ └─ alias
│ └─ Identifier "b" [global] @1:18
│ └─ Identifier "b" [global] (let) @1:18
└─ ObjectExpression @1:24
├─ ObjectProperty @1:24
│ ├─ StringLiteral "foo" @1:26

View file

@ -7,10 +7,10 @@ Program (script) @1:1
│ ├─ BindingPattern (array)
│ │ ├─ entry
│ │ │ └─ alias
│ │ │ └─ Identifier "a" [variable:0] @2:10
│ │ │ └─ Identifier "a" [variable:0] (let) @2:10
│ │ └─ entry
│ │ └─ alias
│ │ └─ Identifier "b" [variable:1] @2:13
│ │ └─ Identifier "b" [variable:1] (let) @2:13
│ └─ ArrayExpression @2:18
│ ├─ NumericLiteral 1 @2:19
│ └─ NumericLiteral 2 @2:22
@ -19,12 +19,12 @@ Program (script) @1:1
│ ├─ BindingPattern (object)
│ │ ├─ entry
│ │ │ └─ name
│ │ │ └─ Identifier "x" [variable:2] @3:11
│ │ │ └─ Identifier "x" [variable:2] (let) @3:11
│ │ └─ entry
│ │ ├─ name
│ │ │ └─ Identifier "y" [global] @3:14
│ │ └─ alias
│ │ └─ Identifier "z" [variable:3] @3:17
│ │ └─ Identifier "z" [variable:3] (let) @3:17
│ └─ ObjectExpression @3:23
│ ├─ ObjectProperty @3:23
│ │ ├─ StringLiteral "x" @3:25
@ -36,7 +36,7 @@ Program (script) @1:1
└─ BinaryExpression (+) @4:22
├─ BinaryExpression (+) @4:18
│ ├─ BinaryExpression (+) @4:14
│ │ ├─ Identifier "a" [variable:0] @4:12
│ │ └─ Identifier "b" [variable:1] @4:16
│ └─ Identifier "x" [variable:2] @4:20
└─ Identifier "z" [variable:3] @4:24
│ │ ├─ Identifier "a" [variable:0] (let) @4:12
│ │ └─ Identifier "b" [variable:1] (let) @4:16
│ └─ Identifier "x" [variable:2] (let) @4:20
└─ Identifier "z" [variable:3] (let) @4:24

View file

@ -61,18 +61,18 @@ Program (script) @2:1
│ │ └─ BindingPattern (array)
│ │ ├─ entry
│ │ │ └─ alias
│ │ │ └─ Identifier "a" [variable:0] @25:15
│ │ │ └─ Identifier "a" [variable:0] (let) @25:15
│ │ └─ entry
│ │ └─ alias
│ │ └─ Identifier "b" [variable:1] @25:18
│ │ └─ Identifier "b" [variable:1] (let) @25:18
│ ├─ rhs
│ │ └─ Identifier "pairs" [argument:0] @25:24
│ └─ body
│ └─ BlockStatement @25:31
│ └─ ExpressionStatement @26:9
│ └─ BinaryExpression (+) @26:11
│ ├─ Identifier "a" [variable:0] @26:9
│ └─ Identifier "b" [variable:1] @26:13
│ ├─ Identifier "a" [variable:0] (let) @26:9
│ └─ Identifier "b" [variable:1] (let) @26:13
└─ FunctionDeclaration "for_in_eval" [direct-eval] [uses-this] [might-need-arguments] @31:1
├─ parameters
│ └─ Identifier "obj" @31:22

View file

@ -87,10 +87,10 @@ Program (script) @4:1
│ │ ├─ BindingPattern (array)
│ │ │ ├─ entry
│ │ │ │ └─ alias
│ │ │ │ └─ Identifier "a" [variable:0] @28:10
│ │ │ │ └─ Identifier "a" [variable:0] (let) @28:10
│ │ │ └─ entry
│ │ │ └─ alias
│ │ │ └─ Identifier "b" [variable:1] @28:13
│ │ │ └─ Identifier "b" [variable:1] (let) @28:13
│ │ └─ ArrayExpression @28:18
│ │ ├─ NumericLiteral 1 @28:19
│ │ └─ NumericLiteral 2 @28:22
@ -99,10 +99,10 @@ Program (script) @4:1
│ │ ├─ BindingPattern (object)
│ │ │ ├─ entry
│ │ │ │ └─ name
│ │ │ │ └─ Identifier "c" [variable:2] @29:13
│ │ │ │ └─ Identifier "c" [variable:2] (const) @29:13
│ │ │ └─ entry
│ │ │ └─ name
│ │ │ └─ Identifier "d" [variable:3] @29:16
│ │ │ └─ Identifier "d" [variable:3] (const) @29:16
│ │ └─ ObjectExpression @29:22
│ │ ├─ ObjectProperty @29:22
│ │ │ ├─ StringLiteral "c" @29:24
@ -115,10 +115,10 @@ Program (script) @4:1
│ │ ├─ BindingPattern (array)
│ │ │ ├─ entry
│ │ │ │ └─ alias
│ │ │ │ └─ Identifier "e" [variable:4] @30:10
│ │ │ │ └─ Identifier "e" [variable:4] (var) @30:10
│ │ │ └─ entry (rest)
│ │ │ └─ alias
│ │ │ └─ Identifier "f" [variable:5] @30:16
│ │ │ └─ Identifier "f" [variable:5] (var) @30:16
│ │ └─ ArrayExpression @30:21
│ │ ├─ NumericLiteral 5 @30:22
│ │ ├─ NumericLiteral 6 @30:25
@ -129,13 +129,13 @@ Program (script) @4:1
│ │ ├─ BinaryExpression (+) @31:22
│ │ │ ├─ BinaryExpression (+) @31:18
│ │ │ │ ├─ BinaryExpression (+) @31:14
│ │ │ │ │ ├─ Identifier "a" [variable:0] @31:12
│ │ │ │ │ └─ Identifier "b" [variable:1] @31:16
│ │ │ │ └─ Identifier "c" [variable:2] @31:20
│ │ │ └─ Identifier "d" [variable:3] @31:24
│ │ └─ Identifier "e" [variable:4] @31:28
│ │ │ │ │ ├─ Identifier "a" [variable:0] (let) @31:12
│ │ │ │ │ └─ Identifier "b" [variable:1] (let) @31:16
│ │ │ │ └─ Identifier "c" [variable:2] (const) @31:20
│ │ │ └─ Identifier "d" [variable:3] (const) @31:24
│ │ └─ Identifier "e" [variable:4] (var) @31:28
│ └─ MemberExpression [computed] @31:33
│ ├─ Identifier "f" [variable:5] @31:32
│ ├─ Identifier "f" [variable:5] (var) @31:32
│ └─ NumericLiteral 0 @31:34
└─ FunctionDeclaration "multi_declarator" @35:1
└─ body

View file

@ -49,13 +49,13 @@ Program (script) @3:1
│ │ │ ├─ name
│ │ │ │ └─ Identifier "arguments" [variable:0] @37:11
│ │ │ └─ alias
│ │ │ └─ Identifier "x" [variable:1] @37:22
│ │ │ └─ Identifier "x" [variable:1] (let) @37:22
│ │ └─ ObjectExpression @37:28
│ │ └─ ObjectProperty @37:28
│ │ ├─ StringLiteral "arguments" @37:30
│ │ └─ NumericLiteral 1 @37:41
│ └─ ReturnStatement @38:5
│ └─ Identifier "x" [variable:1] @38:12
│ └─ Identifier "x" [variable:1] (let) @38:12
└─ FunctionDeclaration "method_named_arguments" @42:1
└─ body
└─ FunctionBody @43:5

View file

@ -14,7 +14,7 @@ block0:
[ 70] End value:reg5
f$ce57f4e1 assign-to-let-variable-tdz.js:7:5
f$d3dfd701 assign-to-let-variable-tdz.js:7:5
Registers: 10
Blocks: 3
Locals: a~0
@ -26,23 +26,19 @@ block0:
[ 0] ThrowIfNullish src:arg0
[ 8] GetById dst:reg5, base:arg0, `location`
[ 20] Mov dst:a~0, src:reg5
[ 30] ThrowIfTDZ src:a~0
[ 38] Typeof dst:reg5, src:a~0
[ 48] LooselyEquals dst:reg6, lhs:String("string"), rhs:reg5
[ 58] Mov dst:reg5, src:reg6
[ 68] JumpFalse condition:reg6, target:block2
[ 30] Typeof dst:reg5, src:a~0
[ 40] LooselyEquals dst:reg6, lhs:String("string"), rhs:reg5
[ 50] Mov dst:reg5, src:reg6
[ 60] JumpFalse condition:reg6, target:block2
block1:
[ 78] GetGlobal dst:reg8, `g`
[ 88] ThrowIfTDZ src:a~0
[ 90] Mov dst:reg9, src:a~0
[ a0] Call dst:reg7, callee:reg8, this_value:Undefined, g, arguments:[reg9]
[ c8] ThrowIfTDZ src:a~0
[ d0] Mov2 dst1:a~0, src1:reg7, dst2:reg5, src2:reg7
[ 70] GetGlobal dst:reg8, `g`
[ 80] Mov dst:reg9, src:a~0
[ 90] Call dst:reg7, callee:reg8, this_value:Undefined, g, arguments:[reg9]
[ b8] Mov2 dst1:a~0, src1:reg7, dst2:reg5, src2:reg7
block2:
[ e8] ThrowIfTDZ src:a~0
[ f0] Return value:a~0
[ d0] Return value:a~0
g$f452072f assign-to-let-variable-tdz.js:4:17

View file

@ -10,7 +10,7 @@ block0:
[ 30] End value:reg5
f$c7e5aa3e destructure-array-rest.js:6:5
f$26085a1b destructure-array-rest.js:6:5
Registers: 14
Blocks: 19
Locals: a~0, b~1
@ -34,65 +34,64 @@ block1:
[ 80] JumpIf condition:reg9, true_target:block14, false_target:block13
block2:
[ 90] ThrowIfTDZ src:b~1
[ 98] Return value:b~1
[ 90] Return value:b~1
block3:
[ a0] Jump target:block5
[ 98] Jump target:block5
block4:
[ a8] Mov dst:reg12, src:Undefined
[ b8] Jump target:block6
[ a0] Mov dst:reg12, src:Undefined
[ b0] Jump target:block6
block5:
[ c0] IteratorNextUnpack dst_value:reg12, dst_done:reg6, iterator_object:reg7, iterator_next:reg8, iterator_done:reg9
[ d8] JumpTrue condition:reg6, target:block4
[ b8] IteratorNextUnpack dst_value:reg12, dst_done:reg6, iterator_object:reg7, iterator_next:reg8, iterator_done:reg9
[ d0] JumpTrue condition:reg6, target:block4
block6:
[ e8] Mov dst:a~0, src:reg12
[ f8] JumpFalse condition:reg6, target:block8
[ e0] Mov dst:a~0, src:reg12
[ f0] JumpFalse condition:reg6, target:block8
block7:
[ 108] NewArray dst:reg13
[ 118] Jump target:block9
[ 100] NewArray dst:reg13
[ 110] Jump target:block9
block8:
[ 120] IteratorToArray dst:reg13, iterator_object:reg7, iterator_next_method:reg8, iterator_done_property:reg9
[ 118] IteratorToArray dst:reg13, iterator_object:reg7, iterator_next_method:reg8, iterator_done_property:reg9
block9:
[ 138] Mov dst:b~1, src:reg13
[ 130] Mov dst:b~1, src:reg13
block10:
[ 148] JumpFalse condition:reg9, target:block12
[ 140] JumpFalse condition:reg9, target:block12
block11:
[ 158] Jump target:block2
[ 150] Return value:b~1
block12:
[ 160] IteratorClose iterator_object:reg7, iterator_next:reg8, iterator_done:reg9, completion_value:Undefined
[ 178] Jump target:block11
[ 158] IteratorClose iterator_object:reg7, iterator_next:reg8, iterator_done:reg9, completion_value:Undefined
[ 170] Jump target:block11
block13:
[ 180] JumpStrictlyEquals lhs:reg10, rhs:Int32(1), true_target:block15, false_target:block16
[ 178] JumpStrictlyEquals lhs:reg10, rhs:Int32(1), true_target:block15, false_target:block16
block14:
[ 198] JumpStrictlyEquals lhs:reg10, rhs:Int32(2), true_target:block17, false_target:block18
[ 190] JumpStrictlyEquals lhs:reg10, rhs:Int32(2), true_target:block17, false_target:block18
block15:
[ 1b0] IteratorClose iterator_object:reg7, iterator_next:reg8, iterator_done:reg9, completion_value:reg11
[ 1c8] Throw src:reg11
[ 1a8] IteratorClose iterator_object:reg7, iterator_next:reg8, iterator_done:reg9, completion_value:reg11
[ 1c0] Throw src:reg11
block16:
[ 1d0] IteratorClose iterator_object:reg7, iterator_next:reg8, iterator_done:reg9, completion_value:Undefined
[ 1e8] Jump target:block14
[ 1c8] IteratorClose iterator_object:reg7, iterator_next:reg8, iterator_done:reg9, completion_value:Undefined
[ 1e0] Jump target:block14
block17:
[ 1f0] Return value:reg11
[ 1e8] Return value:reg11
block18:
[ 1f8] Throw src:reg11
[ 1f0] Throw src:reg11
Exception handlers:
[ a0 .. c0] => handler block1
[ e8 .. 120] => handler block1
[ 138 .. 148] => handler block1
[ 98 .. b8] => handler block1
[ e0 .. 118] => handler block1
[ 130 .. 140] => handler block1

View file

@ -12,7 +12,7 @@ block0:
[ 58] End value:reg5
f$f789224c destructuring-let-compound-assign.js:2:5
f$ffd4f57c destructuring-let-compound-assign.js:2:5
Registers: 8
Blocks: 1
Locals: a~0
@ -23,11 +23,9 @@ f$f789224c destructuring-let-compound-assign.js:2:5
block0:
[ 0] ThrowIfNullish src:arg0
[ 8] GetById dst:reg5, base:arg0, `patchFlag`
[ 20] Mov dst:a~0, src:reg5
[ 30] ThrowIfTDZ src:a~0
[ 38] Mov2 dst1:reg5, src1:a~0, dst2:reg6, src2:arg1
[ 50] GetById dst:reg7, base:reg6, `patchFlag` (e.patchFlag)
[ 68] BitwiseAnd dst:reg6, lhs:Int32(16), rhs:reg7
[ 78] BitwiseOr dst:reg7, lhs:reg5, rhs:reg6
[ 88] Mov dst:a~0, src:reg7
[ 98] End value:Undefined
[ 20] Mov3 dst1:a~0, src1:reg5, dst2:reg5, src2:a~0, dst3:reg6, src3:arg1
[ 40] GetById dst:reg7, base:reg6, `patchFlag` (e.patchFlag)
[ 58] BitwiseAnd dst:reg6, lhs:Int32(16), rhs:reg7
[ 68] BitwiseOr dst:reg7, lhs:reg5, rhs:reg6
[ 78] Mov dst:a~0, src:reg7
[ 88] End value:Undefined

View file

@ -21,7 +21,7 @@ block0:
[ b8] End value:reg5
collect$aa10966b for-in-object-property-iterator-next.js:2:18
collect$a1c4c33b for-in-object-property-iterator-next.js:2:18
Registers: 12
Blocks: 6
Locals: key~0, keys~1
@ -47,8 +47,6 @@ block4:
block5:
[ 68] Mov dst:key~0, src:reg6
[ 78] GetById dst:reg9, base:keys~1, `push` (keys.push)
[ 90] Mov dst:reg10, src:keys~1
[ a0] ThrowIfTDZ src:key~0
[ a8] Mov dst:reg11, src:key~0
[ b8] Call dst:reg8, callee:reg9, this_value:reg10, keys.push, arguments:[reg11]
[ e0] Jump target:block2
[ 90] Mov2 dst1:reg10, src1:keys~1, dst2:reg11, src2:key~0
[ a8] Call dst:reg8, callee:reg9, this_value:reg10, keys.push, arguments:[reg11]
[ d0] Jump target:block2

View file

@ -11,7 +11,7 @@ block0:
[ 38] End value:reg5
f$08f135b5 for-of-cond-rhs-block-order.js:2:21
f$8a532e3d for-of-cond-rhs-block-order.js:2:21
Registers: 19
Blocks: 31
Locals: r~0, n~1
@ -69,85 +69,84 @@ block9:
[ 1a0] JumpIf condition:reg15, true_target:block22, false_target:block21
block10:
[ 1b0] ThrowIfTDZ src:r~0
[ 1b8] Jump target:block2
[ 1b0] Jump target:block2
block11:
[ 1c0] Jump target:block13
[ 1b8] Jump target:block13
block12:
[ 1c8] Mov dst:reg18, src:Undefined
[ 1d8] Jump target:block14
[ 1c0] Mov dst:reg18, src:Undefined
[ 1d0] Jump target:block14
block13:
[ 1e0] IteratorNextUnpack dst_value:reg18, dst_done:reg12, iterator_object:reg13, iterator_next:reg14, iterator_done:reg15
[ 1f8] JumpTrue condition:reg12, target:block12
[ 1d8] IteratorNextUnpack dst_value:reg18, dst_done:reg12, iterator_object:reg13, iterator_next:reg14, iterator_done:reg15
[ 1f0] JumpTrue condition:reg12, target:block12
block14:
[ 208] Mov dst:r~0, src:reg18
[ 218] JumpFalse condition:reg12, target:block16
[ 200] Mov dst:r~0, src:reg18
[ 210] JumpFalse condition:reg12, target:block16
block15:
[ 228] Mov dst:reg18, src:Undefined
[ 238] Jump target:block17
[ 220] Mov dst:reg18, src:Undefined
[ 230] Jump target:block17
block16:
[ 240] IteratorNextUnpack dst_value:reg18, dst_done:reg12, iterator_object:reg13, iterator_next:reg14, iterator_done:reg15
[ 258] JumpTrue condition:reg12, target:block15
[ 238] IteratorNextUnpack dst_value:reg18, dst_done:reg12, iterator_object:reg13, iterator_next:reg14, iterator_done:reg15
[ 250] JumpTrue condition:reg12, target:block15
block17:
[ 268] Mov dst:n~1, src:reg18
[ 260] Mov dst:n~1, src:reg18
block18:
[ 278] JumpFalse condition:reg15, target:block20
[ 270] JumpFalse condition:reg15, target:block20
block19:
[ 288] Jump target:block10
[ 280] Jump target:block10
block20:
[ 290] IteratorClose iterator_object:reg13, iterator_next:reg14, iterator_done:reg15, completion_value:Undefined
[ 2a8] Jump target:block19
[ 288] IteratorClose iterator_object:reg13, iterator_next:reg14, iterator_done:reg15, completion_value:Undefined
[ 2a0] Jump target:block19
block21:
[ 2b0] JumpStrictlyEquals lhs:reg16, rhs:Int32(1), true_target:block23, false_target:block24
[ 2a8] JumpStrictlyEquals lhs:reg16, rhs:Int32(1), true_target:block23, false_target:block24
block22:
[ 2c8] JumpStrictlyEquals lhs:reg16, rhs:Int32(2), true_target:block25, false_target:block26
[ 2c0] JumpStrictlyEquals lhs:reg16, rhs:Int32(2), true_target:block25, false_target:block26
block23:
[ 2e0] IteratorClose iterator_object:reg13, iterator_next:reg14, iterator_done:reg15, completion_value:reg17
[ 2f8] Throw src:reg17
[ 2d8] IteratorClose iterator_object:reg13, iterator_next:reg14, iterator_done:reg15, completion_value:reg17
[ 2f0] Throw src:reg17
block24:
[ 300] IteratorClose iterator_object:reg13, iterator_next:reg14, iterator_done:reg15, completion_value:Undefined
[ 318] Jump target:block22
[ 2f8] IteratorClose iterator_object:reg13, iterator_next:reg14, iterator_done:reg15, completion_value:Undefined
[ 310] Jump target:block22
block25:
[ 320] Mov2 dst1:reg7, src1:reg16, dst2:reg9, src2:reg17
[ 338] Jump target:block7
[ 318] Mov2 dst1:reg7, src1:reg16, dst2:reg9, src2:reg17
[ 330] Jump target:block7
block26:
[ 340] Throw src:reg17
[ 338] Throw src:reg17
block27:
[ 348] IteratorClose iterator_object:reg5, iterator_next:reg8, iterator_done:reg6, completion_value:reg9
[ 360] Throw src:reg9
[ 340] IteratorClose iterator_object:reg5, iterator_next:reg8, iterator_done:reg6, completion_value:reg9
[ 358] Throw src:reg9
block28:
[ 368] IteratorClose iterator_object:reg5, iterator_next:reg8, iterator_done:reg6, completion_value:Undefined
[ 380] JumpStrictlyEquals lhs:reg7, rhs:Int32(2), true_target:block29, false_target:block30
[ 360] IteratorClose iterator_object:reg5, iterator_next:reg8, iterator_done:reg6, completion_value:Undefined
[ 378] JumpStrictlyEquals lhs:reg7, rhs:Int32(2), true_target:block29, false_target:block30
block29:
[ 398] Return value:reg9
[ 390] Return value:reg9
block30:
[ 3a0] Throw src:reg9
[ 398] Throw src:reg9
Exception handlers:
[ 150 .. 1c0] => handler block6
[ 1c0 .. 1e0] => handler block9
[ 1e0 .. 208] => handler block6
[ 208 .. 240] => handler block9
[ 240 .. 268] => handler block6
[ 268 .. 278] => handler block9
[ 278 .. 348] => handler block6
[ 150 .. 1b8] => handler block6
[ 1b8 .. 1d8] => handler block9
[ 1d8 .. 200] => handler block6
[ 200 .. 238] => handler block9
[ 238 .. 260] => handler block6
[ 260 .. 270] => handler block9
[ 270 .. 340] => handler block6

View file

@ -1,4 +1,4 @@
$0a509d5d for-of-iteration-env-capacity.js:1:14
$078cac4d for-of-iteration-env-capacity.js:1:14
Registers: 13
Blocks: 9
Locals: x~0
@ -28,24 +28,22 @@ block3:
[ b0] JumpStrictlyEquals lhs:reg9, rhs:Int32(1), true_target:block5, false_target:block6
block4:
[ c8] Mov dst:x~0, src:reg11
[ d8] ThrowIfTDZ src:x~0
[ e0] Mov dst:reg5, src:x~0
[ f0] Jump target:block2
[ c8] Mov2 dst1:x~0, src1:reg11, dst2:reg5, src2:x~0
[ e0] Jump target:block2
block5:
[ f8] IteratorClose iterator_object:reg6, iterator_next:reg7, iterator_done:reg8, completion_value:reg10
[ 110] Throw src:reg10
[ e8] IteratorClose iterator_object:reg6, iterator_next:reg7, iterator_done:reg8, completion_value:reg10
[ 100] Throw src:reg10
block6:
[ 118] IteratorClose iterator_object:reg6, iterator_next:reg7, iterator_done:reg8, completion_value:Undefined
[ 130] JumpStrictlyEquals lhs:reg9, rhs:Int32(2), true_target:block7, false_target:block8
[ 108] IteratorClose iterator_object:reg6, iterator_next:reg7, iterator_done:reg8, completion_value:Undefined
[ 120] JumpStrictlyEquals lhs:reg9, rhs:Int32(2), true_target:block7, false_target:block8
block7:
[ 148] Return value:reg10
[ 138] Return value:reg10
block8:
[ 150] Throw src:reg10
[ 140] Throw src:reg10
Exception handlers:
[ c8 .. f8] => handler block3
[ c8 .. e8] => handler block3

View file

@ -15,7 +15,7 @@ block0:
[ c0] End value:reg5
forOfBreak$0dacb993 for-of-iterator-close.js:2:18
forOfBreak$7367477b for-of-iterator-close.js:2:18
Registers: 13
Blocks: 13
Locals: x~0
@ -48,38 +48,37 @@ block4:
block5:
[ b8] Mov dst:x~0, src:reg10
[ c8] ThrowIfTDZ src:x~0
[ d0] JumpStrictlyEquals lhs:x~0, rhs:Int32(2), true_target:block6, false_target:block7
[ c8] JumpStrictlyEquals lhs:x~0, rhs:Int32(2), true_target:block6, false_target:block7
block6:
[ e8] Mov dst:reg5, src:Int32(3)
[ f8] Jump target:block4
[ e0] Mov dst:reg5, src:Int32(3)
[ f0] Jump target:block4
block7:
[ 100] Jump target:block2
[ f8] Jump target:block2
block8:
[ 108] IteratorClose iterator_object:reg6, iterator_next:reg7, iterator_done:reg8, completion_value:reg9
[ 120] Throw src:reg9
[ 100] IteratorClose iterator_object:reg6, iterator_next:reg7, iterator_done:reg8, completion_value:reg9
[ 118] Throw src:reg9
block9:
[ 128] IteratorClose iterator_object:reg6, iterator_next:reg7, iterator_done:reg8, completion_value:Undefined
[ 140] JumpStrictlyEquals lhs:reg5, rhs:Int32(3), true_target:block1, false_target:block10
[ 120] IteratorClose iterator_object:reg6, iterator_next:reg7, iterator_done:reg8, completion_value:Undefined
[ 138] JumpStrictlyEquals lhs:reg5, rhs:Int32(3), true_target:block1, false_target:block10
block10:
[ 158] JumpStrictlyEquals lhs:reg5, rhs:Int32(2), true_target:block11, false_target:block12
[ 150] JumpStrictlyEquals lhs:reg5, rhs:Int32(2), true_target:block11, false_target:block12
block11:
[ 170] Return value:reg9
[ 168] Return value:reg9
block12:
[ 178] Throw src:reg9
[ 170] Throw src:reg9
Exception handlers:
[ b8 .. 108] => handler block3
[ b8 .. 100] => handler block3
forOfReturn$3e676e69 for-of-iterator-close.js:9:18
forOfReturn$3ba37d59 for-of-iterator-close.js:9:18
Registers: 12
Blocks: 10
Locals: x~0
@ -110,30 +109,28 @@ block4:
[ a0] JumpStrictlyEquals lhs:reg5, rhs:Int32(1), true_target:block6, false_target:block7
block5:
[ b8] Mov dst:x~0, src:reg10
[ c8] ThrowIfTDZ src:x~0
[ d0] Mov2 dst1:reg9, src1:x~0, dst2:reg5, src2:Int32(2)
[ e8] Jump target:block4
[ b8] Mov3 dst1:x~0, src1:reg10, dst2:reg9, src2:x~0, dst3:reg5, src3:Int32(2)
[ d8] Jump target:block4
block6:
[ f0] IteratorClose iterator_object:reg6, iterator_next:reg7, iterator_done:reg8, completion_value:reg9
[ 108] Throw src:reg9
[ e0] IteratorClose iterator_object:reg6, iterator_next:reg7, iterator_done:reg8, completion_value:reg9
[ f8] Throw src:reg9
block7:
[ 110] IteratorClose iterator_object:reg6, iterator_next:reg7, iterator_done:reg8, completion_value:Undefined
[ 128] JumpStrictlyEquals lhs:reg5, rhs:Int32(2), true_target:block8, false_target:block9
[ 100] IteratorClose iterator_object:reg6, iterator_next:reg7, iterator_done:reg8, completion_value:Undefined
[ 118] JumpStrictlyEquals lhs:reg5, rhs:Int32(2), true_target:block8, false_target:block9
block8:
[ 140] Return value:reg9
[ 130] Return value:reg9
block9:
[ 148] Throw src:reg9
[ 138] Throw src:reg9
Exception handlers:
[ b8 .. f0] => handler block3
[ b8 .. e0] => handler block3
forAwaitOfBreak$16089337 for-of-iterator-close.js:16:24
forAwaitOfBreak$976a8bbf for-of-iterator-close.js:16:24
Registers: 18
Blocks: 30
Locals: x~0
@ -186,77 +183,76 @@ block9:
block10:
[ 150] Mov dst:x~0, src:reg10
[ 160] ThrowIfTDZ src:x~0
[ 168] JumpStrictlyEquals lhs:x~0, rhs:Int32(2), true_target:block11, false_target:block12
[ 160] JumpStrictlyEquals lhs:x~0, rhs:Int32(2), true_target:block11, false_target:block12
block11:
[ 180] Mov dst:reg8, src:Int32(3)
[ 190] Jump target:block5
[ 178] Mov dst:reg8, src:Int32(3)
[ 188] Jump target:block5
block12:
[ 198] Jump target:block3
[ 190] Jump target:block3
block13:
[ 1a0] Jump target:block25
[ 198] Jump target:block25
block14:
[ 1a8] GetMethod dst:reg12, object:reg5, `return`
[ 1b8] JumpUndefined condition:reg12, true_target:block15, false_target:block16
[ 1a0] GetMethod dst:reg12, object:reg5, `return`
[ 1b0] JumpUndefined condition:reg12, true_target:block15, false_target:block16
block15:
[ 1c8] JumpStrictlyEquals lhs:reg8, rhs:Int32(3), true_target:block2, false_target:block20
[ 1c0] JumpStrictlyEquals lhs:reg8, rhs:Int32(3), true_target:block2, false_target:block20
block16:
[ 1e0] Call dst:reg13, callee:reg12, this_value:reg5
[ 200] Await continuation_label:block17, argument:reg13
[ 1d8] Call dst:reg13, callee:reg12, this_value:reg5
[ 1f8] Await continuation_label:block17, argument:reg13
block17:
[ 210] Mov dst:reg14, src:reg0
[ 220] GetCompletionFields type_dst:reg15, value_dst:reg16, completion:reg14
[ 230] JumpStrictlyEquals lhs:reg15, rhs:Int32(1), true_target:block18, false_target:block19
[ 208] Mov dst:reg14, src:reg0
[ 218] GetCompletionFields type_dst:reg15, value_dst:reg16, completion:reg14
[ 228] JumpStrictlyEquals lhs:reg15, rhs:Int32(1), true_target:block18, false_target:block19
block18:
[ 248] ThrowIfNotObject src:reg16
[ 250] Jump target:block15
[ 240] ThrowIfNotObject src:reg16
[ 248] Jump target:block15
block19:
[ 258] Throw src:reg16
[ 250] Throw src:reg16
block20:
[ 260] JumpStrictlyEquals lhs:reg8, rhs:Int32(2), true_target:block21, false_target:block22
[ 258] JumpStrictlyEquals lhs:reg8, rhs:Int32(2), true_target:block21, false_target:block22
block21:
[ 278] Return value:reg9
[ 270] Return value:reg9
block22:
[ 280] Throw src:reg9
[ 278] Throw src:reg9
block23:
[ 288] Throw src:reg9
[ 280] Throw src:reg9
block24:
[ 290] Catch dst:reg12
[ 298] Jump target:block23
[ 288] Catch dst:reg12
[ 290] Jump target:block23
block25:
[ 2a0] GetMethod dst:reg12, object:reg5, `return`
[ 2b0] JumpUndefined condition:reg12, true_target:block23, false_target:block26
[ 298] GetMethod dst:reg12, object:reg5, `return`
[ 2a8] JumpUndefined condition:reg12, true_target:block23, false_target:block26
block26:
[ 2c0] Call dst:reg13, callee:reg12, this_value:reg5
[ 2e0] Await continuation_label:block27, argument:reg13
[ 2b8] Call dst:reg13, callee:reg12, this_value:reg5
[ 2d8] Await continuation_label:block27, argument:reg13
block27:
[ 2f0] Mov dst:reg14, src:reg0
[ 300] GetCompletionFields type_dst:reg15, value_dst:reg16, completion:reg14
[ 310] JumpStrictlyEquals lhs:reg15, rhs:Int32(1), true_target:block28, false_target:block29
[ 2e8] Mov dst:reg14, src:reg0
[ 2f8] GetCompletionFields type_dst:reg15, value_dst:reg16, completion:reg14
[ 308] JumpStrictlyEquals lhs:reg15, rhs:Int32(1), true_target:block28, false_target:block29
block28:
[ 328] Jump target:block23
[ 320] Jump target:block23
block29:
[ 330] Throw src:reg16
[ 328] Throw src:reg16
Exception handlers:
[ 150 .. 1a0] => handler block4
[ 2a0 .. 338] => handler block24
[ 150 .. 198] => handler block4
[ 298 .. 330] => handler block24

View file

@ -84,7 +84,7 @@ block0:
[ 18] Return value:outer~1
forInTeardown$7c859710 lexical-env-teardown.js:28:5
forInTeardown$fde78f98 lexical-env-teardown.js:28:5
Registers: 8
Blocks: 6
Locals: k~0, outer~1
@ -117,11 +117,10 @@ block4:
block5:
[ b8] Mov dst:k~0, src:reg5
[ c8] ThrowIfTDZ src:k~0
[ d0] Jump target:block2
[ c8] Jump target:block2
forOfTeardown$94a2ae97 lexical-env-teardown.js:38:5
forOfTeardown$269c4d7f lexical-env-teardown.js:38:5
Registers: 12
Blocks: 9
Locals: v~0, outer~1
@ -153,25 +152,24 @@ block3:
block4:
[ c0] Mov dst:v~0, src:reg10
[ d0] ThrowIfTDZ src:v~0
[ d8] Jump target:block2
[ d0] Jump target:block2
block5:
[ e0] IteratorClose iterator_object:reg6, iterator_next:reg7, iterator_done:reg8, completion_value:reg9
[ f8] Throw src:reg9
[ d8] IteratorClose iterator_object:reg6, iterator_next:reg7, iterator_done:reg8, completion_value:reg9
[ f0] Throw src:reg9
block6:
[ 100] IteratorClose iterator_object:reg6, iterator_next:reg7, iterator_done:reg8, completion_value:Undefined
[ 118] JumpStrictlyEquals lhs:reg5, rhs:Int32(2), true_target:block7, false_target:block8
[ f8] IteratorClose iterator_object:reg6, iterator_next:reg7, iterator_done:reg8, completion_value:Undefined
[ 110] JumpStrictlyEquals lhs:reg5, rhs:Int32(2), true_target:block7, false_target:block8
block7:
[ 130] Return value:reg9
[ 128] Return value:reg9
block8:
[ 138] Throw src:reg9
[ 130] Throw src:reg9
Exception handlers:
[ c0 .. e0] => handler block3
[ c0 .. d8] => handler block3
catchTeardown$6b833e8d lexical-env-teardown.js:48:5

View file

@ -80,3 +80,19 @@ test("const creation in inner scope", () => {
} while (false);
expect(constantValue).toBe(1);
});
test("reassignment to destructured const in function", () => {
expect(() => {
(() => {
const { value } = { value: 1 };
value = 2;
})();
}).toThrowWithMessage(TypeError, "Invalid assignment to const variable");
expect(() => {
(() => {
const [value] = [1];
[value] = [2];
})();
}).toThrowWithMessage(TypeError, "Invalid assignment to const variable");
});

View file

@ -217,6 +217,18 @@ describe("special left hand sides", () => {
expect(vals).toEqual(["0", "1"]);
});
test("Cannot change destructured constant declaration in body", () => {
const vals = [];
for (const { 0: value } in "ab") {
expect(() => {
value = "x";
}).toThrowWithMessage(TypeError, "Invalid assignment to const variable");
vals.push(value);
}
expect(vals).toEqual(["0", "1"]);
});
});
test("remove properties while iterating", () => {

View file

@ -150,4 +150,16 @@ describe("special left hand sides", () => {
expect(vals).toEqual([1, 2]);
});
test("Cannot change destructured constant declaration in body", () => {
const vals = [];
for (const [value] of [[1], [2]]) {
expect(() => {
value = 3;
}).toThrowWithMessage(TypeError, "Invalid assignment to const variable");
vals.push(value);
}
expect(vals).toEqual([1, 2]);
});
});

View file

@ -269,6 +269,19 @@ test("string object: string property indexing", () => {
expect(lastSetThisValue).toBeNull();
lastSetThisValue = null;
});
test("primitive string object destructuring sees virtual indexed properties", () => {
const { 0: first, 1: second, length } = "ab";
expect(first).toBe("a");
expect(second).toBe("b");
expect(length).toBe(2);
const values = [];
for (const { 0: c } of "ab") {
values.push(c);
}
expect(values).toEqual(["a", "b"]);
});
test("string object: string property name access", () => {
expect(new String("").length).toBe(0);
expect(new String("foo").length).toBe(3);