LibJS: Copy object of member expression to preserve evaluation order

Noticed this pattern when reading some minified JS while debugging a
seemingly unrelated problem and immediately got suspicious because of my
earlier, similar fixes.
This commit is contained in:
InvalidUsernameException 2026-03-21 13:02:49 +01:00 committed by Shannon Booth
parent a94d8a1f78
commit 61e6dbe4e7
11 changed files with 136 additions and 115 deletions

View file

@ -767,6 +767,7 @@ fn generate_member_expression(
return Some(dst);
}
let obj = generate_expression(object, generator, None)?;
let obj = generator.copy_if_needed_to_preserve_evaluation_order(&obj);
let base_id = intern_base_identifier(generator, object);
if computed {
let property = generate_expression(property, generator, None)?;

View file

@ -50,11 +50,12 @@ chained_computed_call$df45ff7f chained-member-call.js:5:5
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] GetByValue dst:reg6, base:arg0, property:arg1 (a[arg1])
[ 20] GetByValue dst:reg7, base:reg6, property:arg2 (a[j][arg2])
[ 38] GetById dst:reg6, base:reg7, `foo` (a[j][k].foo)
[ 58] Call dst:reg5, callee:reg6, this_value:reg7, a[j][k].foo
[ 78] Return value:reg5
[ 8] Mov dst:reg6, src:arg0
[ 18] GetByValue dst:reg7, base:reg6, property:arg1 (a[arg1])
[ 30] GetByValue dst:reg6, base:reg7, property:arg2 (a[j][arg2])
[ 48] GetById dst:reg7, base:reg6, `foo` (a[j][k].foo)
[ 68] Call dst:reg5, callee:reg7, this_value:reg6, a[j][k].foo
[ 88] Return value:reg5
chained_dot_call$9501e458 chained-member-call.js:9:5
@ -63,8 +64,9 @@ chained_dot_call$9501e458 chained-member-call.js:9:5
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] GetById dst:reg6, base:arg0, `b` (a.b)
[ 28] GetById dst:reg7, base:reg6, `c` (a.b.c)
[ 48] GetById dst:reg6, base:reg7, `bar` (a.b.c.bar)
[ 68] Call dst:reg5, callee:reg6, this_value:reg7, a.b.c.bar
[ 88] Return value:reg5
[ 8] Mov dst:reg6, src:arg0
[ 18] GetById dst:reg7, base:reg6, `b` (a.b)
[ 38] GetById dst:reg6, base:reg7, `c` (a.b.c)
[ 58] GetById dst:reg7, base:reg6, `bar` (a.b.c.bar)
[ 78] Call dst:reg5, callee:reg7, this_value:reg6, a.b.c.bar
[ 98] Return value:reg5

View file

@ -24,13 +24,13 @@ subVector$1525553e computed-compound-assign-register-reuse.js:5:13
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] GetByValue dst:reg5, base:arg0, property:Int32(0) (self[Int32(0)])
[ 20] Mov dst:reg6, src:Int32(0)
[ 30] GetByValue dst:reg7, base:arg1, property:Int32(0) (v[Int32(0)])
[ 48] Sub dst:reg8, lhs:reg5, rhs:reg7
[ 58] PutByValue base:arg0, property:reg6, src:reg8, kind:Normal
[ 70] GetByValue dst:reg5, base:arg0, property:Int32(1) (self[Int32(1)])
[ 88] Mov dst:reg6, src:Int32(1)
[ 98] GetByValue dst:reg7, base:arg1, property:Int32(1) (v[Int32(1)])
[ b0] Sub dst:reg8, lhs:reg5, rhs:reg7
[ c0] PutByValue base:arg0, property:reg6, src:reg8, kind:Normal
[ d8] End value:Undefined
[ 20] Mov2 dst1:reg6, src1:Int32(0), dst2:reg7, src2:arg1
[ 38] GetByValue dst:reg8, base:reg7, property:Int32(0) (v[Int32(0)])
[ 50] Sub dst:reg7, lhs:reg5, rhs:reg8
[ 60] PutByValue base:arg0, property:reg6, src:reg7, kind:Normal
[ 78] GetByValue dst:reg5, base:arg0, property:Int32(1) (self[Int32(1)])
[ 90] Mov2 dst1:reg6, src1:Int32(1), dst2:reg7, src2:arg1
[ a8] GetByValue dst:reg8, base:reg7, property:Int32(1) (v[Int32(1)])
[ c0] Sub dst:reg7, lhs:reg5, rhs:reg8
[ d0] PutByValue base:arg0, property:reg6, src:reg7, kind:Normal
[ e8] End value:Undefined

View file

@ -17,22 +17,24 @@ block0:
computed_read$a6f67e31 computed-member-access.js:5:5
Registers: 6
Registers: 7
Blocks: 1
Constants:
[0] = Int32(0)
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] GetByValue dst:reg5, base:arg0, property:Int32(0) (a[Int32(0)])
[ 20] Return value:reg5
[ 8] Mov dst:reg5, src:arg0
[ 18] GetByValue dst:reg6, base:reg5, property:Int32(0) (a[Int32(0)])
[ 30] Return value:reg6
computed_read_expression$adb2a228 computed-member-access.js:9:5
Registers: 6
Registers: 7
Blocks: 1
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] GetByValue dst:reg5, base:arg0, property:arg1 (a[arg1])
[ 20] Return value:reg5
[ 8] Mov dst:reg5, src:arg0
[ 18] GetByValue dst:reg6, base:reg5, property:arg1 (a[arg1])
[ 30] Return value:reg6

View file

@ -22,15 +22,16 @@ block0:
get_string_prop$fa843635 computed-string-to-ById.js:5:5
Registers: 6
Registers: 7
Blocks: 1
Constants:
[0] = String("hello")
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] GetById dst:reg5, base:arg0, `hello` (o.hello)
[ 28] Return value:reg5
[ 8] Mov dst:reg5, src:arg0
[ 18] GetById dst:reg6, base:reg5, `hello` (o.hello)
[ 38] Return value:reg6
set_string_prop$5b793bae computed-string-to-ById.js:8:16
@ -49,24 +50,26 @@ block0:
get_index_prop$f6a04ecf computed-string-to-ById.js:11:5
Registers: 6
Registers: 7
Blocks: 1
Constants:
[0] = String("0")
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] GetByValue dst:reg5, base:arg0, property:String("0") (o[String("0")])
[ 20] Return value:reg5
[ 8] Mov dst:reg5, src:arg0
[ 18] GetByValue dst:reg6, base:reg5, property:String("0") (o[String("0")])
[ 30] Return value:reg6
get_length_prop$2983fb9b computed-string-to-ById.js:14:5
Registers: 6
Registers: 7
Blocks: 1
Constants:
[0] = String("length")
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] GetLength dst:reg5, base:arg0 (o.length)
[ 20] Return value:reg5
[ 8] Mov dst:reg5, src:arg0
[ 18] GetLength dst:reg6, base:reg5 (o.length)
[ 30] Return value:reg6

View file

@ -27,9 +27,9 @@ block0:
[ 10] GetById dst:reg5, base:arg0, `patchFlag`
[ 30] Mov dst:a~0, src:reg5
[ 40] ThrowIfTDZ src:a~0
[ 48] Mov dst:reg5, src:a~0
[ 58] GetById dst:reg6, base:arg1, `patchFlag` (e.patchFlag)
[ 78] BitwiseAnd dst:reg7, lhs:Int32(16), rhs:reg6
[ 88] BitwiseOr dst:reg6, lhs:reg5, rhs:reg7
[ 98] Mov dst:a~0, src:reg6
[ a8] End value:Undefined
[ 48] Mov2 dst1:reg5, src1:a~0, dst2:reg6, src2:arg1
[ 60] GetById dst:reg7, base:reg6, `patchFlag` (e.patchFlag)
[ 80] BitwiseAnd dst:reg6, lhs:Int32(16), rhs:reg7
[ 90] BitwiseOr dst:reg7, lhs:reg5, rhs:reg6
[ a0] Mov dst:a~0, src:reg7
[ b0] End value:Undefined

View file

@ -34,76 +34,77 @@ block2:
[ 38] JumpIf condition:reg11, true_target:block1, false_target:block8
block3:
[ 48] GetById dst:reg6, base:arg0, `x` (o.x)
[ 68] Mov dst:reg5, src:reg6
[ 78] Jump target:block5
[ 48] Mov dst:reg6, src:arg0
[ 58] GetById dst:reg7, base:reg6, `x` (o.x)
[ 78] Mov dst:reg5, src:reg7
[ 88] Jump target:block5
block4:
[ 80] Mov dst:reg5, src:arg1
[ 90] Mov dst:reg5, src:arg1
block5:
[ 90] GetGlobal dst:reg7, `Object`
[ a8] GetById dst:reg8, base:reg7, `entries` (Object.entries)
[ c8] NewObject dst:reg9
[ d8] Call dst:reg6, callee:reg8, this_value:reg7, Object.entries, arguments:[reg9]
[ 100] GetIterator dst_iterator_object:reg5, dst_iterator_next:reg7, dst_iterator_done:reg8, iterable:reg6
[ 118] Jump target:block2
[ a0] GetGlobal dst:reg7, `Object`
[ b8] GetById dst:reg8, base:reg7, `entries` (Object.entries)
[ d8] NewObject dst:reg9
[ e8] Call dst:reg6, callee:reg8, this_value:reg7, Object.entries, arguments:[reg9]
[ 110] GetIterator dst_iterator_object:reg5, dst_iterator_next:reg7, dst_iterator_done:reg8, iterable:reg6
[ 128] Jump target:block2
block6:
[ 120] Catch dst:reg9
[ 128] SetLexicalEnvironment environment:reg4
[ 130] Mov dst:reg6, src:Int32(1)
[ 130] Catch dst:reg9
[ 138] SetLexicalEnvironment environment:reg4
[ 140] Mov dst:reg6, src:Int32(1)
block7:
[ 140] JumpStrictlyEquals lhs:reg6, rhs:Int32(1), true_target:block16, false_target:block17
[ 150] JumpStrictlyEquals lhs:reg6, rhs:Int32(1), true_target:block16, false_target:block17
block8:
[ 158] Mov dst:reg12, src:Bool(false)
[ 168] GetIterator dst_iterator_object:reg13, dst_iterator_next:reg14, dst_iterator_done:reg15, iterable:reg10
[ 180] IteratorNextUnpack dst_value:reg16, dst_done:reg12, iterator_object:reg13, iterator_next:reg14, iterator_done:reg15
[ 198] JumpFalse condition:reg12, target:block10
[ 168] Mov dst:reg12, src:Bool(false)
[ 178] GetIterator dst_iterator_object:reg13, dst_iterator_next:reg14, dst_iterator_done:reg15, iterable:reg10
[ 190] IteratorNextUnpack dst_value:reg16, dst_done:reg12, iterator_object:reg13, iterator_next:reg14, iterator_done:reg15
[ 1a8] JumpFalse condition:reg12, target:block10
block9:
[ 1a8] Mov dst:reg16, src:Undefined
[ 1b8] Jump target:block10
[ 1b8] Mov dst:reg16, src:Undefined
[ 1c8] Jump target:block10
block10:
[ 1c0] Mov dst:r~1, src:reg16
[ 1d0] JumpFalse condition:reg12, target:block12
[ 1d0] Mov dst:r~1, src:reg16
[ 1e0] JumpFalse condition:reg12, target:block12
block11:
[ 1e0] Mov dst:reg16, src:Undefined
[ 1f0] Jump target:block13
[ 1f0] Mov dst:reg16, src:Undefined
[ 200] Jump target:block13
block12:
[ 1f8] IteratorNextUnpack dst_value:reg16, dst_done:reg12, iterator_object:reg13, iterator_next:reg14, iterator_done:reg15
[ 210] JumpTrue condition:reg12, target:block11
[ 208] IteratorNextUnpack dst_value:reg16, dst_done:reg12, iterator_object:reg13, iterator_next:reg14, iterator_done:reg15
[ 220] JumpTrue condition:reg12, target:block11
block13:
[ 220] Mov dst:n~0, src:reg16
[ 230] JumpFalse condition:reg12, target:block15
[ 230] Mov dst:n~0, src:reg16
[ 240] JumpFalse condition:reg12, target:block15
block14:
[ 240] ThrowIfTDZ src:r~1
[ 248] Jump target:block2
[ 250] ThrowIfTDZ src:r~1
[ 258] Jump target:block2
block15:
[ 250] IteratorClose iterator_object:reg13, iterator_next:reg14, iterator_done:reg15, completion_value:Undefined
[ 268] Jump target:block14
[ 260] IteratorClose iterator_object:reg13, iterator_next:reg14, iterator_done:reg15, completion_value:Undefined
[ 278] Jump target:block14
block16:
[ 270] IteratorClose iterator_object:reg5, iterator_next:reg7, iterator_done:reg8, completion_value:reg9
[ 288] Throw src:reg9
[ 280] IteratorClose iterator_object:reg5, iterator_next:reg7, iterator_done:reg8, completion_value:reg9
[ 298] Throw src:reg9
block17:
[ 290] IteratorClose iterator_object:reg5, iterator_next:reg7, iterator_done:reg8, completion_value:Undefined
[ 2a8] JumpStrictlyEquals lhs:reg6, rhs:Int32(2), true_target:block18, false_target:block19
[ 2a0] IteratorClose iterator_object:reg5, iterator_next:reg7, iterator_done:reg8, completion_value:Undefined
[ 2b8] JumpStrictlyEquals lhs:reg6, rhs:Int32(2), true_target:block18, false_target:block19
block18:
[ 2c0] Return value:reg9
[ 2d0] Return value:reg9
block19:
[ 2c8] Throw src:reg9
[ 2d8] Throw src:reg9
Exception handlers:
[ 158 .. 270] => handler block6
[ 168 .. 280] => handler block6

View file

@ -32,40 +32,42 @@ block0:
[ 8] Yield continuation_label:block1, value:Undefined
block1:
[ 18] GetById dst:reg8, base:arg0, `x` (a.x)
[ 38] Yield continuation_label:block2, value:reg8
[ 18] Mov dst:reg8, src:arg0
[ 28] GetById dst:reg9, base:reg8, `x` (a.x)
[ 48] Yield continuation_label:block2, value:reg9
block2:
[ 48] Mov dst:reg5, src:reg0
[ 58] GetCompletionFields type_dst:reg6, value_dst:reg7, completion:reg5
[ 68] JumpStrictlyEquals lhs:reg6, rhs:Int32(1), true_target:block3, false_target:block4
[ 58] Mov dst:reg5, src:reg0
[ 68] GetCompletionFields type_dst:reg6, value_dst:reg7, completion:reg5
[ 78] JumpStrictlyEquals lhs:reg6, rhs:Int32(1), true_target:block3, false_target:block4
block3:
[ 80] GetById dst:reg8, base:arg1, `y` (b.y)
[ a0] Yield continuation_label:block7, value:reg8
[ 90] Mov dst:reg8, src:arg1
[ a0] GetById dst:reg9, base:reg8, `y` (b.y)
[ c0] Yield continuation_label:block7, value:reg9
block4:
[ b0] JumpStrictlyEquals lhs:reg6, rhs:Int32(5), true_target:block5, false_target:block6
[ d0] JumpStrictlyEquals lhs:reg6, rhs:Int32(5), true_target:block5, false_target:block6
block5:
[ c8] Throw src:reg7
[ e8] Throw src:reg7
block6:
[ d0] Yield value:reg7
[ f0] Yield value:reg7
block7:
[ e0] Mov dst:reg5, src:reg0
[ f0] GetCompletionFields type_dst:reg6, value_dst:reg7, completion:reg5
[ 100] JumpStrictlyEquals lhs:reg6, rhs:Int32(1), true_target:block8, false_target:block9
[ 100] Mov dst:reg5, src:reg0
[ 110] GetCompletionFields type_dst:reg6, value_dst:reg7, completion:reg5
[ 120] JumpStrictlyEquals lhs:reg6, rhs:Int32(1), true_target:block8, false_target:block9
block8:
[ 118] Yield value:Undefined
[ 138] Yield value:Undefined
block9:
[ 128] JumpStrictlyEquals lhs:reg6, rhs:Int32(5), true_target:block10, false_target:block11
[ 148] JumpStrictlyEquals lhs:reg6, rhs:Int32(5), true_target:block10, false_target:block11
block10:
[ 140] Throw src:reg7
[ 160] Throw src:reg7
block11:
[ 148] Yield value:reg7
[ 168] Yield value:reg7

View file

@ -13,7 +13,7 @@ block0:
test$c1ed08de if-else-register-lifetime.js:2:5
Registers: 12
Registers: 13
Blocks: 12
Locals: a~0, i~1, j~2
Constants:
@ -59,19 +59,20 @@ block8:
[ 120] Jump target:block2
block9:
[ 128] Mov2 dst1:reg6, src1:arg0, dst2:reg7, src2:j~2
[ 140] Add dst:reg8, lhs:i~1, rhs:j~2
[ 150] GetByValue dst:reg9, base:arg0, property:reg8 (x[reg8])
[ 168] PutByValue base:reg6, property:reg7, src:reg9, kind:Normal (x[reg7])
[ 180] Jump target:block11
[ 128] Mov3 dst1:reg6, src1:arg0, dst2:reg7, src2:j~2, dst3:reg8, src3:arg0
[ 148] Add dst:reg9, lhs:i~1, rhs:j~2
[ 158] GetByValue dst:reg10, base:reg8, property:reg9 (x[reg9])
[ 170] PutByValue base:reg6, property:reg7, src:reg10, kind:Normal (x[reg7])
[ 188] Jump target:block11
block10:
[ 188] Mov2 dst1:reg6, src1:arg0, dst2:reg7, src2:j~2
[ 1a0] GetGlobal dst:reg9, `parseInt`
[ 1b8] Sub dst:reg10, lhs:j~2, rhs:Int32(3)
[ 1c8] GetByValue dst:reg11, base:arg0, property:reg10 (x[reg10])
[ 1e0] Call dst:reg8, callee:reg9, this_value:Undefined, parseInt, arguments:[reg11, Int32(1)]
[ 208] PutByValue base:reg6, property:reg7, src:reg8, kind:Normal (x[reg7])
[ 190] Mov2 dst1:reg6, src1:arg0, dst2:reg7, src2:j~2
[ 1a8] GetGlobal dst:reg9, `parseInt`
[ 1c0] Mov dst:reg10, src:arg0
[ 1d0] Sub dst:reg11, lhs:j~2, rhs:Int32(3)
[ 1e0] GetByValue dst:reg12, base:reg10, property:reg11 (x[reg11])
[ 1f8] Call dst:reg8, callee:reg9, this_value:Undefined, parseInt, arguments:[reg12, Int32(1)]
[ 220] PutByValue base:reg6, property:reg7, src:reg8, kind:Normal (x[reg7])
block11:
[ 220] Jump target:block6
[ 238] Jump target:block6

View file

@ -17,7 +17,7 @@ block0:
assign_nested$ab9ddddc nested-member-base-identifier.js:6:8
Registers: 7
Registers: 8
Blocks: 1
Constants:
[0] = Int32(1)
@ -26,7 +26,8 @@ assign_nested$ab9ddddc nested-member-base-identifier.js:6:8
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] Sub dst:reg5, lhs:arg1, rhs:Int32(1)
[ 18] GetByValue dst:reg6, base:arg0, property:reg5 (arr[reg5])
[ 30] PutById base:reg6, `shader`, src:Int32(0), kind:Normal (arr.shader)
[ 58] End value:Undefined
[ 8] Mov dst:reg5, src:arg0
[ 18] Sub dst:reg6, lhs:arg1, rhs:Int32(1)
[ 28] GetByValue dst:reg7, base:reg5, property:reg6 (arr[reg6])
[ 40] PutById base:reg7, `shader`, src:Int32(0), kind:Normal (arr.shader)
[ 68] End value:Undefined

View file

@ -103,3 +103,11 @@ test("evaluation order for binary operators (LHS reassigns)", () => {
let result = foo(2);
expect(result).toBe(10);
});
test("evaluation order for member expression", () => {
function foo(obj, key) {
return obj[(obj = key)];
}
let result = foo({ asdf: 42 }, "asdf");
expect(result).toBe(42);
});