Skip to content

Commit 71dbb5c

Browse files
committed
pythongh-99181: fix except* on unhashable exceptions
1 parent cfec5b1 commit 71dbb5c

File tree

2 files changed

+156
-17
lines changed

2 files changed

+156
-17
lines changed

Lib/test/test_except_star.py

+130
Original file line numberDiff line numberDiff line change
@@ -1000,5 +1000,135 @@ def test_exc_info_restored(self):
10001000
self.assertEqual(sys.exc_info(), (None, None, None))
10011001

10021002

1003+
class TestExceptStarUnhashableLeafExceptions(ExceptStarTest):
1004+
class UnhashableExc(ValueError):
1005+
hash = None
1006+
1007+
def except_type(self, eg, type):
1008+
match, rest = None, None
1009+
try:
1010+
try:
1011+
raise eg
1012+
except* type as e:
1013+
match = e
1014+
except Exception as e:
1015+
rest = e
1016+
return match, rest
1017+
1018+
def test_catch_unhashable_leaf_exception(self):
1019+
Bad = self.UnhashableExc
1020+
eg = ExceptionGroup("eg", [TypeError(1), Bad(2)])
1021+
match, rest = self.except_type(eg, Bad)
1022+
self.assertExceptionIsLike(match, ExceptionGroup("eg", [Bad(2)]))
1023+
self.assertExceptionIsLike(rest, ExceptionGroup("eg", [TypeError(1)]))
1024+
1025+
def test_propagate_unhashable_leaf(self):
1026+
Bad = self.UnhashableExc
1027+
eg = ExceptionGroup("eg", [TypeError(1), Bad(2)])
1028+
match, rest = self.except_type(eg, TypeError)
1029+
self.assertExceptionIsLike(match, ExceptionGroup("eg", [TypeError(1)]))
1030+
self.assertExceptionIsLike(rest, ExceptionGroup("eg", [Bad(2)]))
1031+
1032+
def test_catch_nothing_unhashable_leaf(self):
1033+
Bad = self.UnhashableExc
1034+
eg = ExceptionGroup("eg", [TypeError(1), Bad(2)])
1035+
match, rest = self.except_type(eg, OSError)
1036+
self.assertIsNone(match)
1037+
self.assertExceptionIsLike(rest, eg)
1038+
1039+
def test_catch_everything_unhashable_leaf(self):
1040+
Bad = self.UnhashableExc
1041+
eg = ExceptionGroup("eg", [TypeError(1), Bad(2)])
1042+
match, rest = self.except_type(eg, Exception)
1043+
self.assertExceptionIsLike(match, eg)
1044+
self.assertIsNone(rest)
1045+
1046+
def test_reraise_unhashable_leaf(self):
1047+
Bad = self.UnhashableExc
1048+
eg = ExceptionGroup("eg", [TypeError(1), Bad(2), ValueError(3)])
1049+
try:
1050+
try:
1051+
raise eg
1052+
except* TypeError:
1053+
pass
1054+
except* Bad:
1055+
raise
1056+
except Exception as e:
1057+
exc = e
1058+
1059+
self.assertExceptionIsLike(
1060+
exc, ExceptionGroup("eg", [Bad(2), ValueError(3)]))
1061+
1062+
1063+
class TestExceptStarUnhashableExceptionGroupSubclass(ExceptStarTest):
1064+
class UnhashableEG(ExceptionGroup):
1065+
hash = None
1066+
1067+
def derive(self, excs):
1068+
return type(self)(self.message, excs)
1069+
1070+
def except_type(self, eg, type):
1071+
match, rest = None, None
1072+
try:
1073+
try:
1074+
raise eg
1075+
except* type as e:
1076+
match = e
1077+
except Exception as e:
1078+
rest = e
1079+
return match, rest
1080+
1081+
def test_catch_some_unhashable_exception_group_subclass(self):
1082+
BadEG = self.UnhashableEG
1083+
eg = BadEG("eg",
1084+
[TypeError(1),
1085+
BadEG("nested", [ValueError(2)])])
1086+
1087+
match, rest = self.except_type(eg, TypeError)
1088+
self.assertExceptionIsLike(match, BadEG("eg", [TypeError(1)]))
1089+
self.assertExceptionIsLike(rest,
1090+
BadEG("eg", [BadEG("nested", [ValueError(2)])]))
1091+
1092+
def test_catch_none_unhashable_exception_group_subclass(self):
1093+
BadEG = self.UnhashableEG
1094+
eg = BadEG("eg",
1095+
[TypeError(1),
1096+
BadEG("nested", [ValueError(2)])])
1097+
1098+
match, rest = self.except_type(eg, OSError)
1099+
self.assertIsNone(match)
1100+
self.assertExceptionIsLike(rest, eg)
1101+
1102+
def test_catch_all_unhashable_exception_group_subclass(self):
1103+
BadEG = self.UnhashableEG
1104+
eg = BadEG("eg",
1105+
[TypeError(1),
1106+
BadEG("nested", [ValueError(2)])])
1107+
1108+
match, rest = self.except_type(eg, Exception)
1109+
self.assertExceptionIsLike(match, eg)
1110+
self.assertIsNone(rest)
1111+
1112+
def test_reraise_unhashable_eg(self):
1113+
BadEG = self.UnhashableEG
1114+
eg = BadEG("eg",
1115+
[TypeError(1), ValueError(2),
1116+
BadEG("nested", [ValueError(3), OSError(4)])])
1117+
1118+
try:
1119+
try:
1120+
raise eg
1121+
except* ValueError:
1122+
pass
1123+
except* OSError:
1124+
raise
1125+
except Exception as e:
1126+
exc = e
1127+
1128+
self.assertExceptionIsLike(
1129+
exc, BadEG("eg", [TypeError(1),
1130+
BadEG("nested", [OSError(4)])]))
1131+
1132+
10031133
if __name__ == '__main__':
10041134
unittest.main()

Objects/exceptions.c

+26-17
Original file line numberDiff line numberDiff line change
@@ -962,11 +962,11 @@ typedef enum {
962962
EXCEPTION_GROUP_MATCH_BY_TYPE = 0,
963963
/* A PyFunction returning True for matching exceptions */
964964
EXCEPTION_GROUP_MATCH_BY_PREDICATE = 1,
965-
/* A set of leaf exceptions to include in the result.
965+
/* A set of the IDs of leaf exception to include in the result.
966966
* This matcher type is used internally by the interpreter
967967
* to construct reraised exceptions.
968968
*/
969-
EXCEPTION_GROUP_MATCH_INSTANCES = 2
969+
EXCEPTION_GROUP_MATCH_INSTANCE_IDS = 2
970970
} _exceptiongroup_split_matcher_type;
971971

972972
static int
@@ -1024,10 +1024,16 @@ exceptiongroup_split_check_match(PyObject *exc,
10241024
Py_DECREF(exc_matches);
10251025
return is_true;
10261026
}
1027-
case EXCEPTION_GROUP_MATCH_INSTANCES: {
1027+
case EXCEPTION_GROUP_MATCH_INSTANCE_IDS: {
10281028
assert(PySet_Check(matcher_value));
10291029
if (!_PyBaseExceptionGroup_Check(exc)) {
1030-
return PySet_Contains(matcher_value, exc);
1030+
PyObject *exc_key = PyLong_FromVoidPtr(exc);
1031+
if (exc_key == NULL) {
1032+
return -1;
1033+
}
1034+
int res = PySet_Contains(matcher_value, exc_key);
1035+
Py_DECREF(exc_key);
1036+
return res;
10311037
}
10321038
return 0;
10331039
}
@@ -1212,32 +1218,35 @@ BaseExceptionGroup_subgroup(PyObject *self, PyObject *args)
12121218
}
12131219

12141220
static int
1215-
collect_exception_group_leaves(PyObject *exc, PyObject *leaves)
1221+
collect_exception_group_leaf_ids(PyObject *exc, PyObject *leaf_ids)
12161222
{
12171223
if (Py_IsNone(exc)) {
12181224
return 0;
12191225
}
12201226

12211227
assert(PyExceptionInstance_Check(exc));
1222-
assert(PySet_Check(leaves));
1228+
assert(PySet_Check(leaf_ids));
12231229

1224-
/* Add all leaf exceptions in exc to the leaves set */
1230+
/* Add IDs of all leaf exceptions in exc to the leaf_ids set */
12251231

12261232
if (!_PyBaseExceptionGroup_Check(exc)) {
1227-
if (PySet_Add(leaves, exc) < 0) {
1233+
PyObject *exc_key = PyLong_FromVoidPtr(exc);
1234+
if (exc_key == NULL) {
12281235
return -1;
12291236
}
1230-
return 0;
1237+
int res = PySet_Add(leaf_ids, exc_key);
1238+
Py_DECREF(exc_key);
1239+
return res;
12311240
}
12321241
PyBaseExceptionGroupObject *eg = _PyBaseExceptionGroupObject_cast(exc);
12331242
Py_ssize_t num_excs = PyTuple_GET_SIZE(eg->excs);
12341243
/* recursive calls */
12351244
for (Py_ssize_t i = 0; i < num_excs; i++) {
12361245
PyObject *e = PyTuple_GET_ITEM(eg->excs, i);
1237-
if (_Py_EnterRecursiveCall(" in collect_exception_group_leaves")) {
1246+
if (_Py_EnterRecursiveCall(" in collect_exception_group_leaf_ids")) {
12381247
return -1;
12391248
}
1240-
int res = collect_exception_group_leaves(e, leaves);
1249+
int res = collect_exception_group_leaf_ids(e, leaf_ids);
12411250
_Py_LeaveRecursiveCall();
12421251
if (res < 0) {
12431252
return -1;
@@ -1258,8 +1267,8 @@ exception_group_projection(PyObject *eg, PyObject *keep)
12581267
assert(_PyBaseExceptionGroup_Check(eg));
12591268
assert(PyList_CheckExact(keep));
12601269

1261-
PyObject *leaves = PySet_New(NULL);
1262-
if (!leaves) {
1270+
PyObject *leaf_ids = PySet_New(NULL);
1271+
if (!leaf_ids) {
12631272
return NULL;
12641273
}
12651274

@@ -1268,18 +1277,18 @@ exception_group_projection(PyObject *eg, PyObject *keep)
12681277
PyObject *e = PyList_GET_ITEM(keep, i);
12691278
assert(e != NULL);
12701279
assert(_PyBaseExceptionGroup_Check(e));
1271-
if (collect_exception_group_leaves(e, leaves) < 0) {
1272-
Py_DECREF(leaves);
1280+
if (collect_exception_group_leaf_ids(e, leaf_ids) < 0) {
1281+
Py_DECREF(leaf_ids);
12731282
return NULL;
12741283
}
12751284
}
12761285

12771286
_exceptiongroup_split_result split_result;
12781287
bool construct_rest = false;
12791288
int err = exceptiongroup_split_recursive(
1280-
eg, EXCEPTION_GROUP_MATCH_INSTANCES, leaves,
1289+
eg, EXCEPTION_GROUP_MATCH_INSTANCE_IDS, leaf_ids,
12811290
construct_rest, &split_result);
1282-
Py_DECREF(leaves);
1291+
Py_DECREF(leaf_ids);
12831292
if (err < 0) {
12841293
return NULL;
12851294
}

0 commit comments

Comments
 (0)