LibJS: Copy LHS of compound assignment to preserve evaluation order
This error was found by asking an LLM to generate additional, related test cases for the bug affecting https://volkswagen.de fixed in an earlier commit.
This commit is contained in:
parent
ced435987c
commit
4cd1fc8019
6 changed files with 76 additions and 48 deletions
|
|
@ -775,6 +775,7 @@ Optional<ScopedOperand> AssignmentExpression::generate_bytecode(Bytecode::Genera
|
|||
return {};
|
||||
|
||||
auto lhs = reference_operands.loaded_value.value();
|
||||
lhs = generator.copy_if_needed_to_preserve_evaluation_order(lhs);
|
||||
|
||||
Bytecode::BasicBlock* rhs_block_ptr { nullptr };
|
||||
Bytecode::BasicBlock* lhs_block_ptr { nullptr };
|
||||
|
|
|
|||
|
|
@ -3575,6 +3575,7 @@ fn generate_assignment_expression(
|
|||
|
||||
// Load LHS value first (needed for both compound and logical assignments).
|
||||
let lhs_val = generate_identifier(ident, generator, None)?;
|
||||
let lhs_val = generator.copy_if_needed_to_preserve_evaluation_order(&lhs_val);
|
||||
|
||||
let is_logical = matches!(
|
||||
op,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ JS bytecode executable "f"
|
|||
[ 10] GetById dst:reg5, base:arg0, property:patchFlag
|
||||
[ 30] Mov dst:a~0, src:reg5
|
||||
[ 40] ThrowIfTDZ src:a~0
|
||||
[ 48] GetById dst:reg5, base:arg1, property:patchFlag, base_identifier:e
|
||||
[ 68] BitwiseAnd dst:reg6, lhs:Int32(16), rhs:reg5
|
||||
[ 78] BitwiseOr dst:a~0, lhs:a~0, rhs:reg6
|
||||
[ 88] End value:Undefined
|
||||
[ 48] Mov dst:reg5, src:a~0
|
||||
[ 58] GetById dst:reg6, base:arg1, property:patchFlag, base_identifier:e
|
||||
[ 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
|
||||
|
|
|
|||
|
|
@ -10,24 +10,32 @@ JS bytecode executable ""
|
|||
JS bytecode executable "sequentialTryCatch"
|
||||
[ 0] 0: GetLexicalEnvironment dst:reg4
|
||||
[ 8] Mov dst:result~2, src:String("")
|
||||
[ 18] Jump target:@58
|
||||
[ 18] Jump target:@78
|
||||
[ 20] 1: Catch dst:reg5
|
||||
[ 28] SetLexicalEnvironment environment:reg4
|
||||
[ 30] Mov dst:e~0, src:reg5
|
||||
[ 40] Add dst:result~2, lhs:result~2, rhs:String("b")
|
||||
[ 50] 2: Jump target:@a8
|
||||
[ 58] 3: Add dst:result~2, lhs:result~2, rhs:String("a")
|
||||
[ 68] Throw src:Int32(1)
|
||||
[ 70] 4: Catch dst:reg5
|
||||
[ 78] SetLexicalEnvironment environment:reg4
|
||||
[ 80] Mov dst:e~1, src:reg5
|
||||
[ 90] Add dst:result~2, lhs:result~2, rhs:String("d")
|
||||
[ a0] 5: Return value:result~2
|
||||
[ a8] 6: Add dst:result~2, lhs:result~2, rhs:String("c")
|
||||
[ b8] Throw src:Int32(2)
|
||||
[ 40] Mov dst:reg6, src:result~2
|
||||
[ 50] Add dst:reg7, lhs:reg6, rhs:String("b")
|
||||
[ 60] Mov dst:result~2, src:reg7
|
||||
[ 70] 2: Jump target:@108
|
||||
[ 78] 3: Mov dst:reg5, src:result~2
|
||||
[ 88] Add dst:reg6, lhs:reg5, rhs:String("a")
|
||||
[ 98] Mov dst:result~2, src:reg6
|
||||
[ a8] Throw src:Int32(1)
|
||||
[ b0] 4: Catch dst:reg5
|
||||
[ b8] SetLexicalEnvironment environment:reg4
|
||||
[ c0] Mov dst:e~1, src:reg5
|
||||
[ d0] Mov dst:reg6, src:result~2
|
||||
[ e0] Add dst:reg7, lhs:reg6, rhs:String("d")
|
||||
[ f0] Mov dst:result~2, src:reg7
|
||||
[ 100] 5: Return value:result~2
|
||||
[ 108] 6: Mov dst:reg5, src:result~2
|
||||
[ 118] Add dst:reg6, lhs:reg5, rhs:String("c")
|
||||
[ 128] Mov dst:result~2, src:reg6
|
||||
[ 138] Throw src:Int32(2)
|
||||
|
||||
Exception handlers:
|
||||
from 58 to 70 handler 20
|
||||
from a8 to c0 handler 70
|
||||
from 78 to b0 handler 20
|
||||
from 108 to 140 handler b0
|
||||
|
||||
"abcd"
|
||||
|
|
|
|||
|
|
@ -17,58 +17,66 @@ JS bytecode executable "continueThroughFinally"
|
|||
[ 8] Mov dst:result~1, src:Int32(0)
|
||||
[ 18] Mov dst:i~0, src:Int32(0)
|
||||
[ 28] Jump target:@48
|
||||
[ 30] 1: Jump target:@b0
|
||||
[ 30] 1: Jump target:@d0
|
||||
[ 38] 2: PostfixIncrement dst:reg5, src:i~0
|
||||
[ 48] 3: JumpLessThan lhs:i~0, rhs:Int32(3), true_target:@30, false_target:@60
|
||||
[ 60] 4: Return value:result~1
|
||||
[ 68] 5: Catch dst:reg6
|
||||
[ 70] SetLexicalEnvironment environment:reg4
|
||||
[ 78] Mov dst:reg5, src:Int32(1)
|
||||
[ 88] 6: Add dst:result~1, lhs:result~1, rhs:Int32(10)
|
||||
[ 98] JumpStrictlyEquals lhs:reg5, rhs:Int32(0), true_target:@108, false_target:@110
|
||||
[ b0] 7: JumpStrictlyEquals lhs:i~0, rhs:Int32(1), true_target:@c8, false_target:@e0
|
||||
[ c8] 8: Mov dst:reg5, src:Int32(3)
|
||||
[ d8] Jump target:@88
|
||||
[ e0] 9: Add dst:result~1, lhs:result~1, rhs:i~0
|
||||
[ f0] Mov dst:reg5, src:Int32(0)
|
||||
[ 100] Jump target:@88
|
||||
[ 108] 10: Jump target:@38
|
||||
[ 110] 11: JumpStrictlyEquals lhs:reg5, rhs:Int32(3), true_target:@38, false_target:@128
|
||||
[ 128] 12: JumpStrictlyEquals lhs:reg5, rhs:Int32(2), true_target:@140, false_target:@148
|
||||
[ 140] 13: Return value:reg6
|
||||
[ 148] 14: Throw src:reg6
|
||||
[ 88] 6: Mov dst:reg7, src:result~1
|
||||
[ 98] Add dst:reg8, lhs:reg7, rhs:Int32(10)
|
||||
[ a8] Mov dst:result~1, src:reg8
|
||||
[ b8] JumpStrictlyEquals lhs:reg5, rhs:Int32(0), true_target:@148, false_target:@150
|
||||
[ d0] 7: JumpStrictlyEquals lhs:i~0, rhs:Int32(1), true_target:@e8, false_target:@100
|
||||
[ e8] 8: Mov dst:reg5, src:Int32(3)
|
||||
[ f8] Jump target:@88
|
||||
[ 100] 9: Mov dst:reg7, src:result~1
|
||||
[ 110] Add dst:reg8, lhs:reg7, rhs:i~0
|
||||
[ 120] Mov dst:result~1, src:reg8
|
||||
[ 130] Mov dst:reg5, src:Int32(0)
|
||||
[ 140] Jump target:@88
|
||||
[ 148] 10: Jump target:@38
|
||||
[ 150] 11: JumpStrictlyEquals lhs:reg5, rhs:Int32(3), true_target:@38, false_target:@168
|
||||
[ 168] 12: JumpStrictlyEquals lhs:reg5, rhs:Int32(2), true_target:@180, false_target:@188
|
||||
[ 180] 13: Return value:reg6
|
||||
[ 188] 14: Throw src:reg6
|
||||
|
||||
Exception handlers:
|
||||
from b0 to 108 handler 68
|
||||
from d0 to 148 handler 68
|
||||
|
||||
JS bytecode executable "breakThroughFinally"
|
||||
[ 0] 0: GetLexicalEnvironment dst:reg4
|
||||
[ 8] Mov dst:result~1, src:Int32(0)
|
||||
[ 18] Mov dst:i~0, src:Int32(0)
|
||||
[ 28] Jump target:@48
|
||||
[ 30] 1: Jump target:@b0
|
||||
[ 30] 1: Jump target:@d0
|
||||
[ 38] 2: PostfixIncrement dst:reg5, src:i~0
|
||||
[ 48] 3: JumpLessThan lhs:i~0, rhs:Int32(10), true_target:@30, false_target:@60
|
||||
[ 60] 4: Return value:result~1
|
||||
[ 68] 5: Catch dst:reg6
|
||||
[ 70] SetLexicalEnvironment environment:reg4
|
||||
[ 78] Mov dst:reg5, src:Int32(1)
|
||||
[ 88] 6: Add dst:result~1, lhs:result~1, rhs:Int32(100)
|
||||
[ 98] JumpStrictlyEquals lhs:reg5, rhs:Int32(0), true_target:@108, false_target:@110
|
||||
[ b0] 7: JumpStrictlyEquals lhs:i~0, rhs:Int32(2), true_target:@c8, false_target:@e0
|
||||
[ c8] 8: Mov dst:reg5, src:Int32(3)
|
||||
[ d8] Jump target:@88
|
||||
[ e0] 9: Add dst:result~1, lhs:result~1, rhs:i~0
|
||||
[ f0] Mov dst:reg5, src:Int32(0)
|
||||
[ 100] Jump target:@88
|
||||
[ 108] 10: Jump target:@38
|
||||
[ 110] 11: JumpStrictlyEquals lhs:reg5, rhs:Int32(3), true_target:@60, false_target:@128
|
||||
[ 128] 12: JumpStrictlyEquals lhs:reg5, rhs:Int32(2), true_target:@140, false_target:@148
|
||||
[ 140] 13: Return value:reg6
|
||||
[ 148] 14: Throw src:reg6
|
||||
[ 88] 6: Mov dst:reg7, src:result~1
|
||||
[ 98] Add dst:reg8, lhs:reg7, rhs:Int32(100)
|
||||
[ a8] Mov dst:result~1, src:reg8
|
||||
[ b8] JumpStrictlyEquals lhs:reg5, rhs:Int32(0), true_target:@148, false_target:@150
|
||||
[ d0] 7: JumpStrictlyEquals lhs:i~0, rhs:Int32(2), true_target:@e8, false_target:@100
|
||||
[ e8] 8: Mov dst:reg5, src:Int32(3)
|
||||
[ f8] Jump target:@88
|
||||
[ 100] 9: Mov dst:reg7, src:result~1
|
||||
[ 110] Add dst:reg8, lhs:reg7, rhs:i~0
|
||||
[ 120] Mov dst:result~1, src:reg8
|
||||
[ 130] Mov dst:reg5, src:Int32(0)
|
||||
[ 140] Jump target:@88
|
||||
[ 148] 10: Jump target:@38
|
||||
[ 150] 11: JumpStrictlyEquals lhs:reg5, rhs:Int32(3), true_target:@60, false_target:@168
|
||||
[ 168] 12: JumpStrictlyEquals lhs:reg5, rhs:Int32(2), true_target:@180, false_target:@188
|
||||
[ 180] 13: Return value:reg6
|
||||
[ 188] 14: Throw src:reg6
|
||||
|
||||
Exception handlers:
|
||||
from b0 to 108 handler 68
|
||||
from d0 to 148 handler 68
|
||||
|
||||
32
|
||||
301
|
||||
|
|
|
|||
|
|
@ -79,3 +79,11 @@ test("object literal key is reassigned during value evaluation", () => {
|
|||
let result = foo("old", "new");
|
||||
expect(result).toBe("old");
|
||||
});
|
||||
|
||||
test("evaluation order for compound assignment", () => {
|
||||
function foo(value) {
|
||||
return (value += value = 5);
|
||||
}
|
||||
let result = foo(2);
|
||||
expect(result).toBe(7);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue