Skip to content

Commit 65d9cf2

Browse files
committed
Implement PyErr_Get/SetHandledException
1 parent 56e7e2a commit 65d9cf2

File tree

7 files changed

+132
-112
lines changed

7 files changed

+132
-112
lines changed

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2024, Oracle and/or its affiliates.
1+
/* Copyright (c) 2024, 2025, Oracle and/or its affiliates.
22
* Copyright (C) 1996-2024 Python Software Foundation
33
*
44
* Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
@@ -5555,10 +5555,12 @@ _PyObject_IsInstanceDictEmpty(PyObject *obj)
55555555
}
55565556
return ((PyDictObject *)dict)->ma_used == 0;
55575557
}
5558+
#endif // GraalPy change
55585559

55595560
void
55605561
_PyObject_FreeInstanceAttributes(PyObject *self)
55615562
{
5563+
#if 0 // GraalPy change
55625564
PyTypeObject *tp = Py_TYPE(self);
55635565
assert(Py_TYPE(self)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
55645566
PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(self);
@@ -5571,11 +5573,13 @@ _PyObject_FreeInstanceAttributes(PyObject *self)
55715573
Py_XDECREF(values->values[i]);
55725574
}
55735575
free_values(values);
5576+
#endif // GraalPy change
55745577
}
55755578

55765579
int
55775580
_PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg)
55785581
{
5582+
#if 0 // GraalPy change
55795583
PyTypeObject *tp = Py_TYPE(obj);
55805584
if((tp->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0) {
55815585
return 0;
@@ -5593,12 +5597,14 @@ _PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg)
55935597
PyObject *dict = _PyDictOrValues_GetDict(dorv);
55945598
Py_VISIT(dict);
55955599
}
5600+
#endif // GraalPy change
55965601
return 0;
55975602
}
55985603

55995604
void
56005605
_PyObject_ClearManagedDict(PyObject *obj)
56015606
{
5607+
#if 0 // GraalPy change
56025608
PyTypeObject *tp = Py_TYPE(obj);
56035609
if((tp->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0) {
56045610
return;
@@ -5620,8 +5626,8 @@ _PyObject_ClearManagedDict(PyObject *obj)
56205626
Py_DECREF(dict);
56215627
}
56225628
}
5623-
}
56245629
#endif // GraalPy change
5630+
}
56255631

56265632
PyObject *
56275633
PyObject_GenericGetDict(PyObject *obj, void *context)

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

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -597,38 +597,51 @@ _PyErr_GetExcInfo(PyThreadState *tstate,
597597
// GraalPy change: different implementation
598598
PyObject* result = GraalPyTruffleErr_GetExcInfo();
599599
if(result == NULL) {
600-
*p_type = NULL;
601-
*p_value = NULL;
602-
*p_traceback = NULL;
600+
*p_type = Py_NewRef(Py_None);
601+
*p_value = Py_NewRef(Py_None);
602+
*p_traceback = Py_NewRef(Py_None);
603603
} else {
604-
*p_type = Py_XNewRef(PyTuple_GetItem(result, 0));
605-
*p_value = Py_XNewRef(PyTuple_GetItem(result, 1));
606-
*p_traceback = Py_XNewRef(PyTuple_GetItem(result, 2));
604+
*p_type = Py_XNewRef(PyTuple_GetItem(result, 0));
605+
*p_value = Py_XNewRef(PyTuple_GetItem(result, 1));
606+
*p_traceback = Py_XNewRef(PyTuple_GetItem(result, 2));
607607
Py_DecRef(result);
608608
}
609609
}
610610

611611
#if 0 // GraalPy change
612+
PyObject*
613+
_PyErr_GetHandledException(PyThreadState *tstate)
614+
{
615+
_PyErr_StackItem *exc_info = _PyErr_GetTopmostException(tstate);
616+
PyObject *exc = exc_info->exc_value;
617+
if (exc == NULL || exc == Py_None) {
618+
return NULL;
619+
}
620+
return Py_NewRef(exc);
621+
}
622+
#endif // GraalPy change
623+
612624
PyObject*
613625
PyErr_GetHandledException(void)
614626
{
615627
PyThreadState *tstate = _PyThreadState_GET();
616628
return _PyErr_GetHandledException(tstate);
617629
}
618630

631+
#if 0 // GraalPy change
619632
void
620633
_PyErr_SetHandledException(PyThreadState *tstate, PyObject *exc)
621634
{
622635
Py_XSETREF(tstate->exc_info->exc_value, Py_XNewRef(exc));
623636
}
637+
#endif // GraalPy change
624638

625639
void
626640
PyErr_SetHandledException(PyObject *exc)
627641
{
628642
PyThreadState *tstate = _PyThreadState_GET();
629643
_PyErr_SetHandledException(tstate, exc);
630644
}
631-
#endif // GraalPy change
632645

633646
void
634647
PyErr_GetExcInfo(PyObject **p_type, PyObject **p_value, PyObject **p_traceback)
@@ -637,7 +650,6 @@ PyErr_GetExcInfo(PyObject **p_type, PyObject **p_value, PyObject **p_traceback)
637650
_PyErr_GetExcInfo(tstate, p_type, p_value, p_traceback);
638651
}
639652

640-
#if 0 // GraalPy change
641653
void
642654
PyErr_SetExcInfo(PyObject *type, PyObject *value, PyObject *traceback)
643655
{
@@ -649,6 +661,7 @@ PyErr_SetExcInfo(PyObject *type, PyObject *value, PyObject *traceback)
649661
}
650662

651663

664+
#if 0 // GraalPy change
652665
PyObject*
653666
_PyErr_StackItemToExcInfoTuple(_PyErr_StackItem *err_info)
654667
{

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

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
22
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
33
#
44
# The Universal Permissive License (UPL), Version 1.0
@@ -186,12 +186,12 @@ class Dummy:
186186
pass
187187

188188

189-
def raise_erorr():
189+
def raise_error():
190190
raise NameError
191191

192192

193193
try:
194-
raise_erorr()
194+
raise_error()
195195
except NameError as e:
196196
example_traceback = e.__traceback__
197197
else:
@@ -654,6 +654,78 @@ class TestPyErr(CPyExtTestCase):
654654
# )
655655

656656

657+
class TestCaughtException(unittest.TestCase):
658+
@classmethod
659+
def setUpClass(cls):
660+
cls.tester = CPyExtType(
661+
"CaughtExceptionTester",
662+
"""
663+
PyObject* TestPyErr_GetHandledException(PyObject* self) {
664+
PyObject* result = PyErr_GetHandledException();
665+
if (result == NULL)
666+
Py_RETURN_NONE;
667+
return result;
668+
}
669+
PyObject* TestPyErr_GetExcInfo(PyObject* self) {
670+
PyObject* typ;
671+
PyObject* val;
672+
PyObject* tb;
673+
PyErr_GetExcInfo(&typ, &val, &tb);
674+
return Py_BuildValue("OOO", typ, val, tb);
675+
}
676+
PyObject* TestPyErr_SetHandledException(PyObject* self, PyObject* arg) {
677+
PyErr_SetHandledException(arg != Py_None? arg : NULL);
678+
return TestPyErr_GetHandledException(NULL);
679+
}
680+
PyObject* TestPyErr_SetExcInfo(PyObject* self, PyObject* arg) {
681+
PyErr_SetExcInfo(Py_NewRef(Py_None), Py_NewRef(arg), Py_NewRef(Py_None));
682+
return TestPyErr_GetHandledException(NULL);
683+
}
684+
""",
685+
tp_methods='''
686+
{"PyErr_GetHandledException", (PyCFunction)TestPyErr_GetHandledException, METH_NOARGS | METH_STATIC, ""},
687+
{"PyErr_GetExcInfo", (PyCFunction)TestPyErr_GetExcInfo, METH_NOARGS | METH_STATIC, ""},
688+
{"PyErr_SetHandledException", (PyCFunction)TestPyErr_SetHandledException, METH_O | METH_STATIC, ""},
689+
{"PyErr_SetExcInfo", (PyCFunction)TestPyErr_SetExcInfo, METH_O | METH_STATIC, ""}
690+
'''
691+
)
692+
693+
def test_PyErr_GetHandledException(self):
694+
try:
695+
raise IndexError
696+
except IndexError as e:
697+
self.assertIs(self.tester.PyErr_GetHandledException(), e)
698+
# do a second time because this time we won't do a stack walk
699+
self.assertIs(self.tester.PyErr_GetHandledException(), e)
700+
701+
def test_PyErr_GetHandledException_no_exception(self):
702+
self.assertIsNone(self.tester.PyErr_GetHandledException())
703+
self.assertIsNone(self.tester.PyErr_GetHandledException())
704+
705+
def test_PyErr_GetExcInfo(self):
706+
try:
707+
raise IndexError
708+
except IndexError as e:
709+
self.assertEqual(self.tester.PyErr_GetExcInfo(), (type(e), e, e.__traceback__))
710+
# do a second time because this time we won't do a stack walk
711+
self.assertEqual(self.tester.PyErr_GetExcInfo(), (type(e), e, e.__traceback__))
712+
713+
def test_PyErr_GetExcInfo_no_exception(self):
714+
self.assertEqual(self.tester.PyErr_GetExcInfo(), (None, None, None))
715+
self.assertEqual(self.tester.PyErr_GetExcInfo(), (None, None, None))
716+
717+
def test_TestPyErr_SetHandledException(self):
718+
e = IndexError()
719+
# TODO on GraalPy the exception doesn't propagate to the function frame, so we can't use sys.exc_info() here
720+
self.assertIs(self.tester.PyErr_SetHandledException(e), e)
721+
self.assertIsNone(self.tester.PyErr_SetHandledException(None))
722+
723+
def test_TestPyErr_SetExcInfo(self):
724+
e = IndexError()
725+
self.assertIs(self.tester.PyErr_SetExcInfo(e), e)
726+
self.assertIsNone(self.tester.PyErr_SetExcInfo(None))
727+
728+
657729
def raise_native_exception():
658730
raise ExceptionSubclass(1)
659731

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

Lines changed: 1 addition & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
22
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
33
#
44
# The Universal Permissive License (UPL), Version 1.0
@@ -49,86 +49,6 @@
4949
exception_with_traceback = e
5050

5151

52-
class TestExceptionobject(unittest.TestCase):
53-
def test_exc_info(self):
54-
TestExcInfo = CPyExtType("TestExcInfo",
55-
"""
56-
PyObject* get_exc_info(PyObject* self) {
57-
PyObject* typ;
58-
PyObject* val;
59-
PyObject* tb;
60-
PyErr_GetExcInfo(&typ, &val, &tb);
61-
Py_XDECREF(val);
62-
Py_XDECREF(tb);
63-
if (typ == NULL) {
64-
Py_RETURN_NONE;
65-
}
66-
return typ;
67-
}
68-
""",
69-
tp_methods='{"get_exc_info", (PyCFunction)get_exc_info, METH_NOARGS, ""}'
70-
)
71-
tester = TestExcInfo()
72-
try:
73-
raise IndexError
74-
except IndexError:
75-
exc_type = tester.get_exc_info()
76-
assert exc_type == IndexError
77-
78-
# do a second time because this time we won't do a stack walk
79-
# disabled due to GR-34711
80-
# exc_type = tester.get_exc_info()
81-
# assert exc_type == IndexError
82-
else:
83-
assert False
84-
85-
def test_set_exc_info(self):
86-
TestSetExcInfo = CPyExtType("TestSetExcInfo",
87-
"""
88-
PyObject* set_exc_info(PyObject* self, PyObject* args) {
89-
PyObject* typ = PyTuple_GetItem(args, 0);
90-
PyObject* val = PyTuple_GetItem(args, 1);
91-
PyObject* tb = PyTuple_GetItem(args, 2);
92-
PyObject* typ1 = NULL;
93-
PyObject* val1 = NULL;
94-
PyObject* tb1 = NULL;
95-
96-
Py_XINCREF(typ);
97-
Py_XINCREF(val);
98-
Py_XINCREF(tb);
99-
PyErr_SetExcInfo(typ, val, tb);
100-
101-
PyErr_GetExcInfo(&typ1, &val1, &tb1);
102-
// ignore the traceback for now
103-
if(typ == typ1 && val == val1) {
104-
return Py_True;
105-
}
106-
return Py_False;
107-
}
108-
""",
109-
tp_methods='{"set_exc_info", (PyCFunction)set_exc_info, METH_O, ""}'
110-
)
111-
tester = TestSetExcInfo()
112-
try:
113-
raise IndexError
114-
except:
115-
typ, val, tb = sys.exc_info()
116-
assert typ == IndexError
117-
118-
119-
120-
# overwrite exception info
121-
expected = (ValueError, ValueError(), None)
122-
res = tester.set_exc_info(expected)
123-
assert res
124-
125-
# TODO uncomment once supported
126-
# actual = sys.exc_info()
127-
# assert actual == expected
128-
else:
129-
assert False
130-
131-
13252
def raise_exception_with_cause():
13353
try:
13454
raise RuntimeError()

0 commit comments

Comments
 (0)