mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-30 16:28:48 +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