From 22479669c864be4685e22eda99c1c89ac1b70a9d Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Mon, 5 Oct 2026 14:16:49 +0200 Subject: [PATCH] Reraise location needs to have the right bytecode node Fixes #1186 --- .../test/interop/PolyglotInteropTest.java | 43 ++++++++++++++++++- .../builtins/modules/ImpModuleBuiltins.java | 2 +- .../cext/common/LoadCExtException.java | 7 +-- .../MaterializeLazyTracebackNode.java | 4 +- .../python/nodes/bytecode/RaiseNode.java | 4 +- .../bytecode_dsl/PBytecodeDSLRootNode.java | 11 ++--- .../python/runtime/exception/PException.java | 9 ++-- 7 files changed, 62 insertions(+), 18 deletions(-) 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); }