Skip to content

Commit af94e4c

Browse files
Hendiadyoin1awesomekling
authored andcommitted
LibJS: Save and restore exceptions on yields in finalizers
Also removes a bunch of related old FIXMEs.
1 parent 8d3eb93 commit af94e4c

File tree

4 files changed

+41
-10
lines changed

4 files changed

+41
-10
lines changed

Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1782,6 +1782,12 @@ static void generate_yield(Bytecode::Generator& generator,
17821782

17831783
Bytecode::CodeGenerationErrorOr<Optional<Bytecode::Operand>> YieldExpression::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional<Bytecode::Operand> preferred_dst) const
17841784
{
1785+
// Note: We need to catch any scheduled exceptions and reschedule them on re-entry
1786+
// as the act of yielding would otherwise clear them out
1787+
// This only applies when we are in a finalizer
1788+
bool is_in_finalizer = generator.is_in_finalizer();
1789+
Optional<Bytecode::Register> saved_exception;
1790+
17851791
Bytecode::Generator::SourceLocationScope scope(generator, *this);
17861792
VERIFY(generator.is_in_generator_function());
17871793

@@ -1890,6 +1896,11 @@ Bytecode::CodeGenerationErrorOr<Optional<Bytecode::Operand>> YieldExpression::ge
18901896
auto current_value = Bytecode::Operand(generator.allocate_register());
18911897
generator.emit_iterator_value(current_value, inner_result);
18921898

1899+
if (is_in_finalizer) {
1900+
saved_exception = generator.allocate_register();
1901+
generator.emit<Bytecode::Op::Mov>(Bytecode::Operand(*saved_exception), Bytecode::Operand(Bytecode::Register::exception()));
1902+
}
1903+
18931904
generate_yield(generator,
18941905
Bytecode::Label { continuation_block },
18951906
current_value,
@@ -2077,6 +2088,10 @@ Bytecode::CodeGenerationErrorOr<Optional<Bytecode::Operand>> YieldExpression::ge
20772088
generate_yield(generator, Bytecode::Label { continuation_block }, received, received_completion, received_completion_type, received_completion_value, type_identifier, value_identifier, AwaitBeforeYield::No);
20782089

20792090
generator.switch_to_basic_block(continuation_block);
2091+
2092+
if (is_in_finalizer)
2093+
generator.emit<Bytecode::Op::Mov>(Bytecode::Operand(Bytecode::Register::exception()), Bytecode::Operand(*saved_exception));
2094+
20802095
generator.emit<Bytecode::Op::Mov>(received_completion, Bytecode::Operand(Bytecode::Register(0)));
20812096
get_received_completion_type_and_value(generator, received_completion, received_completion_type, received_completion_value, type_identifier, value_identifier);
20822097
generator.emit<Bytecode::Op::Jump>(Bytecode::Label { loop_block });
@@ -2092,8 +2107,18 @@ Bytecode::CodeGenerationErrorOr<Optional<Bytecode::Operand>> YieldExpression::ge
20922107
argument = generator.add_constant(js_undefined());
20932108

20942109
auto& continuation_block = generator.make_block();
2110+
2111+
if (is_in_finalizer) {
2112+
saved_exception = generator.allocate_register();
2113+
generator.emit<Bytecode::Op::Mov>(Bytecode::Operand(*saved_exception), Bytecode::Operand(Bytecode::Register::exception()));
2114+
}
2115+
20952116
generate_yield(generator, Bytecode::Label { continuation_block }, *argument, received_completion, received_completion_type, received_completion_value, type_identifier, value_identifier, AwaitBeforeYield::Yes);
20962117
generator.switch_to_basic_block(continuation_block);
2118+
2119+
if (is_in_finalizer)
2120+
generator.emit<Bytecode::Op::Mov>(Bytecode::Operand(Bytecode::Register::exception()), Bytecode::Operand(*saved_exception));
2121+
20972122
generator.emit<Bytecode::Op::Mov>(received_completion, Bytecode::Operand(Bytecode::Register(0)));
20982123
get_received_completion_type_and_value(generator, received_completion, received_completion_type, received_completion_value, type_identifier, value_identifier);
20992124

@@ -2190,9 +2215,6 @@ Bytecode::CodeGenerationErrorOr<Optional<Bytecode::Operand>> IfStatement::genera
21902215
Bytecode::CodeGenerationErrorOr<Optional<Bytecode::Operand>> ContinueStatement::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional<Bytecode::Operand> preferred_dst) const
21912216
{
21922217
Bytecode::Generator::SourceLocationScope scope(generator, *this);
2193-
// FIXME: Handle finally blocks in a graceful manner
2194-
// We need to execute the finally block, but tell it to resume
2195-
// execution at the designated block
21962218
if (!m_target_label.has_value()) {
21972219
generator.generate_continue();
21982220
return Optional<Bytecode::Operand> {};

Userland/Libraries/LibJS/Bytecode/Generator.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,8 @@ class Generator {
238238
}
239239
}
240240

241+
bool is_in_finalizer() const { return m_boundaries.contains_slow(BlockBoundaryType::LeaveFinally); }
242+
241243
void generate_break();
242244
void generate_break(DeprecatedFlyString const& break_label);
243245

Userland/Libraries/LibJS/Bytecode/Op.h

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1595,13 +1595,6 @@ class ScheduleJump final : public Instruction {
15951595
public:
15961596
// Note: We use this instruction to tell the next `finally` block to
15971597
// continue execution with a specific break/continue target;
1598-
// FIXME: We currently don't clear the interpreter internal flag, when we change
1599-
// the control-flow (`break`, `continue`) in a finally-block,
1600-
// FIXME: .NET on x86_64 uses a call to the finally instead, which could make this
1601-
// easier, at the cost of making control-flow changes (`break`, `continue`, `return`)
1602-
// in the finally-block more difficult, but as stated above, those
1603-
// aren't handled 100% correctly at the moment anyway
1604-
// It might be worth investigating a similar mechanism
16051598
constexpr static bool IsTerminator = true;
16061599

16071600
ScheduleJump(Label target)

Userland/Libraries/LibJS/Tests/try-return-finally.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,17 @@ test("return from outer finally with nested unwind contexts", () => {
2424

2525
expect(value).toBe(2);
2626
});
27+
28+
test("restore exception after generator yield in finally", () => {
29+
let generator = (function* () {
30+
try {
31+
throw new Error("foo");
32+
} finally {
33+
yield 42;
34+
}
35+
})();
36+
37+
expect(generator.next().value).toBe(42);
38+
expect(() => generator.next()).toThrowWithMessage(Error, "foo");
39+
expect(generator.next().done).toBe(true);
40+
});

0 commit comments

Comments
 (0)