LibJS: Intercept returns through finally blocks in Bytecode

This is still not perfect, as we now actually crash in the
`try-finally-continue` tests, while we now succeed all
`try-catch-finally-*` tests.

Note that we do not yet go through the finally block when exiting the
unwind context through a break or continue.
This commit is contained in:
Hendiadyoin1 2022-11-13 20:56:53 +01:00 committed by Ali Mohammad Pur
parent c2108489a5
commit fcc3348bc8
Notes: sideshowbarker 2024-07-17 20:22:04 +09:00
5 changed files with 81 additions and 9 deletions

View file

@ -1707,6 +1707,9 @@ Bytecode::CodeGenerationErrorOr<void> IfStatement::generate_bytecode(Bytecode::G
Bytecode::CodeGenerationErrorOr<void> ContinueStatement::generate_bytecode(Bytecode::Generator& generator) const
{
// FIXME: Handle finally blocks in a graceful manner
// We need to execute the finally block, but tell it to resume
// execution at the designated block
if (m_target_label.is_null()) {
generator.perform_needed_unwinds<Bytecode::Op::Jump>();
generator.emit<Bytecode::Op::Jump>().set_targets(
@ -1900,6 +1903,9 @@ Bytecode::CodeGenerationErrorOr<void> ThrowStatement::generate_bytecode(Bytecode
Bytecode::CodeGenerationErrorOr<void> BreakStatement::generate_bytecode(Bytecode::Generator& generator) const
{
// FIXME: Handle finally blocks in a graceful manner
// We need to execute the finally block, but tell it to resume
// execution at the designated block
if (m_target_label.is_null()) {
generator.perform_needed_unwinds<Bytecode::Op::Jump>(true);
generator.emit<Bytecode::Op::Jump>().set_targets(
@ -1937,9 +1943,15 @@ Bytecode::CodeGenerationErrorOr<void> TryStatement::generate_bytecode(Bytecode::
finalizer_target = Bytecode::Label { finalizer_block };
}
if (m_finalizer)
generator.start_boundary(Bytecode::Generator::BlockBoundaryType::ReturnToFinally);
if (m_handler) {
auto& handler_block = generator.make_block();
generator.switch_to_basic_block(handler_block);
if (!m_finalizer)
generator.emit<Bytecode::Op::LeaveUnwindContext>();
generator.begin_variable_scope(Bytecode::Generator::BindingMode::Lexical, Bytecode::Generator::SurroundingScopeKind::Block);
TRY(m_handler->parameter().visit(
[&](FlyString const& parameter) -> Bytecode::CodeGenerationErrorOr<void> {
@ -1974,11 +1986,15 @@ Bytecode::CodeGenerationErrorOr<void> TryStatement::generate_bytecode(Bytecode::
}
}
}
if (m_finalizer)
generator.end_boundary(Bytecode::Generator::BlockBoundaryType::ReturnToFinally);
auto& target_block = generator.make_block();
generator.switch_to_basic_block(saved_block);
generator.emit<Bytecode::Op::EnterUnwindContext>(Bytecode::Label { target_block }, handler_target, finalizer_target);
generator.start_boundary(Bytecode::Generator::BlockBoundaryType::Unwind);
if (m_finalizer)
generator.start_boundary(Bytecode::Generator::BlockBoundaryType::ReturnToFinally);
generator.switch_to_basic_block(target_block);
TRY(m_block->generate_bytecode(generator));
@ -1992,6 +2008,9 @@ Bytecode::CodeGenerationErrorOr<void> TryStatement::generate_bytecode(Bytecode::
next_block = &block;
}
}
if (m_finalizer)
generator.end_boundary(Bytecode::Generator::BlockBoundaryType::ReturnToFinally);
generator.end_boundary(Bytecode::Generator::BlockBoundaryType::Unwind);
generator.switch_to_basic_block(next_block ? *next_block : saved_block);

View file

@ -268,12 +268,17 @@ Label Generator::perform_needed_unwinds_for_labelled_break_and_return_target_blo
for (auto& breakable_scope : m_breakable_scopes.in_reverse()) {
for (; current_boundary > 0; --current_boundary) {
auto boundary = m_boundaries[current_boundary - 1];
// FIXME: Handle ReturnToFinally in a graceful manner
// We need to execute the finally block, but tell it to resume
// execution at the designated label
if (boundary == BlockBoundaryType::Unwind) {
emit<Bytecode::Op::LeaveUnwindContext>();
} else if (boundary == BlockBoundaryType::LeaveLexicalEnvironment) {
emit<Bytecode::Op::LeaveEnvironment>(Bytecode::Op::EnvironmentMode::Lexical);
} else if (boundary == BlockBoundaryType::LeaveVariableEnvironment) {
emit<Bytecode::Op::LeaveEnvironment>(Bytecode::Op::EnvironmentMode::Var);
} else if (boundary == BlockBoundaryType::ReturnToFinally) {
// FIXME: We need to enter the `finally`, while still scheduling the break to happen
} else if (boundary == BlockBoundaryType::Break) {
// Make sure we don't process this boundary twice if the current breakable scope doesn't contain the target label.
--current_boundary;
@ -295,12 +300,17 @@ Label Generator::perform_needed_unwinds_for_labelled_continue_and_return_target_
for (auto& continuable_scope : m_continuable_scopes.in_reverse()) {
for (; current_boundary > 0; --current_boundary) {
auto boundary = m_boundaries[current_boundary - 1];
// FIXME: Handle ReturnToFinally in a graceful manner
// We need to execute the finally block, but tell it to resume
// execution at the designated label
if (boundary == BlockBoundaryType::Unwind) {
emit<Bytecode::Op::LeaveUnwindContext>();
} else if (boundary == BlockBoundaryType::LeaveLexicalEnvironment) {
emit<Bytecode::Op::LeaveEnvironment>(Bytecode::Op::EnvironmentMode::Lexical);
} else if (boundary == BlockBoundaryType::LeaveVariableEnvironment) {
emit<Bytecode::Op::LeaveEnvironment>(Bytecode::Op::EnvironmentMode::Var);
} else if (boundary == BlockBoundaryType::ReturnToFinally) {
// FIXME: We need to enter the `finally`, while still scheduling the continue to happen
} else if (boundary == BlockBoundaryType::Continue) {
// Make sure we don't process this boundary twice if the current continuable scope doesn't contain the target label.
--current_boundary;

View file

@ -169,6 +169,7 @@ public:
Break,
Continue,
Unwind,
ReturnToFinally,
LeaveLexicalEnvironment,
LeaveVariableEnvironment,
};
@ -188,12 +189,27 @@ public:
auto boundary = m_boundaries[i - 1];
if (boundary_to_stop_at.has_value() && boundary == *boundary_to_stop_at)
break;
if (boundary == BlockBoundaryType::Unwind)
using enum BlockBoundaryType;
switch (boundary) {
case Unwind:
emit<Bytecode::Op::LeaveUnwindContext>();
else if (boundary == BlockBoundaryType::LeaveLexicalEnvironment)
break;
case LeaveLexicalEnvironment:
emit<Bytecode::Op::LeaveEnvironment>(Bytecode::Op::EnvironmentMode::Lexical);
else if (boundary == BlockBoundaryType::LeaveVariableEnvironment)
break;
case LeaveVariableEnvironment:
emit<Bytecode::Op::LeaveEnvironment>(Bytecode::Op::EnvironmentMode::Var);
break;
case Break:
case Continue:
break;
case ReturnToFinally:
// FIXME: In the case of breaks/continues we need to tell the `finally` to break/continue
// For now let's ignore the finally to avoid a crash
if (IsSame<OpType, Bytecode::Op::Jump>)
break;
return;
};
}
}

View file

@ -117,14 +117,28 @@ Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Executable const& e
++pc;
}
if (will_return)
break;
if (will_jump)
continue;
if (pc.at_end() && !will_jump)
if (!unwind_contexts().is_empty()) {
auto& unwind_context = unwind_contexts().last();
if (unwind_context.executable == m_current_executable && unwind_context.finalizer) {
m_saved_return_value = make_handle(m_return_value);
m_return_value = {};
m_current_block = unwind_context.finalizer;
// the unwind_context will be pop'ed when entering the finally block
continue;
}
}
if (pc.at_end())
break;
if (!m_saved_exception.is_null())
break;
if (will_return)
break;
}
dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter did run unit {:p}", &executable);
@ -142,8 +156,14 @@ Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Executable const& e
auto frame = m_register_windows.take_last();
auto return_value = m_return_value.value_or(js_undefined());
m_return_value = {};
Value return_value = js_undefined();
if (!m_return_value.is_empty()) {
return_value = m_return_value;
m_return_value = {};
} else if (!m_saved_return_value.is_null()) {
return_value = m_saved_return_value.value();
m_saved_return_value = {};
}
// NOTE: The return value from a called function is put into $0 in the caller context.
if (!m_register_windows.is_empty())
@ -168,7 +188,7 @@ Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Executable const& e
return { throw_completion(thrown_value), nullptr };
}
if (auto register_window = frame.get_pointer<NonnullOwnPtr<RegisterWindow>>())
if (auto* register_window = frame.get_pointer<NonnullOwnPtr<RegisterWindow>>())
return { return_value, move(*register_window) };
return { return_value, nullptr };
}
@ -191,6 +211,12 @@ ThrowCompletionOr<void> Interpreter::continue_pending_unwind(Label const& resume
return result;
}
if (!m_saved_return_value.is_null()) {
do_return(m_saved_return_value.value());
m_saved_return_value = {};
return {};
}
jump(resume_label);
return {};
}

View file

@ -103,6 +103,7 @@ private:
Vector<Variant<NonnullOwnPtr<RegisterWindow>, RegisterWindow*>> m_register_windows;
Optional<BasicBlock const*> m_pending_jump;
Value m_return_value;
Handle<Value> m_saved_return_value;
Executable const* m_current_executable { nullptr };
Handle<Value> m_saved_exception;
OwnPtr<JS::Interpreter> m_ast_interpreter;