mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-28 23:39:02 +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 {
|
||||
Executable const* executable;
|
||||
|
||||
JS::GCPtr<Environment> lexical_environment;
|
||||
|
||||
bool handler_called { false };
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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]);
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue