Skip to content

Commit a456f47

Browse files
committed
[GR-76216] Handle native tuples in exception paths.
PullRequest: graalpython/4606
2 parents 2f450f5 + 6ad997e commit a456f47

11 files changed

Lines changed: 111 additions & 76 deletions

File tree

.agents/skills/windows/SKILL.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
name: windows
3+
description: Windows-specific command guidance. Use when working on Windows and a required CLI tool, especially gdev-cli, is not available directly in PowerShell or cmd.
4+
---
5+
6+
# Windows
7+
8+
On Windows, if `gdev-cli` is not available, it may be available via `wsl bash -l -c gdev-cli`.

graalpython/com.oracle.graal.python.test/src/tests/test_patched_pip.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,12 @@ def build_package(self, name, version):
153153

154154
def add_package_to_index(self, name, version, dist_type):
155155
package = self.build_package(name, version)[dist_type]
156-
shutil.copy(package, self.index_dir)
156+
# extra careful, not using shutil.copy so we get more detailed traceback
157+
# on some flaky windows CI workers
158+
self.assertTrue(package.exists(), f"Built package disappeared: {package}")
159+
destination = self.index_dir / package.name
160+
with open(package, 'rb') as src, open(destination, 'wb') as dst:
161+
shutil.copyfileobj(src, dst)
157162

158163
def run_venv_pip_install(self, package, extra_env=None, assert_stderr_matches=None):
159164
env = self.pip_env.copy()

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/exception/BaseExceptionGroupBuiltins.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
6767
import com.oracle.graal.python.builtins.PythonBuiltins;
6868
import com.oracle.graal.python.builtins.objects.PNone;
69+
import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject;
6970
import com.oracle.graal.python.builtins.objects.common.EconomicMapStorage;
7071
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes;
7172
import com.oracle.graal.python.builtins.objects.dict.PDict;
@@ -355,8 +356,8 @@ private static MatcherType getMatcherType(Node inliningTarget, Object value) {
355356
if (isExceptionTypeUncached(value)) {
356357
return MatcherType.BY_TYPE;
357358
}
358-
if (value instanceof PTuple tuple && PyTupleCheckExactNode.executeUncached(tuple)) {
359-
SequenceStorage storage = tuple.getSequenceStorage();
359+
SequenceStorage storage = getExactTupleStorage(value);
360+
if (storage != null) {
360361
for (int i = 0; i < storage.length(); i++) {
361362
Object elem = SequenceStorageNodes.GetItemScalarNode.executeUncached(storage, i);
362363
if (!isExceptionTypeUncached(elem)) {
@@ -368,6 +369,16 @@ private static MatcherType getMatcherType(Node inliningTarget, Object value) {
368369
throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.EXPECTED_A_FUNCTION_EXCEPTION_TYPE_OR_TUPLE_OF_EXCEPTION_TYPES);
369370
}
370371

372+
private static SequenceStorage getExactTupleStorage(Object value) {
373+
if (value instanceof PTuple tuple && PyTupleCheckExactNode.executeUncached(tuple)) {
374+
return tuple.getSequenceStorage();
375+
}
376+
if (value instanceof PythonAbstractNativeObject nativeTuple && PyTupleCheckExactNode.executeUncached(nativeTuple)) {
377+
return TupleNodes.GetNativeTupleStorage.getUncached().execute(nativeTuple);
378+
}
379+
return null;
380+
}
381+
371382
private static boolean isExceptionTypeUncached(Object value) {
372383
return TypeNodes.IsTypeNode.executeUncached(value) && BuiltinClassProfiles.IsBuiltinClassProfile.profileClassSlowPath(value, PythonBuiltinClassType.PBaseException);
373384
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/exception/PrepareExceptionNode.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,15 @@
4646
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
4747
import com.oracle.graal.python.builtins.objects.PNone;
4848
import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject;
49-
import com.oracle.graal.python.builtins.objects.common.SequenceNodes;
50-
import com.oracle.graal.python.builtins.objects.tuple.PTuple;
49+
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes;
5150
import com.oracle.graal.python.builtins.objects.type.TypeNodes.IsTypeNode;
5251
import com.oracle.graal.python.lib.PyExceptionInstanceCheckNode;
5352
import com.oracle.graal.python.lib.PyObjectIsInstanceNode;
53+
import com.oracle.graal.python.lib.PyTupleCheckNode;
5454
import com.oracle.graal.python.nodes.ErrorMessages;
5555
import com.oracle.graal.python.nodes.PGuards;
5656
import com.oracle.graal.python.nodes.PRaiseNode;
57+
import com.oracle.graal.python.nodes.builtins.TupleNodes.GetTupleStorage;
5758
import com.oracle.graal.python.nodes.call.CallNode;
5859
import com.oracle.graal.python.nodes.classes.IsSubtypeNode;
5960
import com.oracle.graal.python.runtime.object.PFactory;
@@ -96,11 +97,12 @@ static Object doException(@SuppressWarnings("unused") PBaseException exc, @Suppr
9697
throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.INSTANCE_EX_MAY_NOT_HAVE_SEP_VALUE);
9798
}
9899

99-
@Specialization(guards = {"isTypeNode.execute(inliningTarget, type)", "!isPNone(value)", "!isPTuple(value)"}, limit = "1")
100+
@Specialization(guards = {"isTypeNode.execute(inliningTarget, type)", "!isPNone(value)", "!tupleCheck.execute(inliningTarget, value)"}, limit = "1")
100101
static Object doExceptionOrCreate(VirtualFrame frame, Object type, Object value,
101102
@Bind Node inliningTarget,
102103
@SuppressWarnings("unused") @Exclusive @Cached IsTypeNode isTypeNode,
103104
@Exclusive @Cached PyExceptionInstanceCheckNode check,
105+
@SuppressWarnings("unused") @Exclusive @Cached PyTupleCheckNode tupleCheck,
104106
@Cached PyObjectIsInstanceNode isInstanceNode,
105107
@Cached InlinedConditionProfile isInstanceProfile,
106108
@Shared @Cached IsSubtypeNode isSubtypeNode,
@@ -136,17 +138,19 @@ static Object doCreate(VirtualFrame frame, Object type, @SuppressWarnings("unuse
136138
}
137139
}
138140

139-
@Specialization(guards = "isTypeNode.execute(inliningTarget, type)", limit = "1")
140-
static Object doCreateTuple(VirtualFrame frame, Object type, PTuple value,
141+
@Specialization(guards = {"isTypeNode.execute(inliningTarget, type)", "tupleCheck.execute(inliningTarget, value)"}, limit = "1")
142+
static Object doCreateTuple(VirtualFrame frame, Object type, Object value,
141143
@Bind Node inliningTarget,
142144
@SuppressWarnings("unused") @Exclusive @Cached IsTypeNode isTypeNode,
143145
@Exclusive @Cached PyExceptionInstanceCheckNode check,
144-
@Cached SequenceNodes.GetObjectArrayNode getObjectArrayNode,
146+
@SuppressWarnings("unused") @Exclusive @Cached PyTupleCheckNode tupleCheck,
147+
@Exclusive @Cached GetTupleStorage getTupleStorage,
148+
@Exclusive @Cached SequenceStorageNodes.ToArrayNode toArrayNode,
145149
@Shared @Cached IsSubtypeNode isSubtypeNode,
146150
@Exclusive @Cached PRaiseNode raiseNode,
147151
@Shared("callCtor") @Cached CallNode callConstructor) {
148152
checkExceptionClass(inliningTarget, type, isSubtypeNode, raiseNode);
149-
Object[] args = getObjectArrayNode.execute(inliningTarget, value);
153+
Object[] args = toArrayNode.execute(inliningTarget, getTupleStorage.execute(inliningTarget, value));
150154
Object instance = callConstructor.execute(frame, type, args);
151155
if (check.execute(inliningTarget, instance)) {
152156
return instance;

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectIsInstanceNode.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,10 @@ public static PyObjectIsInstanceNode getUncached() {
8787
return PyObjectIsInstanceNodeGen.getUncached();
8888
}
8989

90-
@Specialization(guards = "!isPTuple(cls)", insertBefore = "doTupleConstantLen")
90+
@Specialization(guards = "!tupleCheck.execute(inliningTarget, cls)", insertBefore = "doRecursiveWithNode", limit = "1")
9191
static boolean isInstance(VirtualFrame frame, Object instance, Object cls, @SuppressWarnings("unused") int depth,
9292
@Bind Node inliningTarget,
93+
@SuppressWarnings("unused") @Cached PyTupleCheckNode tupleCheck,
9394
@Cached GetClassNode getClsClassNode,
9495
@Cached IsBuiltinClassExactProfile classProfile,
9596
@Cached GetClassNode getInstanceClassNode,

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectIsSubclassNode.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,10 @@ public static PyObjectIsSubclassNode getUncached() {
8787
return PyObjectIsSubclassNodeGen.getUncached();
8888
}
8989

90-
@Specialization(guards = "!isPTuple(cls)", insertBefore = "doTupleConstantLen")
90+
@Specialization(guards = "!tupleCheck.execute(inliningTarget, cls)", insertBefore = "doRecursiveWithNode", limit = "1")
9191
static boolean isSubclass(VirtualFrame frame, Object derived, Object cls, @SuppressWarnings("unused") int depth,
9292
@Bind Node inliningTarget,
93+
@SuppressWarnings("unused") @Cached PyTupleCheckNode tupleCheck,
9394
@Cached GetClassNode getClsClassNode,
9495
@Cached IsBuiltinClassExactProfile classProfile,
9596
@Cached LookupSpecialMethodNode.Dynamic subclassCheckLookup,

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectRecursiveBinaryCheckNode.java

Lines changed: 32 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,16 @@
4141
package com.oracle.graal.python.lib;
4242

4343
import com.oracle.graal.python.PythonLanguage;
44-
import com.oracle.graal.python.builtins.objects.common.SequenceNodes.GetObjectArrayNode;
45-
import com.oracle.graal.python.builtins.objects.tuple.PTuple;
44+
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes;
4645
import com.oracle.graal.python.nodes.PGuards;
4746
import com.oracle.graal.python.nodes.PNodeWithContext;
47+
import com.oracle.graal.python.nodes.builtins.TupleNodes;
4848
import com.oracle.graal.python.runtime.ExecutionContext.BoundaryCallContext;
4949
import com.oracle.graal.python.runtime.IndirectCallData.BoundaryCallData;
5050
import com.oracle.graal.python.runtime.PythonOptions;
5151
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
5252
import com.oracle.truffle.api.dsl.Bind;
5353
import com.oracle.truffle.api.dsl.Cached;
54-
import com.oracle.truffle.api.dsl.Cached.Shared;
5554
import com.oracle.truffle.api.dsl.GenerateCached;
5655
import com.oracle.truffle.api.dsl.GenerateInline;
5756
import com.oracle.truffle.api.dsl.Idempotent;
@@ -60,8 +59,6 @@
6059
import com.oracle.truffle.api.dsl.Specialization;
6160
import com.oracle.truffle.api.frame.Frame;
6261
import com.oracle.truffle.api.frame.VirtualFrame;
63-
import com.oracle.truffle.api.nodes.ExplodeLoop;
64-
import com.oracle.truffle.api.nodes.ExplodeLoop.LoopExplosionKind;
6562
import com.oracle.truffle.api.nodes.Node;
6663
import com.oracle.truffle.api.nodes.UnadoptableNode;
6764

@@ -74,8 +71,6 @@
7471
@GenerateCached(false)
7572
@SuppressWarnings("truffle-neverdefault")
7673
abstract class PyObjectRecursiveBinaryCheckNode extends PNodeWithContext {
77-
static final int MAX_EXPLODE_LOOP = 16; // is also shifted to the left by recursion depth
78-
7974
public final boolean execute(Frame frame, Object arg, Object classinfo) {
8075
return executeInternal(frame, arg, classinfo, 0);
8176
}
@@ -87,78 +82,64 @@ public final boolean execute(Frame frame, Object arg, Object classinfo) {
8782

8883
abstract PyObjectRecursiveBinaryCheckNode getUncachedRecursive();
8984

90-
@Idempotent
91-
protected static int getMaxExplodeLoop(int depth) {
92-
return MAX_EXPLODE_LOOP >> depth;
93-
}
94-
95-
@Specialization(guards = {"depth < getNodeRecursionLimit(language)", "getLength(clsTuple) == cachedLen", "cachedLen < getMaxExplodeLoop(depth)"}, //
96-
limit = "getVariableArgumentInlineCacheLimit()", excludeForUncached = true)
97-
@ExplodeLoop(kind = LoopExplosionKind.FULL_UNROLL_UNTIL_RETURN)
98-
static boolean doTupleConstantLen(VirtualFrame frame, Object arg, PTuple clsTuple, int depth,
85+
@Specialization(guards = {"depth < getNodeRecursionLimit(language)", "tupleCheck.execute(inliningTarget, clsTuple)"}, limit = "1", excludeForUncached = true)
86+
static boolean doRecursiveWithNode(VirtualFrame frame, Object arg, Object clsTuple, int depth,
9987
@Bind Node inliningTarget,
10088
@SuppressWarnings("unused") @Bind PythonLanguage language,
101-
@Cached("getLength(clsTuple)") int cachedLen,
102-
@Shared @Cached GetObjectArrayNode getObjectArrayNode,
103-
@Shared @Cached("createRecursive()") PyObjectRecursiveBinaryCheckNode recursiveNode) {
104-
Object[] array = getObjectArrayNode.execute(inliningTarget, clsTuple);
105-
int newDepth = depth + 1;
106-
for (int i = 0; i < cachedLen; i++) {
107-
Object cls = array[i];
108-
if (recursiveNode.executeInternal(frame, arg, cls, newDepth)) {
109-
return true;
110-
}
111-
}
112-
return false;
113-
}
114-
115-
@Specialization(guards = "depth < getNodeRecursionLimit(language)", replaces = "doTupleConstantLen", excludeForUncached = true)
116-
static boolean doRecursiveWithNode(VirtualFrame frame, Object arg, PTuple clsTuple, int depth,
117-
@Bind Node inliningTarget,
118-
@SuppressWarnings("unused") @Bind PythonLanguage language,
119-
@Shared @Cached GetObjectArrayNode getObjectArrayNode,
120-
@Shared @Cached("createRecursive()") PyObjectRecursiveBinaryCheckNode recursiveNode) {
121-
return loopRecursive(frame, arg, clsTuple, inliningTarget, getObjectArrayNode, recursiveNode, depth + 1);
89+
@SuppressWarnings("unused") @Cached PyTupleCheckNode tupleCheck,
90+
@Cached TupleNodes.GetTupleStorage getTupleStorage,
91+
@Cached SequenceStorageNodes.ToArrayNode toArrayNode,
92+
@Cached("createRecursive()") PyObjectRecursiveBinaryCheckNode recursiveNode) {
93+
return loopRecursive(frame, arg, clsTuple, inliningTarget, getTupleStorage, toArrayNode, recursiveNode, depth + 1);
12294
}
12395

124-
@Specialization(guards = {"depth >= getNodeRecursionLimit(language)"}, excludeForUncached = true)
125-
boolean doRecursiveTransition(VirtualFrame frame, Object arg, PTuple clsTuple, @SuppressWarnings("unused") int depth,
96+
@SuppressWarnings("truffle-static-method")
97+
@Specialization(guards = {"depth >= getNodeRecursionLimit(language)", "tupleCheck.execute(inliningTarget, clsTuple)"}, limit = "1", excludeForUncached = true)
98+
boolean doRecursiveTransition(VirtualFrame frame, Object arg, Object clsTuple, @SuppressWarnings("unused") int depth,
12699
@Bind Node inliningTarget,
127100
@SuppressWarnings("unused") @Bind PythonLanguage language,
128101
@Cached("createFor($node)") BoundaryCallData boundaryCallData,
129-
@Shared @Cached GetObjectArrayNode getObjectArrayNode) {
102+
@SuppressWarnings("unused") @Cached PyTupleCheckNode tupleCheck,
103+
@Cached TupleNodes.GetTupleStorage getTupleStorage,
104+
@Cached SequenceStorageNodes.ToArrayNode toArrayNode) {
130105
Object state = BoundaryCallContext.enter(frame, boundaryCallData);
131106
try {
132107
// Note: we need actual recursion to trigger the stack overflow error like CPython.
133-
return callRecursiveWithNodeTruffleBoundary(inliningTarget, arg, clsTuple, getObjectArrayNode);
108+
return callRecursiveWithNodeTruffleBoundary(inliningTarget, arg, clsTuple, getTupleStorage, toArrayNode);
134109
} finally {
135110
BoundaryCallContext.exit(frame, boundaryCallData, state);
136111
}
137112
}
138113

139-
@Specialization
140-
boolean doRecursiveUncached(VirtualFrame frame, Object arg, PTuple clsTuple, @SuppressWarnings("unused") int depth) {
114+
@SuppressWarnings("truffle-static-method")
115+
@Specialization(guards = "tupleCheck.execute(inliningTarget, clsTuple)", limit = "1")
116+
boolean doRecursiveUncached(VirtualFrame frame, Object arg, Object clsTuple, @SuppressWarnings("unused") int depth,
117+
@Bind Node inliningTarget,
118+
@SuppressWarnings("unused") @Cached PyTupleCheckNode tupleCheck,
119+
@Cached TupleNodes.GetTupleStorage getTupleStorage,
120+
@Cached SequenceStorageNodes.ToArrayNode toArrayNode) {
141121
assert this instanceof UnadoptableNode;
142-
return loopRecursive(frame, arg, clsTuple, null, GetObjectArrayNode.getUncached(), this, -1);
122+
return loopRecursive(frame, arg, clsTuple, inliningTarget, getTupleStorage, toArrayNode, this, -1);
143123
}
144124

145125
@TruffleBoundary
146-
private boolean callRecursiveWithNodeTruffleBoundary(Node inliningTarget, Object arg, PTuple clsTuple, GetObjectArrayNode getObjectArrayNode) {
147-
return loopRecursive(null, arg, clsTuple, inliningTarget, getObjectArrayNode, getUncachedRecursive(), -1);
126+
private boolean callRecursiveWithNodeTruffleBoundary(Node inliningTarget, Object arg, Object clsTuple, TupleNodes.GetTupleStorage getTupleStorage,
127+
SequenceStorageNodes.ToArrayNode toArrayNode) {
128+
return loopRecursive(null, arg, clsTuple, inliningTarget, getTupleStorage, toArrayNode, getUncachedRecursive(), -1);
148129
}
149130

150-
private static boolean loopRecursive(VirtualFrame frame, Object arg, PTuple clsTuple, Node inliningTarget, GetObjectArrayNode getObjectArrayNode, PyObjectRecursiveBinaryCheckNode node,
151-
int depth) {
152-
for (Object cls : getObjectArrayNode.execute(inliningTarget, clsTuple)) {
131+
private static boolean loopRecursive(VirtualFrame frame, Object arg, Object clsTuple, Node inliningTarget, TupleNodes.GetTupleStorage getTupleStorage,
132+
SequenceStorageNodes.ToArrayNode toArrayNode, PyObjectRecursiveBinaryCheckNode node, int depth) {
133+
for (Object cls : getTupleArray(inliningTarget, clsTuple, getTupleStorage, toArrayNode)) {
153134
if (node.executeInternal(frame, arg, cls, depth)) {
154135
return true;
155136
}
156137
}
157138
return false;
158139
}
159140

160-
protected static int getLength(PTuple t) {
161-
return t.getSequenceStorage().length();
141+
private static Object[] getTupleArray(Node inliningTarget, Object clsTuple, TupleNodes.GetTupleStorage getTupleStorage, SequenceStorageNodes.ToArrayNode toArrayNode) {
142+
return toArrayNode.execute(inliningTarget, getTupleStorage.execute(inliningTarget, clsTuple));
162143
}
163144

164145
@Idempotent

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyTupleCheckExactNode.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -40,9 +40,13 @@
4040
*/
4141
package com.oracle.graal.python.lib;
4242

43+
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
44+
import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject;
4345
import com.oracle.graal.python.builtins.objects.tuple.PTuple;
4446
import com.oracle.graal.python.nodes.PGuards;
4547
import com.oracle.graal.python.nodes.PNodeWithContext;
48+
import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinObjectExactProfile;
49+
import com.oracle.truffle.api.dsl.Cached;
4650
import com.oracle.truffle.api.dsl.Fallback;
4751
import com.oracle.truffle.api.dsl.GenerateCached;
4852
import com.oracle.truffle.api.dsl.GenerateInline;
@@ -70,6 +74,12 @@ static boolean doBuiltinTuple(@SuppressWarnings("unused") PTuple tuple) {
7074
return true;
7175
}
7276

77+
@Specialization
78+
static boolean doNativeTuple(PythonAbstractNativeObject tuple,
79+
@Cached(inline = false) IsBuiltinObjectExactProfile check) {
80+
return check.profileObject(check, tuple, PythonBuiltinClassType.PTuple);
81+
}
82+
7383
@Fallback
7484
static boolean doOther(@SuppressWarnings("unused") Object object) {
7585
return false;

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/TupleNodes.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ SequenceStorage getNative(PythonAbstractNativeObject tuple,
154154
public abstract static class GetNativeTupleStorage extends Node {
155155
public abstract NativeObjectSequenceStorage execute(PythonAbstractNativeObject tuple);
156156

157+
public static GetNativeTupleStorage getUncached() {
158+
return TupleNodesFactory.GetNativeTupleStorageNodeGen.getUncached();
159+
}
160+
157161
@Specialization
158162
NativeObjectSequenceStorage getNative(PythonAbstractNativeObject tuple) {
159163
assert PyTupleCheckNode.executeUncached(tuple);

0 commit comments

Comments
 (0)