mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 04:32:44 +00:00 
			
		
		
		
	LibJS/Bytecode: Always evaluate LHS first in assignment expressions
This fixes an issue where expressions like `a[i] = a[++i]` could evaluate `++i` before `a[i]`.
This commit is contained in:
		
							parent
							
								
									1986693edc
								
							
						
					
					
						commit
						0f8c6dc9ad
					
				
					 3 changed files with 27 additions and 2 deletions
				
			
		|  | @ -435,7 +435,9 @@ Bytecode::CodeGenerationErrorOr<Optional<Bytecode::Operand>> AssignmentExpressio | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     if (expression.is_computed()) { |                     if (expression.is_computed()) { | ||||||
|                         computed_property = TRY(expression.property().generate_bytecode(generator)).value(); |                         auto property = TRY(expression.property().generate_bytecode(generator)).value(); | ||||||
|  |                         computed_property = Bytecode::Operand(generator.allocate_register()); | ||||||
|  |                         generator.emit<Bytecode::Op::Mov>(*computed_property, property); | ||||||
| 
 | 
 | ||||||
|                         // To be continued later with PutByValue.
 |                         // To be continued later with PutByValue.
 | ||||||
|                     } else if (expression.property().is_identifier()) { |                     } else if (expression.property().is_identifier()) { | ||||||
|  |  | ||||||
|  | @ -256,11 +256,13 @@ CodeGenerationErrorOr<Generator::ReferenceOperands> Generator::emit_load_from_re | ||||||
|     auto base = TRY(expression.object().generate_bytecode(*this)).value(); |     auto base = TRY(expression.object().generate_bytecode(*this)).value(); | ||||||
|     if (expression.is_computed()) { |     if (expression.is_computed()) { | ||||||
|         auto property = TRY(expression.property().generate_bytecode(*this)).value(); |         auto property = TRY(expression.property().generate_bytecode(*this)).value(); | ||||||
|  |         auto saved_property = Operand(allocate_register()); | ||||||
|  |         emit<Bytecode::Op::Mov>(saved_property, property); | ||||||
|         auto dst = preferred_dst.has_value() ? preferred_dst.value() : Operand(allocate_register()); |         auto dst = preferred_dst.has_value() ? preferred_dst.value() : Operand(allocate_register()); | ||||||
|         emit<Bytecode::Op::GetByValue>(dst, base, property); |         emit<Bytecode::Op::GetByValue>(dst, base, property); | ||||||
|         return ReferenceOperands { |         return ReferenceOperands { | ||||||
|             .base = base, |             .base = base, | ||||||
|             .referenced_name = property, |             .referenced_name = saved_property, | ||||||
|             .this_value = base, |             .this_value = base, | ||||||
|             .loaded_value = dst, |             .loaded_value = dst, | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|  | @ -0,0 +1,21 @@ | ||||||
|  | test("Assignment should always evaluate LHS first", () => { | ||||||
|  |     function go(a) { | ||||||
|  |         let i = 0; | ||||||
|  |         a[i] = a[++i]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let a = [1, 2, 3]; | ||||||
|  |     go(a); | ||||||
|  |     expect(a).toEqual([2, 2, 3]); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test("Binary assignment should always evaluate LHS first", () => { | ||||||
|  |     function go(a) { | ||||||
|  |         let i = 0; | ||||||
|  |         a[i] |= a[++i]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let a = [1, 2]; | ||||||
|  |     go(a); | ||||||
|  |     expect(a).toEqual([3, 2]); | ||||||
|  | }); | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Andreas Kling
						Andreas Kling