1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 08:47:34 +00:00

LibJS: Fix return value of TryStatement with finalizer

Previously we would always return the result of executing the finalizer,
however the spec dictates the finalizer result must only be returned for
a non-normal completion.
I added some more comments along the way, which should make it more
clear what's going on - the unwinding and exception flow isn't super
straightforward here.
This commit is contained in:
Linus Groh 2021-04-13 00:57:17 +02:00 committed by Andreas Kling
parent e8cbcc2fbf
commit 7cbede4342
2 changed files with 34 additions and 7 deletions

View file

@ -1988,14 +1988,29 @@ Value TryStatement::execute(Interpreter& interpreter, GlobalObject& global_objec
// execute() the finalizer without an exception in our way. // execute() the finalizer without an exception in our way.
auto* previous_exception = interpreter.exception(); auto* previous_exception = interpreter.exception();
interpreter.vm().clear_exception(); interpreter.vm().clear_exception();
// Remember what scope type we were unwinding to, and temporarily
// clear it as well (e.g. return from handler).
auto unwind_until = interpreter.vm().unwind_until();
interpreter.vm().stop_unwind(); interpreter.vm().stop_unwind();
result = m_finalizer->execute(interpreter, global_object);
// If we previously had an exception and the finalizer didn't auto finalizer_result = m_finalizer->execute(interpreter, global_object);
// throw a new one, restore the old one. if (interpreter.vm().should_unwind()) {
// FIXME: This will print debug output in throw_exception() for // This was NOT a 'normal' completion (e.g. return from finalizer).
// a second time with m_should_log_exceptions enabled. result = finalizer_result;
if (previous_exception && !interpreter.exception()) } else {
interpreter.vm().throw_exception(previous_exception); // Continue unwinding to whatever we found ourselves unwinding
// to when the finalizer was entered (e.g. return from handler,
// which is unaffected by normal completion from finalizer).
interpreter.vm().unwind(unwind_until);
// If we previously had an exception and the finalizer didn't
// throw a new one, restore the old one.
// FIXME: This will print debug output in throw_exception() for
// a second time with m_should_log_exceptions enabled.
if (previous_exception && !interpreter.exception())
interpreter.vm().throw_exception(previous_exception);
}
} }
return result; return result;

View file

@ -32,3 +32,15 @@ test("return from finally block", () => {
} }
expect(foo()).toBe("baz"); expect(foo()).toBe("baz");
}); });
test("return from catch block with empty finally", () => {
function foo() {
try {
throw "foo";
} catch {
return "bar";
} finally {
}
}
expect(foo()).toBe("bar");
});