Skip to content

Commit 995762b

Browse files
committed
[GR-46818][GR-46811] Implement native exception subclasses
PullRequest: graalpython/2853
2 parents 2a81839 + 325cdf6 commit 995762b

File tree

85 files changed

+3320
-2121
lines changed

Some content is hidden

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

85 files changed

+3320
-2121
lines changed

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,10 +424,19 @@ PyAPI_FUNC(PyTypeObject*) get_##NAME(RECEIVER obj) { \
424424
PyAPI_FUNC(PyObject*) get_##NAME(RECEIVER obj) { \
425425
return (PyObject*) obj->NAME; \
426426
}
427+
#define OBJECT_FIELD_SETTER(RECEIVER, NAME) \
428+
PyAPI_FUNC(void) set_##NAME(RECEIVER obj, PyObject* value) { \
429+
Py_XINCREF(value); \
430+
Py_XSETREF(obj->NAME, value); \
431+
}
427432
#define PRIMITIVE_FIELD_GETTER(RECEIVER, RESULT, NAME) \
428433
PyAPI_FUNC(RESULT) get_##NAME(RECEIVER obj) { \
429434
return obj->NAME; \
430435
}
436+
#define PRIMITIVE_FIELD_SETTER(RECEIVER, TYPE, NAME) \
437+
PyAPI_FUNC(void) set_##NAME(RECEIVER obj, TYPE value) { \
438+
obj->NAME = value; \
439+
}
431440
#define PRIMITIVE_SUBFIELD_GETTER(RECEIVER, FIELD, RESULT, NAME) \
432441
PyAPI_FUNC(RESULT) get_##NAME(RECEIVER obj) { \
433442
return obj->FIELD? obj->FIELD->NAME : NULL; \
@@ -522,6 +531,20 @@ PRIMITIVE_FIELD_GETTER(PyModuleDef*, Py_ssize_t, m_size)
522531
PRIMITIVE_FIELD_GETTER(PyModuleDef*, const char*, m_doc)
523532
PRIMITIVE_EMBEDDED_FIELD_GETTER(PyComplexObject*, cval, double, real)
524533
PRIMITIVE_EMBEDDED_FIELD_GETTER(PyComplexObject*, cval, double, imag)
534+
OBJECT_FIELD_GETTER(PyBaseExceptionObject*, args);
535+
OBJECT_FIELD_GETTER(PyBaseExceptionObject*, context);
536+
OBJECT_FIELD_GETTER(PyBaseExceptionObject*, cause);
537+
OBJECT_FIELD_GETTER(PyBaseExceptionObject*, traceback);
538+
PRIMITIVE_FIELD_GETTER(PyBaseExceptionObject*, char, suppress_context);
539+
OBJECT_FIELD_SETTER(PyBaseExceptionObject*, args);
540+
OBJECT_FIELD_SETTER(PyBaseExceptionObject*, context);
541+
PyAPI_FUNC(void) set_cause(PyBaseExceptionObject* obj, PyObject* value) {
542+
Py_XSETREF(obj->cause, value);
543+
obj->suppress_context = 1;
544+
}
545+
OBJECT_FIELD_SETTER(PyBaseExceptionObject*, traceback);
546+
PRIMITIVE_FIELD_SETTER(PyBaseExceptionObject*, char, suppress_context);
547+
525548

526549
char* get_ob_sval(PyObject* op) {
527550
return ((PyBytesObject *)(op))->ob_sval;
@@ -1529,6 +1552,14 @@ PyAPI_FUNC(PyCodeObject*) PyCode_NewEmpty(const char* a, const char* b, int c) {
15291552
PyAPI_FUNC(PyCodeObject*) PyCode_NewWithPosOnlyArgs(int a, int b, int c, int d, int e, int f, PyObject* g, PyObject* h, PyObject* i, PyObject* j, PyObject* k, PyObject* l, PyObject* m, PyObject* n, int o, PyObject* p) {
15301553
return GraalPyCode_NewWithPosOnlyArgs(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p);
15311554
}
1555+
#undef PyCodec_Decoder
1556+
PyAPI_FUNC(PyObject*) PyCodec_Decoder(const char* a) {
1557+
return GraalPyCodec_Decoder(truffleString(a));
1558+
}
1559+
#undef PyCodec_Encoder
1560+
PyAPI_FUNC(PyObject*) PyCodec_Encoder(const char* a) {
1561+
return GraalPyCodec_Encoder(truffleString(a));
1562+
}
15321563
#undef PyComplex_FromDoubles
15331564
PyAPI_FUNC(PyObject*) PyComplex_FromDoubles(double a, double b) {
15341565
return GraalPyComplex_FromDoubles(a, b);

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ typedef struct {
120120
BUILTIN(PyCode_New, PyCodeObject*, int, int, int, int, int, PyObject*, PyObject*, PyObject*, PyObject*, PyObject*, PyObject*, PyObject*, PyObject*, int, PyObject*) \
121121
BUILTIN(PyCode_NewEmpty, PyCodeObject*, const char*, const char*, int) \
122122
BUILTIN(PyCode_NewWithPosOnlyArgs, PyCodeObject*, int, int, int, int, int, int, PyObject*, PyObject*, PyObject*, PyObject*, PyObject*, PyObject*, PyObject*, PyObject*, int, PyObject*) \
123+
BUILTIN(PyCodec_Decoder, PyObject*, const char*) \
124+
BUILTIN(PyCodec_Encoder, PyObject*, const char*) \
123125
BUILTIN(PyComplex_FromDoubles, PyObject*, double, double) \
124126
BUILTIN(PyComplex_ImagAsDouble, double, PyObject*) \
125127
BUILTIN(PyComplex_RealAsDouble, double, PyObject*) \

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,17 @@ void initialize_exceptions() {
187187
PyExc_ResourceWarning = PY_EXCEPTION("ResourceWarning");
188188
}
189189

190+
PyObject* exception_subtype_new(PyTypeObject *type, PyObject *args) {
191+
PyBaseExceptionObject *self;
192+
193+
self = (PyBaseExceptionObject *)type->tp_alloc(type, 0);
194+
if (!self)
195+
return NULL;
196+
/* the dict is created on the fly in PyObject_GenericSetAttr */
197+
self->dict = NULL;
198+
self->traceback = self->cause = self->context = NULL;
199+
self->suppress_context = 0;
200+
self->args = args;
201+
Py_INCREF(args);
202+
return (PyObject *)self;
203+
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ PyObject* PyTuple_Pack(Py_ssize_t n, ...) {
6161
return result;
6262
}
6363

64+
PyObject* PyTruffle_Tuple_Alloc(PyTypeObject* cls, Py_ssize_t nitems);
65+
6466
POLYGLOT_DECLARE_TYPE(PyTupleObject);
6567
PyObject * tuple_subtype_new(PyTypeObject *type, PyObject *iterable) {
6668
PyTupleObject* newobj;
@@ -75,7 +77,10 @@ PyObject * tuple_subtype_new(PyTypeObject *type, PyObject *iterable) {
7577
assert(PyTuple_Check(tmp));
7678
n = PyTuple_GET_SIZE(tmp);
7779

78-
newobj = (PyTupleObject*) type->tp_alloc(type, n);
80+
/* GraalPy note: we cannot call type->tp_alloc here because managed subtypes don't inherit tp_alloc but get a generic one.
81+
* In CPython tuple uses the generic one to begin with, so they don't have this problem
82+
*/
83+
newobj = (PyTupleObject*) PyTruffle_Tuple_Alloc(type, n);
7984
if (newobj == NULL) {
8085
return NULL;
8186
}

graalpython/com.oracle.graal.python.jni/src/capi_forwards.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1937,14 +1937,18 @@ PyAPI_FUNC(PyObject*) PyCodec_BackslashReplaceErrors(PyObject* a) {
19371937
PyAPI_FUNC(PyObject*) PyCodec_Decode(PyObject* a, const char* b, const char* c) {
19381938
unimplemented("PyCodec_Decode"); exit(-1);
19391939
}
1940+
PyObject* (*__target__PyCodec_Decoder)(const char*) = NULL;
19401941
PyAPI_FUNC(PyObject*) PyCodec_Decoder(const char* a) {
1941-
unimplemented("PyCodec_Decoder"); exit(-1);
1942+
PyObject* result = (PyObject*) __target__PyCodec_Decoder(a);
1943+
return result;
19421944
}
19431945
PyAPI_FUNC(PyObject*) PyCodec_Encode(PyObject* a, const char* b, const char* c) {
19441946
unimplemented("PyCodec_Encode"); exit(-1);
19451947
}
1948+
PyObject* (*__target__PyCodec_Encoder)(const char*) = NULL;
19461949
PyAPI_FUNC(PyObject*) PyCodec_Encoder(const char* a) {
1947-
unimplemented("PyCodec_Encoder"); exit(-1);
1950+
PyObject* result = (PyObject*) __target__PyCodec_Encoder(a);
1951+
return result;
19481952
}
19491953
PyAPI_FUNC(PyObject*) PyCodec_IgnoreErrors(PyObject* a) {
19501954
unimplemented("PyCodec_IgnoreErrors"); exit(-1);
@@ -6260,6 +6264,8 @@ void initializeCAPIForwards(void* (*getAPI)(const char*)) {
62606264
__target__PyCapsule_New = getAPI("PyCapsule_New");
62616265
__target__PyCapsule_SetName = getAPI("PyCapsule_SetName");
62626266
__target__PyCode_NewEmpty = getAPI("PyCode_NewEmpty");
6267+
__target__PyCodec_Decoder = getAPI("PyCodec_Decoder");
6268+
__target__PyCodec_Encoder = getAPI("PyCodec_Encoder");
62636269
__target__PyContextVar_Get = getAPI("PyContextVar_Get");
62646270
__target__PyContextVar_New = getAPI("PyContextVar_New");
62656271
__target__PyDescrObject_GetName = getAPI("PyDescrObject_GetName");

graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/builtins/modules/ConversionNodeTests.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.junit.rules.ExpectedException;
4747

4848
import com.oracle.graal.python.PythonLanguage;
49+
import com.oracle.graal.python.builtins.objects.exception.PBaseException;
4950
import com.oracle.graal.python.builtins.objects.function.PArguments;
5051
import com.oracle.graal.python.builtins.objects.function.Signature;
5152
import com.oracle.graal.python.nodes.PRootNode;
@@ -109,7 +110,9 @@ public boolean isPythonInternal() {
109110
}
110111
} catch (PException e) {
111112
// materialize PException's error message since we are leaving Python
112-
e.setMessage(e.getUnreifiedException().getFormattedMessage());
113+
if (e.getUnreifiedException() instanceof PBaseException managedException) {
114+
e.setMessage(managedException.getFormattedMessage());
115+
}
113116
throw e;
114117
}
115118
}

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ def assert_raises(err, fn, *args, **kwargs):
5858
assert raised
5959

6060

61+
if sys.implementation.name == 'graalpy':
62+
is_native_object = getattr(__graalpython__, 'is_native_object', None)
63+
if not is_native_object:
64+
raise RuntimeError("Needs to be run with --python.EnableDebuggingBuiltins")
65+
else:
66+
def is_native_object(obj):
67+
return True
68+
69+
6170
def unhandled_error_compare(x, y):
6271
if (isinstance(x, BaseException) and isinstance(y, BaseException)):
6372
return type(x) == type(y)
@@ -591,7 +600,7 @@ def CPyExtType(name, code, **kwargs):
591600
{tp_getattro},
592601
{tp_setattro},
593602
{tp_as_buffer},
594-
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
603+
{tp_flags},
595604
"",
596605
{tp_traverse}, /* tp_traverse */
597606
{tp_clear}, /* tp_clear */
@@ -648,6 +657,7 @@ def CPyExtType(name, code, **kwargs):
648657
kwargs.setdefault("tp_new", "PyType_GenericNew")
649658
kwargs.setdefault("tp_alloc", "PyType_GenericAlloc")
650659
kwargs.setdefault("tp_free", "PyObject_Del")
660+
kwargs.setdefault("tp_flags", "Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE")
651661
kwargs.setdefault("cmembers", "")
652662
kwargs.setdefault("includes", "")
653663
kwargs.setdefault("struct_base", "PyObject_HEAD")

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@
3838
# SOFTWARE.
3939
import unittest
4040

41-
from . import CPyExtTestCase, CPyExtFunction, CPyExtFunctionOutVars, unhandled_error_compare, CPyExtType
41+
from . import CPyExtTestCase, CPyExtFunction, CPyExtFunctionOutVars, unhandled_error_compare, CPyExtType, \
42+
is_native_object
4243

4344
__dir__ = __file__.rpartition("/")[0]
4445

@@ -589,6 +590,7 @@ def test_create_from_buffer_exception(self):
589590
class TestNativeSubclass(unittest.TestCase):
590591
def test_builtins(self):
591592
b = BytesSubclass(b"hello")
593+
assert is_native_object(b)
592594
assert type(b) == BytesSubclass
593595
assert b
594596
assert not BytesSubclass(b'')
@@ -617,3 +619,9 @@ def test_builtins(self):
617619
assert b.replace(b'e', b'a') == b'hallo'
618620
assert b.upper() == b'HELLO'
619621
assert BytesSubclass(b'a,b').split(BytesSubclass(b',')) == [b'a', b'b']
622+
623+
def test_managed_subclass(self):
624+
class ManagedSubclass(BytesSubclass):
625+
pass
626+
627+
assert is_native_object(ManagedSubclass(b"hello"))
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Copyright (c) 2023, 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+
40+
import codecs
41+
42+
from tests.cpyext import CPyExtTestCase, CPyExtFunction, unhandled_error_compare
43+
44+
45+
class TestPyCodec(CPyExtTestCase):
46+
47+
def compile_module(self, name):
48+
type(self).mro()[1].__dict__["test_%s" % name].create_module(name)
49+
super().compile_module(name)
50+
51+
test_PyCodec_Encoder = CPyExtFunction(
52+
lambda args: codecs.lookup(args[0])[0],
53+
lambda: (
54+
("utf-8",),
55+
("ascii",),
56+
("this-doesn't-exist",),
57+
),
58+
resultspec="O",
59+
argspec='s',
60+
arguments=["char* encoding"],
61+
cmpfunc=unhandled_error_compare,
62+
)
63+
64+
test_PyCodec_Decoder = CPyExtFunction(
65+
lambda args: codecs.lookup(args[0])[1],
66+
lambda: (
67+
("utf-8",),
68+
("ascii",),
69+
("this-doesn't-exist",),
70+
),
71+
resultspec="O",
72+
argspec='s',
73+
arguments=["char* encoding"],
74+
cmpfunc=unhandled_error_compare,
75+
)

0 commit comments

Comments
 (0)