Skip to content

Commit a418ff0

Browse files
committed
Fix invalid reference counting for METH_FASTCALL arg arrays
1 parent 4b4b7f9 commit a418ff0

File tree

4 files changed

+233
-9
lines changed

4 files changed

+233
-9
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1986,7 +1986,7 @@ public abstract static class FastCallArgsToSulongNode extends ConvertArgsToSulon
19861986
static void doFastcallCached(Object[] args, int argsOffset, Object[] dest, int destOffset,
19871987
@Cached ToBorrowedRefNode toSulongNode1) {
19881988
dest[destOffset + 0] = toSulongNode1.execute(args[argsOffset]);
1989-
dest[destOffset + 1] = new PySequenceArrayWrapper(args[argsOffset + 1], Long.BYTES);
1989+
dest[destOffset + 1] = args[argsOffset + 1];
19901990
dest[destOffset + 2] = args[argsOffset + 2];
19911991
}
19921992

@@ -2012,7 +2012,7 @@ static void doFastcallCached(Object[] args, int argsOffset, Object[] dest, int d
20122012
@Cached ToBorrowedRefNode toSulongNode1,
20132013
@Cached ToBorrowedRefNode toSulongNode4) {
20142014
dest[destOffset + 0] = toSulongNode1.execute(args[argsOffset]);
2015-
dest[destOffset + 1] = new PySequenceArrayWrapper(args[argsOffset + 1], Long.BYTES);
2015+
dest[destOffset + 1] = args[argsOffset + 1];
20162016
dest[destOffset + 2] = args[argsOffset + 2];
20172017
dest[destOffset + 3] = toSulongNode4.execute(args[argsOffset + 3]);
20182018
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
/*
2+
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package com.oracle.graal.python.builtins.objects.cext.capi;
42+
43+
import com.oracle.graal.python.builtins.objects.cext.capi.CApiContext.LLVMType;
44+
import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes.GetLLVMType;
45+
import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes.IsPointerNode;
46+
import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes.ReleaseNativeWrapperNode;
47+
import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes.ToNewRefNode;
48+
import com.oracle.graal.python.builtins.objects.ints.PInt;
49+
import com.oracle.graal.python.nodes.ErrorMessages;
50+
import com.oracle.graal.python.runtime.PythonContext;
51+
import com.oracle.graal.python.util.OverflowException;
52+
import com.oracle.graal.python.util.PythonUtils;
53+
import com.oracle.truffle.api.CompilerDirectives;
54+
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
55+
import com.oracle.truffle.api.dsl.Cached;
56+
import com.oracle.truffle.api.dsl.Cached.Shared;
57+
import com.oracle.truffle.api.interop.InteropLibrary;
58+
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
59+
import com.oracle.truffle.api.interop.UnsupportedMessageException;
60+
import com.oracle.truffle.api.library.CachedLibrary;
61+
import com.oracle.truffle.api.library.ExportLibrary;
62+
import com.oracle.truffle.api.library.ExportMessage;
63+
import com.oracle.truffle.llvm.spi.NativeTypeLibrary;
64+
65+
import sun.misc.Unsafe;
66+
67+
/**
68+
* A native wrapper for Python object arrays to be used like a {@code PyObject *arr[]}.
69+
*/
70+
@ExportLibrary(InteropLibrary.class)
71+
@ExportLibrary(value = NativeTypeLibrary.class, useForAOT = false)
72+
public final class CPyObjectArrayWrapper extends PythonNativeWrapper {
73+
74+
private static final Unsafe UNSAFE = PythonUtils.initUnsafe();
75+
76+
@TruffleBoundary
77+
private static long allocateBoundary(long size) {
78+
return UNSAFE.allocateMemory(size);
79+
}
80+
81+
@TruffleBoundary
82+
private static void freeBoundary(long ptr) {
83+
UNSAFE.freeMemory(ptr);
84+
}
85+
86+
private final Object[] wrappers;
87+
88+
public CPyObjectArrayWrapper(Object[] delegate) {
89+
super(delegate);
90+
wrappers = new Object[delegate.length];
91+
}
92+
93+
public Object[] getObjectArray(PythonNativeWrapperLibrary lib) {
94+
return ((Object[]) lib.getDelegate(this));
95+
}
96+
97+
@ExportMessage
98+
boolean isPointer(
99+
@Cached IsPointerNode pIsPointerNode) {
100+
return pIsPointerNode.execute(this);
101+
}
102+
103+
@ExportMessage
104+
long asPointer(
105+
@CachedLibrary("this") PythonNativeWrapperLibrary lib) throws UnsupportedMessageException {
106+
Object nativePointer = lib.getNativePointer(this);
107+
assert nativePointer instanceof Long;
108+
return (long) nativePointer;
109+
}
110+
111+
@ExportMessage
112+
long getArraySize() {
113+
return wrappers.length;
114+
}
115+
116+
@ExportMessage
117+
@SuppressWarnings("static-method")
118+
boolean hasArrayElements() {
119+
return true;
120+
}
121+
122+
@ExportMessage
123+
Object readArrayElement(long index,
124+
@CachedLibrary("this") PythonNativeWrapperLibrary lib,
125+
@Shared("toNewRefNode") @Cached ToNewRefNode toNewRefNode) throws InvalidArrayIndexException {
126+
try {
127+
int idx = PInt.intValueExact(index);
128+
if (idx >= 0 && idx < wrappers.length) {
129+
if (wrappers[idx] == null) {
130+
Object[] arr = getObjectArray(lib);
131+
wrappers[idx] = toNewRefNode.execute(arr[idx]);
132+
}
133+
return wrappers[idx];
134+
}
135+
} catch (OverflowException e) {
136+
// fall through
137+
}
138+
CompilerDirectives.transferToInterpreterAndInvalidate();
139+
throw InvalidArrayIndexException.create(index);
140+
}
141+
142+
@ExportMessage
143+
boolean isArrayElementReadable(long identifier) {
144+
return 0 <= identifier && identifier < wrappers.length;
145+
}
146+
147+
@ExportMessage
148+
@SuppressWarnings("static-method")
149+
boolean hasNativeType() {
150+
return true;
151+
}
152+
153+
@ExportMessage
154+
@SuppressWarnings("static-method")
155+
Object getNativeType(
156+
@Cached GetLLVMType getLLVMType) {
157+
return getLLVMType.execute(LLVMType.PyObject_ptr_ptr_t);
158+
}
159+
160+
/**
161+
* Copies a Java {@code Object[]} to a native {@code PyObject *arr[]}. For this, the native
162+
* memory is allocated off-heap using {@code Unsafe}.
163+
*/
164+
@ExportMessage
165+
void toNative(
166+
@CachedLibrary("this") PythonNativeWrapperLibrary lib,
167+
@Cached InvalidateNativeObjectsAllManagedNode invalidateNode,
168+
@Shared("toNewRefNode") @Cached ToNewRefNode toNewRefNode,
169+
@CachedLibrary(limit = "3") InteropLibrary interopLib) {
170+
if (!PythonContext.get(lib).isNativeAccessAllowed()) {
171+
CompilerDirectives.transferToInterpreterAndInvalidate();
172+
throw new RuntimeException(ErrorMessages.NATIVE_ACCESS_NOT_ALLOWED);
173+
}
174+
invalidateNode.execute();
175+
if (!lib.isNative(this)) {
176+
Object[] data = getObjectArray(lib);
177+
long ptr = allocateBoundary((long) wrappers.length * Long.BYTES);
178+
try {
179+
for (int i = 0; i < data.length; i++) {
180+
if (wrappers[i] == null) {
181+
wrappers[i] = toNewRefNode.execute(data[i]);
182+
}
183+
// we need a pointer, so manually send toNative
184+
interopLib.toNative(wrappers[i]);
185+
UNSAFE.putLong(ptr + (long) i * Long.BYTES, interopLib.asPointer(wrappers[i]));
186+
}
187+
} catch (UnsupportedMessageException e) {
188+
throw CompilerDirectives.shouldNotReachHere();
189+
}
190+
setNativePointer(ptr);
191+
}
192+
}
193+
194+
public void free(PythonNativeWrapperLibrary lib, ReleaseNativeWrapperNode releaseNativeWrapperNode) {
195+
if (!PythonContext.get(lib).isNativeAccessAllowed()) {
196+
CompilerDirectives.transferToInterpreterAndInvalidate();
197+
throw new RuntimeException(ErrorMessages.NATIVE_ACCESS_NOT_ALLOWED);
198+
}
199+
for (int i = 0; i < wrappers.length; i++) {
200+
releaseNativeWrapperNode.execute(wrappers[i]);
201+
}
202+
if (lib.isNative(this)) {
203+
freeBoundary((long) lib.getNativePointer(this));
204+
}
205+
}
206+
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/ExternalFunctionNodes.java

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,7 @@ public static final class MethFastcallWithKeywordsRoot extends MethodDescriptorR
901901
@Child private PythonObjectFactory factory;
902902
@Child private ReadVarArgsNode readVarargsNode;
903903
@Child private ReadVarKeywordsNode readKwargsNode;
904+
@Child private PythonNativeWrapperLibrary wrapperLib;
904905

905906
public MethFastcallWithKeywordsRoot(PythonLanguage language, String name, boolean isStatic) {
906907
super(language, name, isStatic);
@@ -925,17 +926,26 @@ protected Object[] prepareCArguments(VirtualFrame frame) {
925926
fastcallKwnames[i] = kwargs[i].getName();
926927
fastcallArgs[args.length + i] = kwargs[i].getValue();
927928
}
928-
return new Object[]{self, factory.createTuple(fastcallArgs), args.length, factory.createTuple(fastcallKwnames)};
929+
return new Object[]{self, new CPyObjectArrayWrapper(fastcallArgs), args.length, factory.createTuple(fastcallKwnames)};
929930
}
930931

931932
@Override
932933
protected void postprocessCArguments(VirtualFrame frame, Object[] cArguments) {
933934
ReleaseNativeWrapperNode releaseNativeWrapperNode = ensureReleaseNativeWrapperNode();
934935
releaseNativeWrapperNode.execute(cArguments[0]);
935-
releaseNativeWrapperNode.execute(cArguments[1]);
936+
CPyObjectArrayWrapper wrapper = (CPyObjectArrayWrapper) cArguments[1];
937+
wrapper.free(ensureWrapperLib(wrapper), ensureReleaseNativeWrapperNode());
936938
releaseNativeWrapperNode.execute(cArguments[3]);
937939
}
938940

941+
private PythonNativeWrapperLibrary ensureWrapperLib(CPyObjectArrayWrapper wrapper) {
942+
if (wrapperLib == null) {
943+
CompilerDirectives.transferToInterpreterAndInvalidate();
944+
wrapperLib = insert(PythonNativeWrapperLibrary.getFactory().create(wrapper));
945+
}
946+
return wrapperLib;
947+
}
948+
939949
@Override
940950
public Signature getSignature() {
941951
return SIGNATURE;
@@ -944,31 +954,39 @@ public Signature getSignature() {
944954

945955
public static final class MethFastcallRoot extends MethodDescriptorRoot {
946956
private static final Signature SIGNATURE = new Signature(-1, false, 1, false, new String[]{"self"}, KEYWORDS_HIDDEN_CALLABLE, true);
947-
@Child private PythonObjectFactory factory;
948957
@Child private ReadVarArgsNode readVarargsNode;
958+
@Child private PythonNativeWrapperLibrary wrapperLib;
949959

950960
public MethFastcallRoot(PythonLanguage language, String name, boolean isStatic) {
951961
super(language, name, isStatic);
952962
}
953963

954964
public MethFastcallRoot(PythonLanguage language, String name, boolean isStatic, PExternalFunctionWrapper provider) {
955965
super(language, name, isStatic, provider);
956-
this.factory = PythonObjectFactory.create();
957966
this.readVarargsNode = ReadVarArgsNode.create(true);
958967
}
959968

960969
@Override
961970
protected Object[] prepareCArguments(VirtualFrame frame) {
962971
Object self = readSelf(frame);
963972
Object[] args = readVarargsNode.executeObjectArray(frame);
964-
return new Object[]{self, factory.createTuple(args), args.length};
973+
return new Object[]{self, new CPyObjectArrayWrapper(args), args.length};
965974
}
966975

967976
@Override
968977
protected void postprocessCArguments(VirtualFrame frame, Object[] cArguments) {
969978
ReleaseNativeWrapperNode releaseNativeWrapperNode = ensureReleaseNativeWrapperNode();
970979
releaseNativeWrapperNode.execute(cArguments[0]);
971-
releaseNativeWrapperNode.execute(cArguments[1]);
980+
CPyObjectArrayWrapper wrapper = (CPyObjectArrayWrapper) cArguments[1];
981+
wrapper.free(ensureWrapperLib(wrapper), ensureReleaseNativeWrapperNode());
982+
}
983+
984+
private PythonNativeWrapperLibrary ensureWrapperLib(CPyObjectArrayWrapper wrapper) {
985+
if (wrapperLib == null) {
986+
CompilerDirectives.transferToInterpreterAndInvalidate();
987+
wrapperLib = insert(PythonNativeWrapperLibrary.getFactory().create(wrapper));
988+
}
989+
return wrapperLib;
972990
}
973991

974992
@Override

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,7 @@ public abstract class ErrorMessages {
732732
public static final String ALTERNATE_NOT_ALLOWED_WITH_C_FOR_INT = "Alternate form (#) not allowed with integer format specifier 'c'";
733733
public static final String ALTERNATE_NOT_ALLOWED_WITH_STRING_FMT = "Alternate form (#) not allowed in string format specifier";
734734
public static final String CAPI_LOAD_ERROR = "Could not load C API from %s.";
735-
public static final String NATIVE_ACCESS_NOT_ALLOWED = "Cannot run any C extensions because native access is not allowed.";
735+
public static final String NATIVE_ACCESS_NOT_ALLOWED = "Attempted to do native access but is not allowed.";
736736
public static final String HPY_LOAD_ERROR = "Could not load HPy C API from %s.";
737737
public static final String CANNOT_CONVERT_NEGATIVE_VALUE_TO_UNSIGNED_INT = "can't convert negative value to unsigned int";
738738
public static final String SEND_NON_NONE_TO_UNSTARTED_GENERATOR = "can't send non-None value to a just-started generator";

0 commit comments

Comments
 (0)