Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions core/engine/src/bytecompiler/jump_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,9 @@ impl JumpRecord {
finally_throw_flag,
finally_throw_index,
} => {
let index = value as i32;
// Note: +1 because 0 is reserved for the fallthrough entry of the
// jump table emitted in `pop_try_with_finally_control_info`.
let index = value as i32 + 1;
compiler
.bytecode
.emit_store_false(finally_throw_flag.into());
Expand Down Expand Up @@ -608,27 +610,33 @@ impl ByteCompiler<'_> {
self.patch_jump_with_target(*label, finally_start);
}

// The jump table holds `info.jumps.len() + 1` entries. Entry 0 is the
// fallthrough target, taken when no `break`, `continue`, or `return`
// inside the protected region selected a jump record (the index
// register keeps its initial value of `0`). Entries `1..=N` correspond
// to the registered jump records and are selected by `HandleFinally`.
// NOTE: +4 to jump past the index operand.
let jump_table_index = self.next_opcode_location() + size_of::<u32>() as u32;
self.bytecode.emit_jump_table(
finally_throw_index,
thin_vec![Self::DUMMY_ADDRESS; info.jumps.len()],
thin_vec![Self::DUMMY_ADDRESS; info.jumps.len() + 1],
);

// We are assuming any indices outside our jump table will fallback
// to executing the next available op. Since we kinda control the jump
// table index here, this doesn't matter too much, but we _could_ also
// throw a PanicError on the next instruction.
// Skip the jump-record handlers when falling through.
let fallthrough = self.jump();

let mut patch_jumps = Vec::with_capacity(info.jumps.len());
let mut patch_jumps = vec![Self::DUMMY_ADDRESS; info.jumps.len() + 1];
// Handle breaks/continue/returns in a finally block
for i in 0..info.jumps.len() {
patch_jumps.push(self.next_opcode_location());
patch_jumps[i + 1] = self.next_opcode_location();

let jump_record = info.jumps[i].clone();
jump_record.perform_actions(Self::DUMMY_ADDRESS, self);
}

self.patch_jump(fallthrough);
patch_jumps[0] = self.next_opcode_location();

self.bytecode
.patch_jump_table(jump_table_index, &patch_jumps);
}
Expand Down
42 changes: 42 additions & 0 deletions core/engine/src/tests/control_flow/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,48 @@ fn catch_binding_finally() {
)]);
}

#[test]
fn finally_fallthrough_with_untaken_return() {
run_test_actions([
TestAction::assert_eq(
indoc! {r#"
function g(x) {
try {
if (x) return -1;
} catch (e) {} finally {}
return 42;
}
g(0);
"#},
42,
),
TestAction::assert_eq(
indoc! {r#"
function g(x) {
try {
if (x) return -1;
} catch (e) {} finally {}
return 42;
}
g(1);
"#},
-1,
),
TestAction::assert_eq(
indoc! {r#"
function h(x) {
try {
if (x) return -1;
} finally {}
return 42;
}
h(0);
"#},
42,
),
]);
}

#[test]
fn finally_with_loop_break() {
run_test_actions([TestAction::assert_eq(
Expand Down
Loading