Skip to content

Commit 65e7c1f

Browse files
authored
bpo-46219, 46221: simplify except* implementation following exc_info changes. Move helpers to exceptions.c. Do not assume that exception groups are truthy. (GH-30289)
1 parent 8e75c6b commit 65e7c1f

File tree

9 files changed

+179
-149
lines changed

9 files changed

+179
-149
lines changed

.github/CODEOWNERS

+8
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ Python/ceval.c @markshannon
2121
Python/compile.c @markshannon
2222
Python/ast_opt.c @isidentical
2323

24+
# Exceptions
25+
Lib/traceback.py @iritkatriel
26+
Lib/test/test_except*.py @iritkatriel
27+
Lib/test/test_traceback.py @iritkatriel
28+
Objects/exceptions.c @iritkatriel
29+
Python/traceback.c @iritkatriel
30+
Python/pythonrun.c @iritkatriel
31+
2432
# Hashing
2533
**/*hashlib* @python/crypto-team @tiran
2634
**/*pyhash* @python/crypto-team @tiran

Doc/library/dis.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -911,8 +911,8 @@ All of the following opcodes use their arguments.
911911
Combines the raised and reraised exceptions list from TOS, into an exception
912912
group to propagate from a try-except* block. Uses the original exception
913913
group from TOS1 to reconstruct the structure of reraised exceptions. Pops
914-
two items from the stack and pushes 0 (for lasti, which is unused) followed
915-
by the exception to reraise or ``None`` if there isn't one.
914+
two items from the stack and pushes the exception to reraise or ``None``
915+
if there isn't one.
916916

917917
.. versionadded:: 3.11
918918

Include/internal/pycore_pyerrors.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@ PyAPI_FUNC(PyObject *) _PyExc_CreateExceptionGroup(
8787
const char *msg,
8888
PyObject *excs);
8989

90-
PyAPI_FUNC(PyObject *) _PyExc_ExceptionGroupProjection(
91-
PyObject *left,
92-
PyObject *right);
90+
PyAPI_FUNC(PyObject *) _PyExc_PrepReraiseStar(
91+
PyObject *orig,
92+
PyObject *excs);
9393

9494
PyAPI_FUNC(int) _PyErr_CheckSignalsTstate(PyThreadState *tstate);
9595

Lib/importlib/_bootstrap_external.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ def _write_atomic(path, data, mode=0o666):
375375
# Python 3.11a4 3467 (Change CALL_xxx opcodes)
376376
# Python 3.11a4 3468 (Add SEND opcode)
377377
# Python 3.11a4 3469 (bpo-45711: remove type, traceback from exc_info)
378+
# Python 3.11a4 3470 (bpo-46221: PREP_RERAISE_STAR no longer pushes lasti)
378379

379380
#
380381
# MAGIC must change whenever the bytecode emitted by the compiler may no
@@ -384,7 +385,7 @@ def _write_atomic(path, data, mode=0o666):
384385
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
385386
# in PC/launcher.c must also be updated.
386387

387-
MAGIC_NUMBER = (3469).to_bytes(2, 'little') + b'\r\n'
388+
MAGIC_NUMBER = (3470).to_bytes(2, 'little') + b'\r\n'
388389
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
389390

390391
_PYCACHE = '__pycache__'

Lib/test/test_except_star.py

+28
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,34 @@ def derive(self, excs):
952952
self.assertEqual(teg.code, 42)
953953
self.assertEqual(teg.exceptions[0].code, 101)
954954

955+
def test_falsy_exception_group_subclass(self):
956+
class FalsyEG(ExceptionGroup):
957+
def __bool__(self):
958+
return False
959+
960+
def derive(self, excs):
961+
return FalsyEG(self.message, excs)
962+
963+
try:
964+
try:
965+
raise FalsyEG("eg", [TypeError(1), ValueError(2)])
966+
except *TypeError as e:
967+
tes = e
968+
raise
969+
except *ValueError as e:
970+
ves = e
971+
pass
972+
except Exception as e:
973+
exc = e
974+
975+
for e in [tes, ves, exc]:
976+
self.assertFalse(e)
977+
self.assertIsInstance(e, FalsyEG)
978+
979+
self.assertExceptionIsLike(exc, FalsyEG("eg", [TypeError(1)]))
980+
self.assertExceptionIsLike(tes, FalsyEG("eg", [TypeError(1)]))
981+
self.assertExceptionIsLike(ves, FalsyEG("eg", [ValueError(2)]))
982+
955983

956984
class TestExceptStarCleanup(ExceptStarTest):
957985
def test_exc_info_restored(self):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:opcode:`PREP_RERAISE_STAR` no longer pushes ``lasti`` to the stack.

Objects/exceptions.c

+118-2
Original file line numberDiff line numberDiff line change
@@ -1207,8 +1207,8 @@ collect_exception_group_leaves(PyObject *exc, PyObject *leaves)
12071207
* of eg which contains all leaf exceptions that are contained
12081208
* in any exception group in keep.
12091209
*/
1210-
PyObject *
1211-
_PyExc_ExceptionGroupProjection(PyObject *eg, PyObject *keep)
1210+
static PyObject *
1211+
exception_group_projection(PyObject *eg, PyObject *keep)
12121212
{
12131213
assert(_PyBaseExceptionGroup_Check(eg));
12141214
assert(PyList_CheckExact(keep));
@@ -1245,6 +1245,122 @@ _PyExc_ExceptionGroupProjection(PyObject *eg, PyObject *keep)
12451245
return result;
12461246
}
12471247

1248+
static bool
1249+
is_same_exception_metadata(PyObject *exc1, PyObject *exc2)
1250+
{
1251+
assert(PyExceptionInstance_Check(exc1));
1252+
assert(PyExceptionInstance_Check(exc2));
1253+
1254+
PyBaseExceptionObject *e1 = (PyBaseExceptionObject *)exc1;
1255+
PyBaseExceptionObject *e2 = (PyBaseExceptionObject *)exc2;
1256+
1257+
return (e1->note == e2->note &&
1258+
e1->traceback == e2->traceback &&
1259+
e1->cause == e2->cause &&
1260+
e1->context == e2->context);
1261+
}
1262+
1263+
/*
1264+
This function is used by the interpreter to calculate
1265+
the exception group to be raised at the end of a
1266+
try-except* construct.
1267+
1268+
orig: the original except that was caught.
1269+
excs: a list of exceptions that were raised/reraised
1270+
in the except* clauses.
1271+
1272+
Calculates an exception group to raise. It contains
1273+
all exceptions in excs, where those that were reraised
1274+
have same nesting structure as in orig, and those that
1275+
were raised (if any) are added as siblings in a new EG.
1276+
1277+
Returns NULL and sets an exception on failure.
1278+
*/
1279+
PyObject *
1280+
_PyExc_PrepReraiseStar(PyObject *orig, PyObject *excs)
1281+
{
1282+
assert(PyExceptionInstance_Check(orig));
1283+
assert(PyList_Check(excs));
1284+
1285+
Py_ssize_t numexcs = PyList_GET_SIZE(excs);
1286+
1287+
if (numexcs == 0) {
1288+
return Py_NewRef(Py_None);
1289+
}
1290+
1291+
if (!_PyBaseExceptionGroup_Check(orig)) {
1292+
/* a naked exception was caught and wrapped. Only one except* clause
1293+
* could have executed,so there is at most one exception to raise.
1294+
*/
1295+
1296+
assert(numexcs == 1 || (numexcs == 2 && PyList_GET_ITEM(excs, 1) == Py_None));
1297+
1298+
PyObject *e = PyList_GET_ITEM(excs, 0);
1299+
assert(e != NULL);
1300+
return Py_NewRef(e);
1301+
}
1302+
1303+
PyObject *raised_list = PyList_New(0);
1304+
if (raised_list == NULL) {
1305+
return NULL;
1306+
}
1307+
PyObject* reraised_list = PyList_New(0);
1308+
if (reraised_list == NULL) {
1309+
Py_DECREF(raised_list);
1310+
return NULL;
1311+
}
1312+
1313+
/* Now we are holding refs to raised_list and reraised_list */
1314+
1315+
PyObject *result = NULL;
1316+
1317+
/* Split excs into raised and reraised by comparing metadata with orig */
1318+
for (Py_ssize_t i = 0; i < numexcs; i++) {
1319+
PyObject *e = PyList_GET_ITEM(excs, i);
1320+
assert(e != NULL);
1321+
if (Py_IsNone(e)) {
1322+
continue;
1323+
}
1324+
bool is_reraise = is_same_exception_metadata(e, orig);
1325+
PyObject *append_list = is_reraise ? reraised_list : raised_list;
1326+
if (PyList_Append(append_list, e) < 0) {
1327+
goto done;
1328+
}
1329+
}
1330+
1331+
PyObject *reraised_eg = exception_group_projection(orig, reraised_list);
1332+
if (reraised_eg == NULL) {
1333+
goto done;
1334+
}
1335+
1336+
if (!Py_IsNone(reraised_eg)) {
1337+
assert(is_same_exception_metadata(reraised_eg, orig));
1338+
}
1339+
Py_ssize_t num_raised = PyList_GET_SIZE(raised_list);
1340+
if (num_raised == 0) {
1341+
result = reraised_eg;
1342+
}
1343+
else if (num_raised > 0) {
1344+
int res = 0;
1345+
if (!Py_IsNone(reraised_eg)) {
1346+
res = PyList_Append(raised_list, reraised_eg);
1347+
}
1348+
Py_DECREF(reraised_eg);
1349+
if (res < 0) {
1350+
goto done;
1351+
}
1352+
result = _PyExc_CreateExceptionGroup("", raised_list);
1353+
if (result == NULL) {
1354+
goto done;
1355+
}
1356+
}
1357+
1358+
done:
1359+
Py_XDECREF(raised_list);
1360+
Py_XDECREF(reraised_list);
1361+
return result;
1362+
}
1363+
12481364
static PyMemberDef BaseExceptionGroup_members[] = {
12491365
{"message", T_OBJECT, offsetof(PyBaseExceptionGroupObject, msg), READONLY,
12501366
PyDoc_STR("exception message")},

Python/ceval.c

+1-132
Original file line numberDiff line numberDiff line change
@@ -1092,7 +1092,6 @@ match_class(PyThreadState *tstate, PyObject *subject, PyObject *type,
10921092

10931093

10941094
static int do_raise(PyThreadState *tstate, PyObject *exc, PyObject *cause);
1095-
static PyObject *do_reraise_star(PyObject *excs, PyObject *orig);
10961095
static int exception_group_match(
10971096
PyObject* exc_value, PyObject *match_type,
10981097
PyObject **match, PyObject **rest);
@@ -2777,16 +2776,14 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
27772776
assert(PyList_Check(excs));
27782777
PyObject *orig = POP();
27792778

2780-
PyObject *val = do_reraise_star(excs, orig);
2779+
PyObject *val = _PyExc_PrepReraiseStar(orig, excs);
27812780
Py_DECREF(excs);
27822781
Py_DECREF(orig);
27832782

27842783
if (val == NULL) {
27852784
goto error;
27862785
}
27872786

2788-
PyObject *lasti_unused = Py_NewRef(_PyLong_GetZero());
2789-
PUSH(lasti_unused);
27902787
PUSH(val);
27912788
DISPATCH();
27922789
}
@@ -6313,134 +6310,6 @@ exception_group_match(PyObject* exc_value, PyObject *match_type,
63136310
return 0;
63146311
}
63156312

6316-
/* Logic for the final raise/reraise of a try-except* contruct
6317-
(too complicated for inlining).
6318-
*/
6319-
6320-
static bool
6321-
is_same_exception_metadata(PyObject *exc1, PyObject *exc2)
6322-
{
6323-
assert(PyExceptionInstance_Check(exc1));
6324-
assert(PyExceptionInstance_Check(exc2));
6325-
6326-
PyObject *tb1 = PyException_GetTraceback(exc1);
6327-
PyObject *ctx1 = PyException_GetContext(exc1);
6328-
PyObject *cause1 = PyException_GetCause(exc1);
6329-
PyObject *tb2 = PyException_GetTraceback(exc2);
6330-
PyObject *ctx2 = PyException_GetContext(exc2);
6331-
PyObject *cause2 = PyException_GetCause(exc2);
6332-
6333-
bool result = (Py_Is(tb1, tb2) &&
6334-
Py_Is(ctx1, ctx2) &&
6335-
Py_Is(cause1, cause2));
6336-
6337-
Py_XDECREF(tb1);
6338-
Py_XDECREF(ctx1);
6339-
Py_XDECREF(cause1);
6340-
Py_XDECREF(tb2);
6341-
Py_XDECREF(ctx2);
6342-
Py_XDECREF(cause2);
6343-
return result;
6344-
}
6345-
6346-
/*
6347-
excs: a list of exceptions to raise/reraise
6348-
orig: the original except that was caught
6349-
6350-
Calculates an exception group to raise. It contains
6351-
all exceptions in excs, where those that were reraised
6352-
have same nesting structure as in orig, and those that
6353-
were raised (if any) are added as siblings in a new EG.
6354-
6355-
Returns NULL and sets an exception on failure.
6356-
*/
6357-
static PyObject *
6358-
do_reraise_star(PyObject *excs, PyObject *orig)
6359-
{
6360-
assert(PyList_Check(excs));
6361-
assert(PyExceptionInstance_Check(orig));
6362-
6363-
Py_ssize_t numexcs = PyList_GET_SIZE(excs);
6364-
6365-
if (numexcs == 0) {
6366-
return Py_NewRef(Py_None);
6367-
}
6368-
6369-
if (!_PyBaseExceptionGroup_Check(orig)) {
6370-
/* a naked exception was caught and wrapped. Only one except* clause
6371-
* could have executed,so there is at most one exception to raise.
6372-
*/
6373-
6374-
assert(numexcs == 1 || (numexcs == 2 && PyList_GET_ITEM(excs, 1) == Py_None));
6375-
6376-
PyObject *e = PyList_GET_ITEM(excs, 0);
6377-
assert(e != NULL);
6378-
return Py_NewRef(e);
6379-
}
6380-
6381-
6382-
PyObject *raised_list = PyList_New(0);
6383-
if (raised_list == NULL) {
6384-
return NULL;
6385-
}
6386-
PyObject* reraised_list = PyList_New(0);
6387-
if (reraised_list == NULL) {
6388-
Py_DECREF(raised_list);
6389-
return NULL;
6390-
}
6391-
6392-
/* Now we are holding refs to raised_list and reraised_list */
6393-
6394-
PyObject *result = NULL;
6395-
6396-
/* Split excs into raised and reraised by comparing metadata with orig */
6397-
for (Py_ssize_t i = 0; i < numexcs; i++) {
6398-
PyObject *e = PyList_GET_ITEM(excs, i);
6399-
assert(e != NULL);
6400-
if (Py_IsNone(e)) {
6401-
continue;
6402-
}
6403-
bool is_reraise = is_same_exception_metadata(e, orig);
6404-
PyObject *append_list = is_reraise ? reraised_list : raised_list;
6405-
if (PyList_Append(append_list, e) < 0) {
6406-
goto done;
6407-
}
6408-
}
6409-
6410-
PyObject *reraised_eg = _PyExc_ExceptionGroupProjection(orig, reraised_list);
6411-
if (reraised_eg == NULL) {
6412-
goto done;
6413-
}
6414-
6415-
if (!Py_IsNone(reraised_eg)) {
6416-
assert(is_same_exception_metadata(reraised_eg, orig));
6417-
}
6418-
6419-
Py_ssize_t num_raised = PyList_GET_SIZE(raised_list);
6420-
if (num_raised == 0) {
6421-
result = reraised_eg;
6422-
}
6423-
else if (num_raised > 0) {
6424-
int res = 0;
6425-
if (!Py_IsNone(reraised_eg)) {
6426-
res = PyList_Append(raised_list, reraised_eg);
6427-
}
6428-
Py_DECREF(reraised_eg);
6429-
if (res < 0) {
6430-
goto done;
6431-
}
6432-
result = _PyExc_CreateExceptionGroup("", raised_list);
6433-
if (result == NULL) {
6434-
goto done;
6435-
}
6436-
}
6437-
6438-
done:
6439-
Py_XDECREF(raised_list);
6440-
Py_XDECREF(reraised_list);
6441-
return result;
6442-
}
6443-
64446313
/* Iterate v argcnt times and store the results on the stack (via decreasing
64456314
sp). Return 1 for success, 0 if error.
64466315

0 commit comments

Comments
 (0)