Skip to content

Commit 54f95d3

Browse files
committed
[GR-53302] Fixes for pybind11, part #1
PullRequest: graalpython/3287
2 parents 8e88879 + 75740fb commit 54f95d3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+841
-142
lines changed

graalpython/com.oracle.graal.python.cext/src/capi.c

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,10 +249,27 @@ PyAPI_FUNC(void) mmap_init_bufferprotocol(PyObject* mmap_type) {
249249
(getbufferproc)mmap_getbuffer,
250250
(releasebufferproc)NULL,
251251
};
252-
set_PyTypeObject_tp_as_buffer(mmap_type, &mmap_as_buffer);
252+
GraalPy_set_PyTypeObject_tp_as_buffer(mmap_type, &mmap_as_buffer);
253253
((PyTypeObject*) mmap_type)->tp_as_buffer = &mmap_as_buffer;
254254
}
255255

256+
static int cdata_getbuffer(PyObject* type, Py_buffer* view, int flags) {
257+
return GraalPyTruffleCData_NewGetBuffer(type, view, flags);
258+
}
259+
260+
static void cdata_releasebuffer(PyObject* obj, Py_buffer* view) {
261+
return GraalPyTruffleCData_ReleaseBuffer(obj, view);
262+
}
263+
264+
PyAPI_FUNC(void) PyTruffleCData_InitBufferProtocol(PyObject* type) {
265+
static PyBufferProcs cdata_as_buffer = {
266+
cdata_getbuffer,
267+
cdata_releasebuffer,
268+
};
269+
GraalPy_set_PyTypeObject_tp_as_buffer(type, &cdata_as_buffer);
270+
((PyTypeObject*) type)->tp_as_buffer = &cdata_as_buffer;
271+
}
272+
256273
struct _longobject* _Py_FalseStructReference;
257274
struct _longobject* _Py_TrueStructReference;
258275
PyObject* _Py_EllipsisObjectReference;

graalpython/com.oracle.graal.python.cext/src/capsule.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2021, 2023, Oracle and/or its affiliates.
1+
/* Copyright (c) 2021, 2024, Oracle and/or its affiliates.
22
* Copyright (C) 1996-2021 Python Software Foundation
33
*
44
* Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
@@ -14,3 +14,7 @@ PyAPI_FUNC(PyTypeObject*) getPyCapsuleTypeReference() {
1414
return &PyCapsule_Type;
1515
}
1616

17+
PyAPI_FUNC(void) PyTruffleCapsule_CallDestructor(PyObject* capsule, PyCapsule_Destructor destructor) {
18+
destructor(capsule);
19+
}
20+

graalpython/com.oracle.graal.python.test/src/tests/cpyext/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import os
4343
import shutil
4444
import sys
45+
import time
4546
import unittest
4647
from copy import deepcopy
4748
from io import StringIO
@@ -54,6 +55,7 @@
5455

5556
IS_MANAGED_LAUNCHER = not GRAALPYTHON or __graalpython__.is_managed_launcher()
5657

58+
5759
def assert_raises(err, fn, *args, **kwargs):
5860
raised = False
5961
try:

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_capsule.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,16 @@
3636
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3737
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3838
# SOFTWARE.
39+
import gc
40+
import os
41+
import time
3942

40-
from . import CPyExtTestCase, CPyExtFunction, unhandled_error_compare
43+
from . import CPyExtTestCase, CPyExtFunction, unhandled_error_compare, CPyExtType
4144

4245
__dir__ = __file__.rpartition("/")[0]
4346

4447

4548
class TestPyCapsule(CPyExtTestCase):
46-
4749
test_PyCapsule_CheckExact = CPyExtFunction(
4850
lambda args: True,
4951
lambda: (
@@ -112,3 +114,33 @@ class TestPyCapsule(CPyExtTestCase):
112114
callfunction="wrap_PyCapsule_SetContext",
113115
cmpfunc=unhandled_error_compare
114116
)
117+
118+
if os.environ.get('GRAALPYTEST_RUN_GC_TESTS'):
119+
def test_capsule_destructor(self):
120+
Tester = CPyExtType(
121+
"CapsuleDestructorTester",
122+
code="""
123+
static void capsule_destructor(PyObject* capsule) {
124+
PyObject* contents = (PyObject*) PyCapsule_GetPointer(capsule, "capsule");
125+
assert(PyDict_Check(contents));
126+
PyDict_SetItemString(contents, "destructor_was_here", Py_NewRef(Py_True));
127+
Py_DECREF(contents);
128+
}
129+
130+
static PyObject* create_capsule(PyObject* unused, PyObject* contents) {
131+
return PyCapsule_New(Py_NewRef(contents), "capsule", capsule_destructor);
132+
}
133+
""",
134+
tp_methods='{"create_capsule", (PyCFunction)create_capsule, METH_O | METH_STATIC, NULL}',
135+
)
136+
d = {}
137+
capsule = Tester.create_capsule(d)
138+
assert capsule
139+
assert not d
140+
del capsule
141+
start = time.time()
142+
while "destructor_was_here" not in d:
143+
if time.time() - start > 60:
144+
raise AssertionError("Capsule destructor didn't execute within timeout")
145+
gc.collect()
146+
time.sleep(0.01)

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_ceval.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,12 @@
3737
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3838
# SOFTWARE.
3939

40-
from . import CPyExtTestCase, CPyExtFunction, unhandled_error_compare
40+
from . import CPyExtTestCase, CPyExtFunction, unhandled_error_compare, CPyExtType
4141

4242
__dir__ = __file__.rpartition("/")[0]
4343

44-
class TestCeval(CPyExtTestCase):
45-
46-
def compile_module(self, name):
47-
type(self).mro()[1].__dict__["test_%s" % name].create_module(name)
48-
super(TestCeval, self).compile_module(name)
4944

45+
class TestCeval(CPyExtTestCase):
5046
test_Py_EnterLeaveRecursiveCall = CPyExtFunction(
5147
# We don't know the exact limit on CPython since it uses the counter in
5248
# the interpreter as well.
@@ -81,3 +77,15 @@ def compile_module(self, name):
8177
arguments=["int n", "char* where"],
8278
cmpfunc=unhandled_error_compare
8379
)
80+
81+
def test_PyEval_GetGlobals(self):
82+
Tester = CPyExtType(
83+
"GetGlobalsTester",
84+
code="""
85+
static PyObject* get_globals(PyObject* unused) {
86+
return Py_NewRef(PyEval_GetGlobals());
87+
}
88+
""",
89+
tp_methods='{"get_globals", (PyCFunction)get_globals, METH_NOARGS | METH_STATIC, NULL}',
90+
)
91+
assert Tester.get_globals() is globals()
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
2+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3+
#
4+
# The Universal Permissive License (UPL), Version 1.0
5+
#
6+
# Subject to the condition set forth below, permission is hereby granted to any
7+
# person obtaining a copy of this software, associated documentation and/or
8+
# data (collectively the "Software"), free of charge and under any and all
9+
# copyright rights in the Software, and any and all patent rights owned or
10+
# freely licensable by each licensor hereunder covering either (i) the
11+
# unmodified Software as contributed to or provided by such licensor, or (ii)
12+
# the Larger Works (as defined below), to deal in both
13+
#
14+
# (a) the Software, and
15+
#
16+
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
17+
# one is included with the Software each a "Larger Work" to which the Software
18+
# is contributed by such licensors),
19+
#
20+
# without restriction, including without limitation the rights to copy, create
21+
# derivative works of, display, perform, and distribute the Software and make,
22+
# use, sell, offer for sale, import, export, have made, and have sold the
23+
# Software and the Larger Work(s), and to sublicense the foregoing rights on
24+
# either these or other terms.
25+
#
26+
# This license is subject to the following condition:
27+
#
28+
# The above copyright notice and either this complete permission notice or at a
29+
# minimum a reference to the UPL must be included in all copies or substantial
30+
# portions of the Software.
31+
#
32+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38+
# SOFTWARE.
39+
import ctypes
40+
import struct
41+
import sys
42+
43+
from tests.cpyext import CPyExtTestCase, CPyExtType
44+
45+
BufferTester = CPyExtType(
46+
'BufferTester',
47+
cmembers='Py_buffer buffer;',
48+
code='''
49+
static PyObject* buffer_tester_new(PyTypeObject* type, PyObject* args, PyObject* kwargs) {
50+
PyObject* obj;
51+
if (PyArg_ParseTuple(args, "O", &obj) < 0)
52+
return NULL;
53+
PyObject* self = type->tp_alloc(type, 0);
54+
if (!self)
55+
return NULL;
56+
if (PyObject_GetBuffer(obj, &((BufferTesterObject*)self)->buffer, PyBUF_CONTIG_RO) < 0) {
57+
Py_DECREF(self);
58+
return NULL;
59+
}
60+
return self;
61+
}
62+
63+
static PyObject* buffer_tester_enter(PyObject* self) {
64+
return Py_NewRef(self);
65+
}
66+
67+
static PyObject* buffer_tester_exit(BufferTesterObject* self, PyObject* args) {
68+
PyBuffer_Release(&self->buffer);
69+
Py_RETURN_NONE;
70+
}
71+
72+
static PyObject* buffer_tester_obj(BufferTesterObject* self) {
73+
return Py_NewRef(self->buffer.obj);
74+
}
75+
76+
static PyObject* buffer_tester_bytes(BufferTesterObject* self) {
77+
return PyBytes_FromStringAndSize(self->buffer.buf, self->buffer.len);
78+
}
79+
80+
static PyObject* buffer_tester_itemsize(BufferTesterObject* self) {
81+
return PyLong_FromSsize_t(self->buffer.itemsize);
82+
}
83+
84+
static PyObject* buffer_tester_format(BufferTesterObject* self) {
85+
return PyBytes_FromString(self->buffer.format);
86+
}
87+
88+
static PyObject* buffer_tester_shape(BufferTesterObject* self) {
89+
PyObject* tuple = PyTuple_New(self->buffer.ndim);
90+
if (!tuple)
91+
return NULL;
92+
for (int i = 0; i < self->buffer.ndim; i++) {
93+
PyObject* value = PyLong_FromSsize_t(self->buffer.shape[i]);
94+
if (!value)
95+
return NULL;
96+
PyTuple_SET_ITEM(tuple, i, value);
97+
}
98+
return tuple;
99+
}
100+
''',
101+
tp_new='buffer_tester_new',
102+
tp_methods='''
103+
{"__enter__", (PyCFunction)buffer_tester_enter, METH_NOARGS, ""},
104+
{"__exit__", (PyCFunction)buffer_tester_exit, METH_VARARGS, ""}
105+
''',
106+
tp_getset='''
107+
{"obj", (getter)buffer_tester_obj, NULL, NULL, NULL},
108+
{"bytes", (getter)buffer_tester_bytes, NULL, NULL, NULL},
109+
{"itemsize", (getter)buffer_tester_itemsize, NULL, NULL, NULL},
110+
{"format", (getter)buffer_tester_format, NULL, NULL, NULL},
111+
{"shape", (getter)buffer_tester_shape, NULL, NULL, NULL}
112+
''',
113+
)
114+
115+
116+
class TestCDataBuffer(CPyExtTestCase):
117+
def test_buffer(self):
118+
if sys.implementation.name == 'graalpy' and __graalpython__.get_platform_id() == 'managed':
119+
# TODO we don't support converting ctypes arrays to native memory in managed
120+
return
121+
int_format = struct.Struct(">i")
122+
inner_type = ctypes.c_int.__ctype_be__ * 2
123+
outer_type = inner_type * 2
124+
array = outer_type(inner_type(1, 2), inner_type(3, 4))
125+
with BufferTester(array) as buffer:
126+
assert buffer.obj is array
127+
assert buffer.bytes == b''.join(int_format.pack(n) for n in [1, 2, 3, 4])
128+
assert buffer.itemsize == int_format.size
129+
assert buffer.format.startswith(b'>')
130+
assert struct.Struct(buffer.format).size == int_format.size
131+
assert buffer.shape == (2, 2)

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_pythonrun.py

Lines changed: 29 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -37,54 +37,42 @@
3737
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3838
# SOFTWARE.
3939

40-
import sys
41-
42-
from . import CPyExtTestCase, CPyExtFunction, unhandled_error_compare
40+
from . import CPyExtTestCase, CPyExtFunction, unhandled_error_compare, CPyExtType
4341

4442
__dir__ = __file__.rpartition("/")[0]
4543

4644

47-
# python_run_test_result = None
48-
49-
def _reference_run_string(args):
50-
if not isinstance(args[2], dict):
51-
if sys.version_info.minor >= 6:
52-
raise SystemError
53-
else:
54-
raise TypeError
55-
if not isinstance(args[3], dict):
56-
raise TypeError
57-
return None
58-
59-
60-
def _run_string_compare(x, y):
61-
res = unhandled_error_compare(x, y)
62-
if (isinstance(x, Exception)):
63-
return res
45+
class TestPythonRun(CPyExtTestCase):
6446

65-
global python_run_test_result
66-
pr = python_run_test_result
67-
res = res and pr == 42
68-
python_run_test_result = None
69-
if not res:
70-
assert False, "python_run_test_result is %s" % pr
71-
return res
47+
def test_PyRun_String(self):
48+
Tester = CPyExtType(
49+
"PyRunStringTester",
50+
code="""
51+
static PyObject* call_PyRun_String(PyObject* unused, PyObject* args) {
52+
int eval;
53+
char *string;
54+
PyObject *globals, *locals;
55+
if (PyArg_ParseTuple(args, "spOO", &string, &eval, &globals, &locals) < 0)
56+
return NULL;
57+
int start = eval ? Py_eval_input : Py_file_input;
58+
return PyRun_String(string, start, globals, locals);
59+
}
60+
""",
61+
tp_methods='{"call_PyRun_String", (PyCFunction)call_PyRun_String, METH_VARARGS | METH_STATIC}'
62+
)
63+
g = {}
64+
l = {}
65+
assert Tester.call_PyRun_String("a = 1", False, g, l) is None
66+
assert l.get('a') == 1
67+
assert 'a' not in g
68+
g = {}
69+
l = {}
7270

71+
assert Tester.call_PyRun_String("global a\na = 1", False, g, l) is None
72+
assert g.get('a') == 1
73+
assert 'a' not in l
7374

74-
class TestPythonRun(CPyExtTestCase):
75-
76-
test_PyRun_StringFlags = CPyExtFunction(
77-
_reference_run_string,
78-
lambda: (
79-
("globals().update({'python_run_test_result':42})", 256, globals(), locals(), 0),
80-
("globals().update({'python_run_test_result':42})", 256, 'globals()', locals(), 0),
81-
("globals().update({'python_run_test_result':42})", 256, globals(), 'locals()', 0),
82-
),
83-
resultspec="O",
84-
argspec='siOOk',
85-
arguments=["char* source", "int type", "PyObject* globals", "PyObject* locals", "PyCompilerFlags* ignored"],
86-
cmpfunc=unhandled_error_compare
87-
)
75+
assert Tester.call_PyRun_String("1 + a", True, {}, {'a': 2}) == 3
8876

8977
test_Py_CompileString = CPyExtFunction(
9078
lambda args: compile(

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,5 +875,11 @@ def test_encode(self):
875875
self.assertRaises(UnicodeEncodeError, codec.encode, '\xffff')
876876

877877

878+
class UTF32Test(unittest.TestCase):
879+
def test_utf32_surrogate_error(self):
880+
with self.assertRaisesRegex(UnicodeDecodeError, "codec can't decode bytes in position 4-7"):
881+
b'a\x00\x00\x00\x00\xd8\x00\x00z\x00\x00\x00'.decode('utf-32')
882+
883+
878884
if __name__ == '__main__':
879885
unittest.main()

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/MMapModuleBuiltins.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2019, 2024, 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
@@ -135,7 +135,7 @@ public MMapModuleBuiltins() {
135135
public void postInitialize(Python3Core core) {
136136
super.postInitialize(core);
137137
core.getContext().registerCApiHook(() -> {
138-
CExtNodes.PCallCapiFunction.getUncached().call(NativeCAPISymbol.FUN_MMAP_INIT_BUFFERPROTOCOL, PythonToNativeNode.executeUncached(PythonBuiltinClassType.PMMap));
138+
CExtNodes.PCallCapiFunction.callUncached(NativeCAPISymbol.FUN_MMAP_INIT_BUFFERPROTOCOL, PythonToNativeNode.executeUncached(PythonBuiltinClassType.PMMap));
139139
});
140140
}
141141

0 commit comments

Comments
 (0)