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:
Hendiadyoin1 2023-10-21 22:46:40 +02:00 committed by Andreas Kling
parent 4da5b8ec67
commit 1341f4438d
Notes: sideshowbarker 2024-07-17 07:16:27 +09:00
4 changed files with 95 additions and 2 deletions

View file

@ -15,7 +15,6 @@ namespace JS::Bytecode {
struct UnwindInfo {
Executable const* executable;
JS::GCPtr<Environment> lexical_environment;
bool handler_called { false };

View file

@ -246,7 +246,7 @@ void Interpreter::run_bytecode()
enter_unwind_context();
m_current_block = &static_cast<Op::EnterUnwindContext const&>(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<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;
}
case Instruction::Type::ScheduleJump: {
m_scheduled_jump = &static_cast<Op::ScheduleJump const&>(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()

View file

@ -32,6 +32,7 @@ struct CallFrame {
Vector<Value> registers;
Vector<GCPtr<Environment>> saved_lexical_environments;
Vector<UnwindInfo> unwind_contexts;
Vector<BasicBlock const*> previously_scheduled_jumps;
};
class Interpreter {

View file

@ -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]);
});