mirror of
				https://github.com/LadybirdBrowser/ladybird.git
				synced 2025-10-31 13:20:59 +00:00 
			
		
		
		
	LibJS: Save scheduled jumps when entering unwind contexts
These are then restored upon `ContinuePendingUnwind`. This stops us from forgetting where we needed to jump when we do extra try-catches in finally blocks. Co-Authored-By: Jesús "gsus" Lapastora <cyber.gsuscode@gmail.com>
This commit is contained in:
		
							parent
							
								
									4da5b8ec67
								
							
						
					
					
						commit
						1341f4438d
					
				
				
				Notes:
				
					sideshowbarker
				
				2024-07-17 07:16:27 +09:00 
				
			
			Author: https://github.com/Hendiadyoin1
Commit: 1341f4438d
Pull-request: https://github.com/SerenityOS/serenity/pull/21653
			
					 4 changed files with 95 additions and 2 deletions
				
			
		|  | @ -15,7 +15,6 @@ namespace JS::Bytecode { | ||||||
| 
 | 
 | ||||||
| struct UnwindInfo { | struct UnwindInfo { | ||||||
|     Executable const* executable; |     Executable const* executable; | ||||||
| 
 |  | ||||||
|     JS::GCPtr<Environment> lexical_environment; |     JS::GCPtr<Environment> lexical_environment; | ||||||
| 
 | 
 | ||||||
|     bool handler_called { false }; |     bool handler_called { false }; | ||||||
|  |  | ||||||
|  | @ -246,7 +246,7 @@ void Interpreter::run_bytecode() | ||||||
|                 enter_unwind_context(); |                 enter_unwind_context(); | ||||||
|                 m_current_block = &static_cast<Op::EnterUnwindContext const&>(instruction).entry_point().block(); |                 m_current_block = &static_cast<Op::EnterUnwindContext const&>(instruction).entry_point().block(); | ||||||
|                 goto start; |                 goto start; | ||||||
|             case Instruction::Type::ContinuePendingUnwind: |             case Instruction::Type::ContinuePendingUnwind: { | ||||||
|                 if (auto exception = reg(Register::exception()); !exception.is_empty()) { |                 if (auto exception = reg(Register::exception()); !exception.is_empty()) { | ||||||
|                     result = throw_completion(exception); |                     result = throw_completion(exception); | ||||||
|                     break; |                     break; | ||||||
|  | @ -255,14 +255,20 @@ void Interpreter::run_bytecode() | ||||||
|                     do_return(saved_return_value()); |                     do_return(saved_return_value()); | ||||||
|                     break; |                     break; | ||||||
|                 } |                 } | ||||||
|  |                 auto const* old_scheduled_jump = call_frame().previously_scheduled_jumps.take_last(); | ||||||
|                 if (m_scheduled_jump) { |                 if (m_scheduled_jump) { | ||||||
|                     // FIXME: If we `break` or `continue` in the finally, we need to clear
 |                     // FIXME: If we `break` or `continue` in the finally, we need to clear
 | ||||||
|                     //        this field
 |                     //        this field
 | ||||||
|  |                     //        Same goes for popping an old_scheduled_jump form the stack
 | ||||||
|                     m_current_block = exchange(m_scheduled_jump, nullptr); |                     m_current_block = exchange(m_scheduled_jump, nullptr); | ||||||
|                 } else { |                 } else { | ||||||
|                     m_current_block = &static_cast<Op::ContinuePendingUnwind const&>(instruction).resume_target().block(); |                     m_current_block = &static_cast<Op::ContinuePendingUnwind const&>(instruction).resume_target().block(); | ||||||
|  |                     // set the scheduled jump to the old value if we continue
 | ||||||
|  |                     // where we left it
 | ||||||
|  |                     m_scheduled_jump = old_scheduled_jump; | ||||||
|                 } |                 } | ||||||
|                 goto start; |                 goto start; | ||||||
|  |             } | ||||||
|             case Instruction::Type::ScheduleJump: { |             case Instruction::Type::ScheduleJump: { | ||||||
|                 m_scheduled_jump = &static_cast<Op::ScheduleJump const&>(instruction).target().block(); |                 m_scheduled_jump = &static_cast<Op::ScheduleJump const&>(instruction).target().block(); | ||||||
|                 auto const* finalizer = m_current_block->finalizer(); |                 auto const* finalizer = m_current_block->finalizer(); | ||||||
|  | @ -418,6 +424,8 @@ void Interpreter::enter_unwind_context() | ||||||
|     unwind_contexts().empend( |     unwind_contexts().empend( | ||||||
|         m_current_executable, |         m_current_executable, | ||||||
|         vm().running_execution_context().lexical_environment); |         vm().running_execution_context().lexical_environment); | ||||||
|  |     call_frame().previously_scheduled_jumps.append(m_scheduled_jump); | ||||||
|  |     m_scheduled_jump = nullptr; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Interpreter::leave_unwind_context() | void Interpreter::leave_unwind_context() | ||||||
|  |  | ||||||
|  | @ -32,6 +32,7 @@ struct CallFrame { | ||||||
|     Vector<Value> registers; |     Vector<Value> registers; | ||||||
|     Vector<GCPtr<Environment>> saved_lexical_environments; |     Vector<GCPtr<Environment>> saved_lexical_environments; | ||||||
|     Vector<UnwindInfo> unwind_contexts; |     Vector<UnwindInfo> unwind_contexts; | ||||||
|  |     Vector<BasicBlock const*> previously_scheduled_jumps; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class Interpreter { | class Interpreter { | ||||||
|  |  | ||||||
|  | @ -361,3 +361,88 @@ test("Throw while breaking", () => { | ||||||
| 
 | 
 | ||||||
|     expect(executionOrder).toEqual([1, 2, 3]); |     expect(executionOrder).toEqual([1, 2, 3]); | ||||||
| }); | }); | ||||||
|  | 
 | ||||||
|  | test("Throw while breaking with nested try-catch in finalizer", () => { | ||||||
|  |     const executionOrder = []; | ||||||
|  |     try { | ||||||
|  |         for (const i = 1337; ; expect().fail("Jumped to for loop update block")) { | ||||||
|  |             try { | ||||||
|  |                 executionOrder.push(1); | ||||||
|  |                 break; | ||||||
|  |             } finally { | ||||||
|  |                 try { | ||||||
|  |                     throw 1; | ||||||
|  |                 } catch { | ||||||
|  |                     executionOrder.push(2); | ||||||
|  |                 } | ||||||
|  |                 executionOrder.push(3); | ||||||
|  |             } | ||||||
|  |             expect().fail("Jumped out of inner finally statement"); | ||||||
|  |         } | ||||||
|  |     } finally { | ||||||
|  |         executionOrder.push(4); | ||||||
|  |     } | ||||||
|  |     expect(() => { | ||||||
|  |         i; | ||||||
|  |     }).toThrowWithMessage(ReferenceError, "'i' is not defined"); | ||||||
|  | 
 | ||||||
|  |     expect(executionOrder).toEqual([1, 2, 3, 4]); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test("Throw while breaking with nested try-catch-finally in finalizer", () => { | ||||||
|  |     const executionOrder = []; | ||||||
|  |     try { | ||||||
|  |         for (const i = 1337; ; expect().fail("Jumped to for loop update block")) { | ||||||
|  |             try { | ||||||
|  |                 executionOrder.push(1); | ||||||
|  |                 break; | ||||||
|  |             } finally { | ||||||
|  |                 try { | ||||||
|  |                     executionOrder.push(2); | ||||||
|  |                 } catch { | ||||||
|  |                     expect.fail("Entered catch"); | ||||||
|  |                 } finally { | ||||||
|  |                     executionOrder.push(3); | ||||||
|  |                 } | ||||||
|  |                 executionOrder.push(4); | ||||||
|  |             } | ||||||
|  |             expect().fail("Jumped out of inner finally statement"); | ||||||
|  |         } | ||||||
|  |     } finally { | ||||||
|  |         executionOrder.push(5); | ||||||
|  |     } | ||||||
|  |     expect(() => { | ||||||
|  |         i; | ||||||
|  |     }).toThrowWithMessage(ReferenceError, "'i' is not defined"); | ||||||
|  | 
 | ||||||
|  |     expect(executionOrder).toEqual([1, 2, 3, 4, 5]); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test("Throw while breaking with nested try-catch-finally with throw in finalizer", () => { | ||||||
|  |     const executionOrder = []; | ||||||
|  |     try { | ||||||
|  |         for (const i = 1337; ; expect().fail("Jumped to for loop update block")) { | ||||||
|  |             try { | ||||||
|  |                 executionOrder.push(1); | ||||||
|  |                 break; | ||||||
|  |             } finally { | ||||||
|  |                 try { | ||||||
|  |                     throw 1; | ||||||
|  |                 } catch { | ||||||
|  |                     executionOrder.push(2); | ||||||
|  |                 } finally { | ||||||
|  |                     executionOrder.push(3); | ||||||
|  |                 } | ||||||
|  |                 executionOrder.push(4); | ||||||
|  |             } | ||||||
|  |             expect().fail("Jumped out of inner finally statement"); | ||||||
|  |         } | ||||||
|  |     } finally { | ||||||
|  |         executionOrder.push(5); | ||||||
|  |     } | ||||||
|  |     expect(() => { | ||||||
|  |         i; | ||||||
|  |     }).toThrowWithMessage(ReferenceError, "'i' is not defined"); | ||||||
|  | 
 | ||||||
|  |     expect(executionOrder).toEqual([1, 2, 3, 4, 5]); | ||||||
|  | }); | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Hendiadyoin1
						Hendiadyoin1