Jelajahi Sumber

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.
Linus Groh 4 tahun lalu
induk
melakukan
7cbede4342

+ 22 - 7
Userland/Libraries/LibJS/AST.cpp

@@ -1988,14 +1988,29 @@ Value TryStatement::execute(Interpreter& interpreter, GlobalObject& global_objec
         // execute() the finalizer without an exception in our way.
         auto* previous_exception = interpreter.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();
-        result = m_finalizer->execute(interpreter, global_object);
-        // 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);
+
+        auto finalizer_result = m_finalizer->execute(interpreter, global_object);
+        if (interpreter.vm().should_unwind()) {
+            // This was NOT a 'normal' completion (e.g. return from finalizer).
+            result = finalizer_result;
+        } else {
+            // 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;

+ 12 - 0
Userland/Libraries/LibJS/Tests/try-catch-finally-return.js

@@ -32,3 +32,15 @@ test("return from finally block", () => {
     }
     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");
+});