Skip to content

Commit 566f6e1

Browse files
committed
Restore behavior of PyErr_Restore, and add tests.
1 parent fe6c34f commit 566f6e1

File tree

3 files changed

+137
-47
lines changed

3 files changed

+137
-47
lines changed

Lib/test/test_capi/test_misc.py

+39
Original file line numberDiff line numberDiff line change
@@ -1550,5 +1550,44 @@ def func2(x=None):
15501550
self.do_test(func2)
15511551

15521552

1553+
class Test_ErrSetAndRestore(unittest.TestCase):
1554+
1555+
def test_err_set_raised(self):
1556+
with self.assertRaises(ValueError):
1557+
_testcapi.err_set_raised(ValueError())
1558+
v = ValueError()
1559+
try:
1560+
_testcapi.err_set_raised(v)
1561+
except ValueError as ex:
1562+
self.assertIs(v, ex)
1563+
1564+
def test_err_restore(self):
1565+
with self.assertRaises(ValueError):
1566+
_testcapi.err_restore(ValueError)
1567+
with self.assertRaises(ValueError):
1568+
_testcapi.err_restore(ValueError, 1)
1569+
with self.assertRaises(ValueError):
1570+
_testcapi.err_restore(ValueError, 1, None)
1571+
with self.assertRaises(ValueError):
1572+
_testcapi.err_restore(ValueError, ValueError())
1573+
try:
1574+
_testcapi.err_restore(KeyError, "hi")
1575+
except KeyError as k:
1576+
self.assertEqual("hi", k.args[0])
1577+
try:
1578+
1/0
1579+
except Exception as e:
1580+
tb = e.__traceback__
1581+
with self.assertRaises(ValueError):
1582+
_testcapi.err_restore(ValueError, 1, tb)
1583+
with self.assertRaises(TypeError):
1584+
_testcapi.err_restore(ValueError, 1, 0)
1585+
try:
1586+
_testcapi.err_restore(ValueError, 1, tb)
1587+
except ValueError as v:
1588+
self.assertEqual(1, v.args[0])
1589+
self.assertIs(tb, v.__traceback__.tb_next)
1590+
1591+
15531592
if __name__ == "__main__":
15541593
unittest.main()

Modules/_testcapimodule.c

+38-1
Original file line numberDiff line numberDiff line change
@@ -2149,7 +2149,7 @@ dict_get_version(PyObject *self, PyObject *args)
21492149
return NULL;
21502150

21512151
_Py_COMP_DIAG_PUSH
2152-
_Py_COMP_DIAG_IGNORE_DEPR_DECLS
2152+
_Py_COMP_DIAG_IGNORE_DEPR_DECLS
21532153
version = dict->ma_version_tag;
21542154
_Py_COMP_DIAG_POP
21552155

@@ -3244,6 +3244,41 @@ function_set_kw_defaults(PyObject *self, PyObject *args)
32443244
Py_RETURN_NONE;
32453245
}
32463246

3247+
static PyObject *
3248+
err_set_raised(PyObject *self, PyObject *exc)
3249+
{
3250+
Py_INCREF(exc);
3251+
PyErr_SetRaisedException(exc);
3252+
assert(PyErr_Occurred());
3253+
return NULL;
3254+
}
3255+
3256+
static PyObject *
3257+
err_restore(PyObject *self, PyObject *args) {
3258+
PyObject *type = NULL, *value = NULL, *traceback = NULL;
3259+
switch(PyTuple_Size(args)) {
3260+
case 3:
3261+
traceback = PyTuple_GetItem(args, 2);
3262+
Py_INCREF(traceback);
3263+
/* fall through */
3264+
case 2:
3265+
value = PyTuple_GetItem(args, 1);
3266+
Py_INCREF(value);
3267+
/* fall through */
3268+
case 1:
3269+
type = PyTuple_GetItem(args, 0);
3270+
Py_INCREF(type);
3271+
break;
3272+
default:
3273+
PyErr_SetString(PyExc_TypeError,
3274+
"wrong number of arguments");
3275+
return NULL;
3276+
}
3277+
PyErr_Restore(type, value, traceback);
3278+
assert(PyErr_Occurred());
3279+
return NULL;
3280+
}
3281+
32473282
static PyObject *test_buildvalue_issue38913(PyObject *, PyObject *);
32483283

32493284
static PyMethodDef TestMethods[] = {
@@ -3392,6 +3427,8 @@ static PyMethodDef TestMethods[] = {
33923427
{"function_set_defaults", function_set_defaults, METH_VARARGS, NULL},
33933428
{"function_get_kw_defaults", function_get_kw_defaults, METH_O, NULL},
33943429
{"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL},
3430+
{"err_set_raised", err_set_raised, METH_O, NULL},
3431+
{"err_restore", err_restore, METH_VARARGS, NULL},
33953432
{NULL, NULL} /* sentinel */
33963433
};
33973434

Python/errors.c

+60-46
Original file line numberDiff line numberDiff line change
@@ -35,35 +35,75 @@ _PyErr_SetRaisedException(PyThreadState *tstate, PyObject *exc)
3535
Py_XDECREF(old_exc);
3636
}
3737

38+
static PyObject*
39+
_PyErr_CreateException(PyObject *exception_type, PyObject *value)
40+
{
41+
PyObject *exc;
42+
43+
if (value == NULL || value == Py_None) {
44+
exc = _PyObject_CallNoArgs(exception_type);
45+
}
46+
else if (PyTuple_Check(value)) {
47+
exc = PyObject_Call(exception_type, value, NULL);
48+
}
49+
else {
50+
exc = PyObject_CallOneArg(exception_type, value);
51+
}
52+
53+
if (exc != NULL && !PyExceptionInstance_Check(exc)) {
54+
PyErr_Format(PyExc_TypeError,
55+
"calling %R should have returned an instance of "
56+
"BaseException, not %s",
57+
exception_type, Py_TYPE(exc)->tp_name);
58+
Py_CLEAR(exc);
59+
}
60+
61+
return exc;
62+
}
63+
3864
void
3965
_PyErr_Restore(PyThreadState *tstate, PyObject *type, PyObject *value,
4066
PyObject *traceback)
4167
{
42-
#ifdef Py_DEBUG
43-
/* Check that we are being passed a normalized exception.
44-
*
45-
* Exceptions are normalized if all NULL,
46-
* or if curexc_type = Py_TYPE(curexc_value) and
47-
* curexc_traceback = curexc_value->traceback
48-
* and both type and traceback are valid */
49-
if (value == NULL) {
50-
assert(type == NULL);
68+
if (type == NULL) {
69+
assert(value == NULL);
5170
assert(traceback == NULL);
71+
_PyErr_SetRaisedException(tstate, NULL);
72+
return;
5273
}
53-
else {
54-
assert(PyExceptionClass_Check(type));
55-
assert(type == (PyObject *)Py_TYPE(value));
74+
assert(PyExceptionClass_Check(type));
75+
if (value != NULL && type == (PyObject *)Py_TYPE(value)) {
76+
/* Already normalized */
5677
assert(((PyBaseExceptionObject *)value)->traceback != Py_None);
57-
assert(traceback == ((PyBaseExceptionObject *)value)->traceback ||
58-
(traceback == Py_None &&
59-
((PyBaseExceptionObject *)value)->traceback == NULL)
60-
);
6178
}
62-
#endif
63-
79+
else {
80+
PyObject *exc = _PyErr_CreateException(type, value);
81+
Py_XDECREF(value);
82+
if (exc == NULL) {
83+
Py_DECREF(type);
84+
Py_XDECREF(traceback);
85+
return;
86+
}
87+
value = exc;
88+
}
89+
assert(PyExceptionInstance_Check(value));
90+
if (traceback != NULL && !PyTraceBack_Check(traceback)) {
91+
if (traceback == Py_None) {
92+
Py_DECREF(Py_None);
93+
traceback = NULL;
94+
}
95+
else {
96+
PyErr_SetString(PyExc_TypeError, "traceback must be a Traceback or None");
97+
Py_DECREF(type);
98+
Py_XDECREF(traceback);
99+
return;
100+
}
101+
}
102+
PyObject *old_traceback = ((PyBaseExceptionObject *)value)->traceback;
103+
((PyBaseExceptionObject *)value)->traceback = traceback;
104+
Py_XDECREF(old_traceback);
64105
_PyErr_SetRaisedException(tstate, value);
65-
Py_XDECREF(type);
66-
Py_XDECREF(traceback);
106+
Py_DECREF(type);
67107
}
68108

69109
void
@@ -94,32 +134,6 @@ _PyErr_GetTopmostException(PyThreadState *tstate)
94134
return exc_info;
95135
}
96136

97-
static PyObject*
98-
_PyErr_CreateException(PyObject *exception_type, PyObject *value)
99-
{
100-
PyObject *exc;
101-
102-
if (value == NULL || value == Py_None) {
103-
exc = _PyObject_CallNoArgs(exception_type);
104-
}
105-
else if (PyTuple_Check(value)) {
106-
exc = PyObject_Call(exception_type, value, NULL);
107-
}
108-
else {
109-
exc = PyObject_CallOneArg(exception_type, value);
110-
}
111-
112-
if (exc != NULL && !PyExceptionInstance_Check(exc)) {
113-
PyErr_Format(PyExc_TypeError,
114-
"calling %R should have returned an instance of "
115-
"BaseException, not %s",
116-
exception_type, Py_TYPE(exc)->tp_name);
117-
Py_CLEAR(exc);
118-
}
119-
120-
return exc;
121-
}
122-
123137
void
124138
_PyErr_SetObject(PyThreadState *tstate, PyObject *exception, PyObject *value)
125139
{

0 commit comments

Comments
 (0)