Skip to content

Commit d4153a9

Browse files
authored
gh-146480: Override the exception in _PyErr_SetKeyError() (#146486)
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 d0061d5 commit d4153a9

File tree

3 files changed

+50
-4
lines changed

3 files changed

+50
-4
lines changed

Lib/test/test_capi/test_exceptions.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313

1414
from .test_misc import decode_stderr
1515

16-
# Skip this test if the _testcapi module isn't available.
16+
# Skip this test if the _testcapi or _testinternalcapi module isn't available.
1717
_testcapi = import_helper.import_module('_testcapi')
18+
_testinternalcapi = import_helper.import_module('_testinternalcapi')
1819

1920
NULL = None
2021

@@ -108,6 +109,26 @@ def __del__(self):
108109
b'<string>:7: RuntimeWarning: Testing PyErr_WarnEx',
109110
])
110111

112+
def test__pyerr_setkeyerror(self):
113+
# Test _PyErr_SetKeyError()
114+
_pyerr_setkeyerror = _testinternalcapi._pyerr_setkeyerror
115+
for arg in (
116+
"key",
117+
# check that a tuple argument is not unpacked
118+
(1, 2, 3),
119+
# PyErr_SetObject(exc_type, exc_value) uses exc_value if it's
120+
# already an exception, but _PyErr_SetKeyError() always creates a
121+
# new KeyError.
122+
KeyError('arg'),
123+
):
124+
with self.subTest(arg=arg):
125+
with self.assertRaises(KeyError) as cm:
126+
# Test calling _PyErr_SetKeyError() with an exception set
127+
# to check that the function overrides the current
128+
# exception.
129+
_pyerr_setkeyerror(arg)
130+
self.assertEqual(cm.exception.args, (arg,))
131+
111132

112133
class Test_FatalError(unittest.TestCase):
113134

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: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,13 +246,23 @@ PyErr_SetObject(PyObject *exception, PyObject *value)
246246
_PyErr_SetObject(tstate, exception, value);
247247
}
248248

249-
/* Set a key error with the specified argument, wrapping it in a
250-
* tuple automatically so that tuple keys are not unpacked as the
251-
* exception arguments. */
249+
/* Set a key error with the specified argument. This function should be used to
250+
* raise a KeyError with an argument instead of PyErr_SetObject(PyExc_KeyError,
251+
* arg) which has a special behavior. PyErr_SetObject() unpacks arg if it's a
252+
* tuple, and it uses arg instead of creating a new exception if arg is an
253+
* exception.
254+
*
255+
* If an exception is already set, override the exception. */
252256
void
253257
_PyErr_SetKeyError(PyObject *arg)
254258
{
255259
PyThreadState *tstate = _PyThreadState_GET();
260+
261+
// PyObject_CallOneArg() must not be called with an exception set,
262+
// otherwise _Py_CheckFunctionResult() can fail if the function returned
263+
// a result with an excception set.
264+
_PyErr_Clear(tstate);
265+
256266
PyObject *exc = PyObject_CallOneArg(PyExc_KeyError, arg);
257267
if (!exc) {
258268
/* caller will expect error to be set anyway */

0 commit comments

Comments
 (0)