Skip to content

Commit 43b72b0

Browse files
committed
gh-146480: Override the exception in _PyErr_SetKeyError()
If _PyErr_SetKeyError() is called with an exception set, it now replaces the current exception with KeyError (as expected), instead of setting a SystemError or failing with a fatal error (in debug mode).
1 parent 5c0dcb3 commit 43b72b0

3 files changed

Lines changed: 33 additions & 1 deletion

File tree

Lib/test/test_capi/test_misc.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,15 @@ def test_tp_bases_slot_none(self):
928928
_testcapi.create_heapctype_with_none_bases_slot
929929
)
930930

931+
def test__pyerr_setkeyerror(self):
932+
# Test _PyErr_SetKeyError()
933+
_pyerr_setkeyerror = _testinternalcapi._pyerr_setkeyerror
934+
with self.assertRaises(KeyError) as cm:
935+
# Test _PyErr_SetKeyError() with an exception set to check
936+
# that the function overrides the current exception
937+
_pyerr_setkeyerror("key")
938+
self.assertEqual(cm.exception.args, ("key",))
939+
931940

932941
@requires_limited_api
933942
class TestHeapTypeRelative(unittest.TestCase):

Modules/_testinternalcapi.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2838,6 +2838,20 @@ test_threadstate_set_stack_protection(PyObject *self, PyObject *Py_UNUSED(args))
28382838
}
28392839

28402840

2841+
static PyObject *
2842+
_pyerr_setkeyerror(PyObject *self, PyObject *arg)
2843+
{
2844+
// Test that _PyErr_SetKeyError() overrides the current exception
2845+
// if an exception is set
2846+
PyErr_NoMemory();
2847+
2848+
_PyErr_SetKeyError(arg);
2849+
2850+
assert(PyErr_Occurred());
2851+
return NULL;
2852+
}
2853+
2854+
28412855
static PyMethodDef module_functions[] = {
28422856
{"get_configs", get_configs, METH_NOARGS},
28432857
{"get_eval_frame_stats", get_eval_frame_stats, METH_NOARGS, NULL},
@@ -2959,6 +2973,7 @@ static PyMethodDef module_functions[] = {
29592973
{"module_get_gc_hooks", module_get_gc_hooks, METH_O},
29602974
{"test_threadstate_set_stack_protection",
29612975
test_threadstate_set_stack_protection, METH_NOARGS},
2976+
{"_pyerr_setkeyerror", _pyerr_setkeyerror, METH_O},
29622977
{NULL, NULL} /* sentinel */
29632978
};
29642979

Python/errors.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,11 +248,19 @@ PyErr_SetObject(PyObject *exception, PyObject *value)
248248

249249
/* Set a key error with the specified argument, wrapping it in a
250250
* tuple automatically so that tuple keys are not unpacked as the
251-
* exception arguments. */
251+
* exception arguments.
252+
*
253+
* If an exception is already set, override the exception. */
252254
void
253255
_PyErr_SetKeyError(PyObject *arg)
254256
{
255257
PyThreadState *tstate = _PyThreadState_GET();
258+
259+
// PyObject_CallOneArg() must not be called with an exception set,
260+
// otherwise _Py_CheckFunctionResult() can fail if the function returned
261+
// a result with an excception set.
262+
_PyErr_Clear(tstate);
263+
256264
PyObject *exc = PyObject_CallOneArg(PyExc_KeyError, arg);
257265
if (!exc) {
258266
/* caller will expect error to be set anyway */

0 commit comments

Comments
 (0)