diff --git a/Userland/Libraries/LibJS/Bytecode/BasicBlock.h b/Userland/Libraries/LibJS/Bytecode/BasicBlock.h index b0fbfc63ff..3ea5aae1ce 100644 --- a/Userland/Libraries/LibJS/Bytecode/BasicBlock.h +++ b/Userland/Libraries/LibJS/Bytecode/BasicBlock.h @@ -15,7 +15,6 @@ namespace JS::Bytecode { struct UnwindInfo { Executable const* executable; - JS::GCPtr lexical_environment; bool handler_called { false }; diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp index 18880633cd..decb48ab69 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -246,7 +246,7 @@ void Interpreter::run_bytecode() enter_unwind_context(); m_current_block = &static_cast(instruction).entry_point().block(); goto start; - case Instruction::Type::ContinuePendingUnwind: + case Instruction::Type::ContinuePendingUnwind: { if (auto exception = reg(Register::exception()); !exception.is_empty()) { result = throw_completion(exception); break; @@ -255,14 +255,20 @@ void Interpreter::run_bytecode() do_return(saved_return_value()); break; } + auto const* old_scheduled_jump = call_frame().previously_scheduled_jumps.take_last(); if (m_scheduled_jump) { // FIXME: If we `break` or `continue` in the finally, we need to clear // this field + // Same goes for popping an old_scheduled_jump form the stack m_current_block = exchange(m_scheduled_jump, nullptr); } else { m_current_block = &static_cast(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; + } case Instruction::Type::ScheduleJump: { m_scheduled_jump = &static_cast(instruction).target().block(); auto const* finalizer = m_current_block->finalizer(); @@ -418,6 +424,8 @@ void Interpreter::enter_unwind_context() unwind_contexts().empend( m_current_executable, vm().running_execution_context().lexical_environment); + call_frame().previously_scheduled_jumps.append(m_scheduled_jump); + m_scheduled_jump = nullptr; } void Interpreter::leave_unwind_context() diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.h b/Userland/Libraries/LibJS/Bytecode/Interpreter.h index 043e5de467..2b26e38277 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.h +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.h @@ -32,6 +32,7 @@ struct CallFrame { Vector registers; Vector> saved_lexical_environments; Vector unwind_contexts; + Vector previously_scheduled_jumps; }; class Interpreter { diff --git a/Userland/Libraries/LibJS/Tests/try-finally-break.js b/Userland/Libraries/LibJS/Tests/try-finally-break.js index 5699acda12..0c34565682 100644 --- a/Userland/Libraries/LibJS/Tests/try-finally-break.js +++ b/Userland/Libraries/LibJS/Tests/try-finally-break.js @@ -361,3 +361,88 @@ test("Throw while breaking", () => { 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]); +});