diff --git a/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/interop/PolyglotInteropTest.java b/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/interop/PolyglotInteropTest.java
index e500684ecd..e9ee01f860 100644
--- a/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/interop/PolyglotInteropTest.java
+++ b/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/interop/PolyglotInteropTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
@@ -42,11 +42,14 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
import java.io.IOException;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Context.Builder;
+import org.graalvm.polyglot.PolyglotException;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
import org.junit.After;
@@ -83,6 +86,44 @@ public void tearDown() {
context.close();
}
+ @Test
+ public void exceptionFromExecThroughFinally() {
+ assertExceptionFromExec("""
+ def run(code):
+ try:
+ exec(code, {})
+ finally:
+ pass
+ """);
+ }
+
+ @Test
+ public void exceptionFromExecThroughExceptReraise() {
+ assertExceptionFromExec("""
+ def run(code):
+ try:
+ exec(code, {})
+ except BaseException:
+ raise
+ """);
+ }
+
+ private void assertExceptionFromExec(String supportCode) {
+ context.eval(Source.newBuilder("python", supportCode, "support.py").buildLiteral());
+ Value run = context.getBindings("python").getMember("run");
+ String[] code = {"undefined_name", "raise ValueError('boom')", "1 / 0"};
+ String[] messages = {"NameError: name 'undefined_name' is not defined", "ValueError: boom", "ZeroDivisionError: division by zero"};
+ // Exercise both the uncached and cached bytecode interpreters.
+ for (int round = 0; round < 10; round++) {
+ for (int i = 0; i < code.length; i++) {
+ String snippet = code[i];
+ PolyglotException exception = assertThrows(PolyglotException.class, () -> run.execute(snippet));
+ assertTrue(exception.isGuestException());
+ assertEquals(messages[i], exception.getMessage());
+ }
+ }
+ }
+
@Test
public void accessSuitePy() throws IOException {
Source suitePy = Source.newBuilder("python", "{ \"libraries\" : {\n" +
diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ImpModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ImpModuleBuiltins.java
index 061d2bf9e4..25c615978c 100644
--- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ImpModuleBuiltins.java
+++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ImpModuleBuiltins.java
@@ -884,7 +884,7 @@ private static Object doCreate(PythonObject moduleSpec, Node inliningTarget, Pyt
} catch (CannotCastException e) {
throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.BAD_ARG_TYPE_FOR_BUILTIN_OP);
} catch (ApiInitException ie) {
- throw ie.reraise();
+ throw ie.reraise(inliningTarget);
} catch (ImportException ie) {
throw ie.reraise();
} catch (IOException e) {
diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/LoadCExtException.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/LoadCExtException.java
index 253263aa12..d48b2c4c38 100644
--- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/LoadCExtException.java
+++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/LoadCExtException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
@@ -46,6 +46,7 @@
import com.oracle.graal.python.nodes.PConstructAndRaiseNode;
import com.oracle.graal.python.runtime.exception.PException;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.strings.TruffleString;
public abstract class LoadCExtException extends Exception {
@@ -89,9 +90,9 @@ public ApiInitException(TruffleString formatString, Object... formatArgs) {
}
@TruffleBoundary
- public PException reraise() {
+ public PException reraise(Node node) {
if (cause instanceof PException pcause) {
- throw pcause.getExceptionForReraise(false);
+ throw pcause.getExceptionForReraise(node, false);
} else if (cause != null) {
throw PConstructAndRaiseNode.getUncached().executeWithFmtMessageAndArgs(null, SystemError, ErrorMessages.M, new Object[]{cause}, null);
}
diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/traceback/MaterializeLazyTracebackNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/traceback/MaterializeLazyTracebackNode.java
index dbae553ba6..31d2c2d50a 100644
--- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/traceback/MaterializeLazyTracebackNode.java
+++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/traceback/MaterializeLazyTracebackNode.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
@@ -109,7 +109,7 @@
*
{@link PException PException} must never be rethrown after it has been possibly exposed to
* the program, because its Truffle stacktrace may already be frozen, and it would not capture more
* frames. If you need to rethrow without the catching site appearing in the traceback, use
- * {@link PException#getExceptionForReraise(boolean)} method to obtain a fresh {@link PException
+ * {@link PException#getExceptionForReraise} method to obtain a fresh {@link PException
* PException} to throw
*
*
diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/RaiseNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/RaiseNode.java
index 5ffc63ccb0..34e3ecf877 100644
--- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/RaiseNode.java
+++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/RaiseNode.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2025, Oracle and/or its affiliates.
+ * Copyright (c) 2017, 2026, Oracle and/or its affiliates.
* Copyright (c) 2013, Regents of the University of California
*
* All rights reserved.
@@ -137,7 +137,7 @@ static void reraise(VirtualFrame frame, @SuppressWarnings("unused") PNone type,
@Cached InlinedConditionProfile hasPException) {
AbstractTruffleException caughtException = getCaughtExceptionNode.execute(frame);
if (hasPException.profile(inliningTarget, caughtException instanceof PException)) {
- PException exceptionToReraise = ((PException) caughtException).getExceptionForReraise(rootNodeVisible);
+ PException exceptionToReraise = ((PException) caughtException).getExceptionForReraise(inliningTarget, rootNodeVisible);
exceptionToReraise.dontTraceOnReraise();
throw exceptionToReraise;
} else if (caughtException != null) {
diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java
index 43233ca540..6bc56316c9 100644
--- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java
+++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java
@@ -1018,7 +1018,7 @@ private PException traceException(VirtualFrame frame, BytecodeNode bytecode, int
}
// The exception was reified already. Return a new exception that looks like this
// catch didn't happen.
- result = result.getExceptionForReraise(!isInternal());
+ result = result.getExceptionForReraise(bytecode, !isInternal());
result.setCatchLocation(bci, bytecode);
}
}
@@ -3663,8 +3663,9 @@ public static void perform(VirtualFrame frame, Object typeOrExceptionObject, Obj
public static final class Reraise {
@Specialization
public static void doPException(PException ex,
+ @Bind BytecodeNode bytecode,
@Bind PBytecodeDSLRootNode root) {
- PException pe = ex.getExceptionForReraise(!root.isInternal());
+ PException pe = ex.getExceptionForReraise(bytecode, !root.isInternal());
pe.dontTraceOnReraise();
throw pe;
}
@@ -4236,7 +4237,7 @@ public static void doExceptional(VirtualFrame frame,
Object result = callExit.execute(frame, exit, contextManager, excType, pythonException, excTraceback);
if (!isTrue.execute(frame, result)) {
if (exception instanceof PException pException) {
- PException reraisedException = pException.getExceptionForReraise(!rootNode.isInternal());
+ PException reraisedException = pException.getExceptionForReraise(inliningTarget, !rootNode.isInternal());
reraisedException.dontTraceOnReraise();
throw reraisedException;
} else if (exception instanceof AbstractTruffleException ate) {
@@ -4334,7 +4335,7 @@ public static void doExceptional(VirtualFrame frame,
@Cached PyObjectIsTrueNode isTrue) {
if (!isTrue.execute(frame, result)) {
if (exception instanceof PException) {
- throw ((PException) exception).getExceptionForReraise(!rootNode.isInternal());
+ throw ((PException) exception).getExceptionForReraise(inliningTarget, !rootNode.isInternal());
} else if (exception instanceof AbstractTruffleException) {
throw (AbstractTruffleException) exception;
} else {
@@ -4734,7 +4735,7 @@ static void doPException(PException exception,
@InliningCutoff
private static void reraiseException(PException exception, BytecodeNode bytecodeNode) {
- throw exception.getExceptionForReraise(!((PBytecodeDSLRootNode) bytecodeNode.getRootNode()).internal);
+ throw exception.getExceptionForReraise(bytecodeNode, !((PBytecodeDSLRootNode) bytecodeNode.getRootNode()).internal);
}
@Specialization(guards = "!isPException(exception)")
diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/exception/PException.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/exception/PException.java
index ebdc51fd95..d4a17879eb 100644
--- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/exception/PException.java
+++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/exception/PException.java
@@ -427,9 +427,9 @@ public void notifyAddedTracebackFrame(Frame frame, boolean visible) {
* look like the last catch didn't happen, which is desired in `raise` without arguments, at the
* end of `finally`, `__exit__`...
*/
- public PException getExceptionForReraise(boolean rootNodeVisible) {
+ public PException getExceptionForReraise(Node location, boolean rootNodeVisible) {
ensureReified();
- PException pe = PException.fromObject(pythonException, getLocation(), false);
+ PException pe = PException.fromObjectFixUncachedLocation(pythonException, location, false);
pe.reraised = true;
if (pe.getUnreifiedException() instanceof PBaseExceptionGroup grp) {
grp.setContainsReraises(true);
@@ -465,10 +465,11 @@ boolean isException() {
}
@ExportMessage
- RuntimeException throwException(@Exclusive @Cached GilNode gil) {
+ RuntimeException throwException(@Bind Node location,
+ @Exclusive @Cached GilNode gil) {
boolean mustRelease = gil.acquire();
try {
- throw getExceptionForReraise(false);
+ throw getExceptionForReraise(location, false);
} finally {
gil.release(mustRelease);
}