From 31aa255278ee515d827e0b7fae470797dcea3fd2 Mon Sep 17 00:00:00 2001 From: stepan Date: Wed, 6 May 2026 17:36:27 +0200 Subject: [PATCH] Preserve exception group order for except-star reraises --- .../tests/unittest_tags/test_except_star.txt | 1 + .../exception/BaseExceptionGroupBuiltins.java | 43 ++++++++++++++++++- .../EncapsulateExceptionGroupNode.java | 15 +++---- 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_except_star.txt b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_except_star.txt index c596c0e0b1..a88aa5afb1 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_except_star.txt +++ b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_except_star.txt @@ -47,6 +47,7 @@ test.test_except_star.TestExceptStarSplitSemantics.test_singleton_groups_are_kep test.test_except_star.TestExceptStar_WeirdExceptionGroupSubclass.test_catch_all_unhashable_exception_group_subclass @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_except_star.TestExceptStar_WeirdExceptionGroupSubclass.test_catch_none_unhashable_exception_group_subclass @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_except_star.TestExceptStar_WeirdExceptionGroupSubclass.test_catch_some_unhashable_exception_group_subclass @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_except_star.TestExceptStar_WeirdExceptionGroupSubclass.test_reraise_unhashable_eg @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_except_star.TestExceptStar_WeirdLeafExceptions.test_catch_everything_unhashable_leaf @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_except_star.TestExceptStar_WeirdLeafExceptions.test_catch_nothing_unhashable_leaf @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_except_star.TestExceptStar_WeirdLeafExceptions.test_catch_unhashable_leaf_exception @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/exception/BaseExceptionGroupBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/exception/BaseExceptionGroupBuiltins.java index 43b16365cc..151021bd0c 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/exception/BaseExceptionGroupBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/exception/BaseExceptionGroupBuiltins.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -51,7 +51,10 @@ import static com.oracle.graal.python.util.PythonUtils.tsLiteral; import java.util.ArrayList; +import java.util.Collections; +import java.util.IdentityHashMap; import java.util.List; +import java.util.Set; import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.annotations.Builtin; @@ -299,6 +302,44 @@ private static PBaseExceptionGroup subset(Node inliningTarget, PBaseExceptionGro return eg; } + /** + * Build the sub-exception group of {@code orig} that contains all leaf exceptions also present + * in {@code keep}. This corresponds to CPython's {@code exception_group_projection()} helper. + */ + @TruffleBoundary + public static PBaseExceptionGroup exceptionGroupProjection(Node inliningTarget, PBaseExceptionGroup orig, Object[] keep) { + Set leafExceptions = Collections.newSetFromMap(new IdentityHashMap<>()); + for (Object exception : keep) { + collectExceptionGroupLeafIdentities(exception, leafExceptions); + } + return exceptionGroupProjection(inliningTarget, orig, leafExceptions); + } + + private static void collectExceptionGroupLeafIdentities(Object exception, Set leafExceptions) { + if (exception instanceof PBaseExceptionGroup group) { + for (Object child : group.getExceptions()) { + collectExceptionGroupLeafIdentities(child, leafExceptions); + } + } else if (exception != PNone.NONE) { + leafExceptions.add(exception); + } + } + + private static PBaseExceptionGroup exceptionGroupProjection(Node inliningTarget, PBaseExceptionGroup orig, Set leafExceptions) { + List matches = new ArrayList<>(); + for (Object exception : orig.getExceptions()) { + if (exception instanceof PBaseExceptionGroup group) { + PBaseExceptionGroup projected = exceptionGroupProjection(inliningTarget, group, leafExceptions); + if (projected != null) { + matches.add(projected); + } + } else if (leafExceptions.contains(exception)) { + matches.add(exception); + } + } + return subset(inliningTarget, orig, matches.toArray()); + } + private enum MatcherType { BY_TYPE, BY_PREDICATE, diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/exception/EncapsulateExceptionGroupNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/exception/EncapsulateExceptionGroupNode.java index c658092f73..8babbe6225 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/exception/EncapsulateExceptionGroupNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/exception/EncapsulateExceptionGroupNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 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,6 +42,7 @@ import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.builtins.objects.PNone; +import com.oracle.graal.python.builtins.objects.exception.BaseExceptionGroupBuiltins; import com.oracle.graal.python.builtins.objects.exception.PBaseException; import com.oracle.graal.python.builtins.objects.exception.PBaseExceptionGroup; import com.oracle.graal.python.lib.PyObjectCallMethodObjArgs; @@ -102,9 +103,7 @@ public static Object matchExceptions(VirtualFrame frame, if (isBaseExceptionGroupProfile.profile(inliningTarget, exceptionObj instanceof PBaseExceptionGroup)) { PBaseExceptionGroup group = (PBaseExceptionGroup) exceptionObj; if (groupContainsReraisesProfile.profile(inliningTarget, group.getContainsReraises())) { - for (Object e : group.getExceptions()) { - reraisedUnhandledExceptions.add(e); - } + reraisedUnhandledExceptions.add(group); } else { exceptionGroupList.add(group); } @@ -117,12 +116,8 @@ public static Object matchExceptions(VirtualFrame frame, return PException.fromExceptionInfo(exceptionGroup, PythonOptions.isPExceptionWithJavaStacktrace(language)); } if (reraisedOrUnhandledSizeProfile.profile(inliningTarget, reraisedUnhandledExceptions.size() != 0)) { - PBaseExceptionGroup reraisedGroup = (PBaseExceptionGroup) deriveExceptionGroup.execute( - frame, - inliningTarget, - exceptionOrigUnreified, - T_DERIVE, - PFactory.createTuple(language, reraisedUnhandledExceptions.toArray(new Object[0]))); + PBaseExceptionGroup reraisedGroup = BaseExceptionGroupBuiltins.exceptionGroupProjection( + inliningTarget, (PBaseExceptionGroup) exceptionOrigUnreified, reraisedUnhandledExceptions.toArray(new Object[0])); reraisedGroup.setParent(exceptionGroup.getParent()); exceptionGroupList.add(reraisedGroup); }