Skip to content

Commit 9e5d30c

Browse files
authored
bpo-39882: Py_FatalError() logs the function name (GH-18819)
The Py_FatalError() function is replaced with a macro which logs automatically the name of the current function, unless the Py_LIMITED_API macro is defined. Changes: * Add _Py_FatalErrorFunc() function. * Remove the function name from the message of Py_FatalError() calls which included the function name. * Update tests.
1 parent 7b3c252 commit 9e5d30c

17 files changed

+112
-69
lines changed

Doc/c-api/sys.rst

+7
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,13 @@ Process Control
388388
function :c:func:`abort` is called which will attempt to produce a :file:`core`
389389
file.
390390
391+
The ``Py_FatalError()`` function is replaced with a macro which logs
392+
automatically the name of the current function, unless the
393+
``Py_LIMITED_API`` macro is defined.
394+
395+
.. versionchanged:: 3.9
396+
Log the function name automatically.
397+
391398
392399
.. c:function:: void Py_Exit(int status)
393400

Include/cpython/pyerrors.h

+6
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,12 @@ PyAPI_FUNC(void) _PyErr_WriteUnraisableMsg(
178178
const char *err_msg,
179179
PyObject *obj);
180180

181+
PyAPI_FUNC(void) _Py_NO_RETURN _Py_FatalErrorFunc(
182+
const char *func,
183+
const char *message);
184+
185+
#define Py_FatalError(message) _Py_FatalErrorFunc(__func__, message)
186+
181187
#ifdef __cplusplus
182188
}
183189
#endif

Include/pyerrors.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ PyAPI_FUNC(void) PyErr_GetExcInfo(PyObject **, PyObject **, PyObject **);
2121
PyAPI_FUNC(void) PyErr_SetExcInfo(PyObject *, PyObject *, PyObject *);
2222
#endif
2323

24-
/* Defined in Python/pylifecycle.c */
24+
/* Defined in Python/pylifecycle.c
25+
26+
The Py_FatalError() function is replaced with a macro which logs
27+
automatically the name of the current function, unless the Py_LIMITED_API
28+
macro is defined. */
2529
PyAPI_FUNC(void) _Py_NO_RETURN Py_FatalError(const char *message);
2630

2731
#if defined(Py_DEBUG) || defined(Py_LIMITED_API)

Lib/test/test_capi.py

+11-9
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ def test_no_FatalError_infinite_loop(self):
6161
self.assertEqual(out, b'')
6262
# This used to cause an infinite loop.
6363
self.assertTrue(err.rstrip().startswith(
64-
b'Fatal Python error:'
65-
b' PyThreadState_Get: no current thread'))
64+
b'Fatal Python error: '
65+
b'PyThreadState_Get: no current thread'))
6666

6767
def test_memoryview_from_NULL_pointer(self):
6868
self.assertRaises(ValueError, _testcapi.make_memoryview_from_NULL_pointer)
@@ -197,7 +197,8 @@ def test_return_null_without_error(self):
197197
""")
198198
rc, out, err = assert_python_failure('-c', code)
199199
self.assertRegex(err.replace(b'\r', b''),
200-
br'Fatal Python error: a function returned NULL '
200+
br'Fatal Python error: _Py_CheckFunctionResult: '
201+
br'a function returned NULL '
201202
br'without setting an error\n'
202203
br'Python runtime state: initialized\n'
203204
br'SystemError: <built-in function '
@@ -225,8 +226,9 @@ def test_return_result_with_error(self):
225226
""")
226227
rc, out, err = assert_python_failure('-c', code)
227228
self.assertRegex(err.replace(b'\r', b''),
228-
br'Fatal Python error: a function returned a '
229-
br'result with an error set\n'
229+
br'Fatal Python error: _Py_CheckFunctionResult: '
230+
br'a function returned a result '
231+
br'with an error set\n'
230232
br'Python runtime state: initialized\n'
231233
br'ValueError\n'
232234
br'\n'
@@ -668,7 +670,7 @@ def test_buffer_overflow(self):
668670
r"\n"
669671
r"Enable tracemalloc to get the memory block allocation traceback\n"
670672
r"\n"
671-
r"Fatal Python error: bad trailing pad byte")
673+
r"Fatal Python error: _PyMem_DebugRawFree: bad trailing pad byte")
672674
regex = regex.format(ptr=self.PTR_REGEX)
673675
regex = re.compile(regex, flags=re.DOTALL)
674676
self.assertRegex(out, regex)
@@ -684,14 +686,14 @@ def test_api_misuse(self):
684686
r"\n"
685687
r"Enable tracemalloc to get the memory block allocation traceback\n"
686688
r"\n"
687-
r"Fatal Python error: bad ID: Allocated using API 'm', verified using API 'r'\n")
689+
r"Fatal Python error: _PyMem_DebugRawFree: bad ID: Allocated using API 'm', verified using API 'r'\n")
688690
regex = regex.format(ptr=self.PTR_REGEX)
689691
self.assertRegex(out, regex)
690692

691693
def check_malloc_without_gil(self, code):
692694
out = self.check(code)
693-
expected = ('Fatal Python error: Python memory allocator called '
694-
'without holding the GIL')
695+
expected = ('Fatal Python error: _PyMem_DebugMalloc: '
696+
'Python memory allocator called without holding the GIL')
695697
self.assertIn(expected, out)
696698

697699
def test_pymem_malloc_without_gil(self):

Lib/test/test_exceptions.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1078,8 +1078,9 @@ def recurse(cnt):
10781078
"""
10791079
with SuppressCrashReport():
10801080
rc, out, err = script_helper.assert_python_failure("-c", code)
1081-
self.assertIn(b'Fatal Python error: Cannot recover from '
1082-
b'MemoryErrors while normalizing exceptions.', err)
1081+
self.assertIn(b'Fatal Python error: _PyErr_NormalizeException: '
1082+
b'Cannot recover from MemoryErrors while '
1083+
b'normalizing exceptions.', err)
10831084

10841085
@cpython_only
10851086
def test_MemoryError(self):

Lib/test/test_faulthandler.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,9 @@ def check_error(self, code, line_number, fatal_error, *,
123123
self.assertRegex(output, regex)
124124
self.assertNotEqual(exitcode, 0)
125125

126-
def check_fatal_error(self, code, line_number, name_regex, **kw):
126+
def check_fatal_error(self, code, line_number, name_regex, func=None, **kw):
127+
if func:
128+
name_regex = '%s: %s' % (func, name_regex)
127129
fatal_error = 'Fatal Python error: %s' % name_regex
128130
self.check_error(code, line_number, fatal_error, **kw)
129131

@@ -173,6 +175,7 @@ def test_fatal_error_c_thread(self):
173175
3,
174176
'in new thread',
175177
know_current_thread=False,
178+
func='faulthandler_fatal_error_thread',
176179
py_fatal_error=True)
177180

178181
def test_sigabrt(self):
@@ -230,6 +233,7 @@ def test_fatal_error(self):
230233
""",
231234
2,
232235
'xyz',
236+
func='faulthandler_fatal_error_py',
233237
py_fatal_error=True)
234238

235239
def test_fatal_error_without_gil(self):
@@ -239,6 +243,7 @@ def test_fatal_error_without_gil(self):
239243
""",
240244
2,
241245
'xyz',
246+
func='faulthandler_fatal_error_py',
242247
py_fatal_error=True)
243248

244249
@unittest.skipIf(sys.platform.startswith('openbsd'),

Lib/test/test_io.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -4247,7 +4247,8 @@ def run():
42474247
err = res.err.decode()
42484248
if res.rc != 0:
42494249
# Failure: should be a fatal error
4250-
pattern = (r"Fatal Python error: could not acquire lock "
4250+
pattern = (r"Fatal Python error: _enter_buffered_busy: "
4251+
r"could not acquire lock "
42514252
r"for <(_io\.)?BufferedWriter name='<{stream_name}>'> "
42524253
r"at interpreter shutdown, possibly due to "
42534254
r"daemon threads".format_map(locals()))

Lib/test/test_sys.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,8 @@ def set_recursion_limit_at_depth(depth, limit):
269269
finally:
270270
sys.setrecursionlimit(oldlimit)
271271

272+
# The error message is specific to CPython
273+
@test.support.cpython_only
272274
def test_recursionlimit_fatalerror(self):
273275
# A fatal error occurs if a second recursion limit is hit when recovering
274276
# from a first one.
@@ -290,7 +292,8 @@ def f():
290292
err = sub.communicate()[1]
291293
self.assertTrue(sub.returncode, sub.returncode)
292294
self.assertIn(
293-
b"Fatal Python error: Cannot recover from stack overflow",
295+
b"Fatal Python error: _Py_CheckRecursiveCall: "
296+
b"Cannot recover from stack overflow",
294297
err)
295298

296299
def test_getwindowsversion(self):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The :c:func:`Py_FatalError` function is replaced with a macro which logs
2+
automatically the name of the current function, unless the ``Py_LIMITED_API``
3+
macro is defined.

Objects/obmalloc.c

+13-12
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ static void* _PyMem_DebugRealloc(void *ctx, void *ptr, size_t size);
2525
static void _PyMem_DebugFree(void *ctx, void *p);
2626

2727
static void _PyObject_DebugDumpAddress(const void *p);
28-
static void _PyMem_DebugCheckAddress(char api_id, const void *p);
28+
static void _PyMem_DebugCheckAddress(const char *func, char api_id, const void *p);
2929

3030
static void _PyMem_SetupDebugHooksDomain(PyMemAllocatorDomain domain);
3131

@@ -2205,7 +2205,7 @@ _PyMem_DebugRawFree(void *ctx, void *p)
22052205
uint8_t *q = (uint8_t *)p - 2*SST; /* address returned from malloc */
22062206
size_t nbytes;
22072207

2208-
_PyMem_DebugCheckAddress(api->api_id, p);
2208+
_PyMem_DebugCheckAddress(__func__, api->api_id, p);
22092209
nbytes = read_size_t(q);
22102210
nbytes += PYMEM_DEBUG_EXTRA_BYTES;
22112211
memset(q, PYMEM_DEADBYTE, nbytes);
@@ -2230,7 +2230,7 @@ _PyMem_DebugRawRealloc(void *ctx, void *p, size_t nbytes)
22302230
#define ERASED_SIZE 64
22312231
uint8_t save[2*ERASED_SIZE]; /* A copy of erased bytes. */
22322232

2233-
_PyMem_DebugCheckAddress(api->api_id, p);
2233+
_PyMem_DebugCheckAddress(__func__, api->api_id, p);
22342234

22352235
data = (uint8_t *)p;
22362236
head = data - 2*SST;
@@ -2314,41 +2314,42 @@ _PyMem_DebugRawRealloc(void *ctx, void *p, size_t nbytes)
23142314
}
23152315

23162316
static inline void
2317-
_PyMem_DebugCheckGIL(void)
2317+
_PyMem_DebugCheckGIL(const char *func)
23182318
{
23192319
if (!PyGILState_Check()) {
2320-
Py_FatalError("Python memory allocator called "
2321-
"without holding the GIL");
2320+
_Py_FatalErrorFunc(func,
2321+
"Python memory allocator called "
2322+
"without holding the GIL");
23222323
}
23232324
}
23242325

23252326
static void *
23262327
_PyMem_DebugMalloc(void *ctx, size_t nbytes)
23272328
{
2328-
_PyMem_DebugCheckGIL();
2329+
_PyMem_DebugCheckGIL(__func__);
23292330
return _PyMem_DebugRawMalloc(ctx, nbytes);
23302331
}
23312332

23322333
static void *
23332334
_PyMem_DebugCalloc(void *ctx, size_t nelem, size_t elsize)
23342335
{
2335-
_PyMem_DebugCheckGIL();
2336+
_PyMem_DebugCheckGIL(__func__);
23362337
return _PyMem_DebugRawCalloc(ctx, nelem, elsize);
23372338
}
23382339

23392340

23402341
static void
23412342
_PyMem_DebugFree(void *ctx, void *ptr)
23422343
{
2343-
_PyMem_DebugCheckGIL();
2344+
_PyMem_DebugCheckGIL(__func__);
23442345
_PyMem_DebugRawFree(ctx, ptr);
23452346
}
23462347

23472348

23482349
static void *
23492350
_PyMem_DebugRealloc(void *ctx, void *ptr, size_t nbytes)
23502351
{
2351-
_PyMem_DebugCheckGIL();
2352+
_PyMem_DebugCheckGIL(__func__);
23522353
return _PyMem_DebugRawRealloc(ctx, ptr, nbytes);
23532354
}
23542355

@@ -2358,7 +2359,7 @@ _PyMem_DebugRealloc(void *ctx, void *ptr, size_t nbytes)
23582359
* The API id, is also checked.
23592360
*/
23602361
static void
2361-
_PyMem_DebugCheckAddress(char api, const void *p)
2362+
_PyMem_DebugCheckAddress(const char *func, char api, const void *p)
23622363
{
23632364
const uint8_t *q = (const uint8_t *)p;
23642365
char msgbuf[64];
@@ -2406,7 +2407,7 @@ _PyMem_DebugCheckAddress(char api, const void *p)
24062407

24072408
error:
24082409
_PyObject_DebugDumpAddress(p);
2409-
Py_FatalError(msg);
2410+
_Py_FatalErrorFunc(func, msg);
24102411
}
24112412

24122413
/* Display info to stderr about the memory block at p. */

Parser/parser.c

+3-2
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@ s_push(stack *s, const dfa *d, node *parent)
5454
static void
5555
s_pop(stack *s)
5656
{
57-
if (s_empty(s))
58-
Py_FatalError("s_pop: parser stack underflow -- FATAL");
57+
if (s_empty(s)) {
58+
Py_FatalError("parser stack underflow");
59+
}
5960
s->s_top++;
6061
}
6162

Parser/tokenizer.c

+5-3
Original file line numberDiff line numberDiff line change
@@ -1031,10 +1031,12 @@ static void
10311031
tok_backup(struct tok_state *tok, int c)
10321032
{
10331033
if (c != EOF) {
1034-
if (--tok->cur < tok->buf)
1035-
Py_FatalError("tok_backup: beginning of buffer");
1036-
if (*tok->cur != c)
1034+
if (--tok->cur < tok->buf) {
1035+
Py_FatalError("beginning of buffer");
1036+
}
1037+
if (*tok->cur != c) {
10371038
*tok->cur = c;
1039+
}
10381040
}
10391041
}
10401042

Python/ceval.c

+6-6
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ PyEval_AcquireLock(void)
283283
struct _ceval_runtime_state *ceval = &runtime->ceval;
284284
PyThreadState *tstate = _PyRuntimeState_GetThreadState(runtime);
285285
if (tstate == NULL) {
286-
Py_FatalError("PyEval_AcquireLock: current thread state is NULL");
286+
Py_FatalError("current thread state is NULL");
287287
}
288288
take_gil(ceval, tstate);
289289
exit_thread_if_finalizing(tstate);
@@ -314,7 +314,7 @@ PyEval_AcquireThread(PyThreadState *tstate)
314314
take_gil(ceval, tstate);
315315
exit_thread_if_finalizing(tstate);
316316
if (_PyThreadState_Swap(&runtime->gilstate, tstate) != NULL) {
317-
Py_FatalError("PyEval_AcquireThread: non-NULL old thread state");
317+
Py_FatalError("non-NULL old thread state");
318318
}
319319
}
320320

@@ -326,7 +326,7 @@ PyEval_ReleaseThread(PyThreadState *tstate)
326326
_PyRuntimeState *runtime = tstate->interp->runtime;
327327
PyThreadState *new_tstate = _PyThreadState_Swap(&runtime->gilstate, NULL);
328328
if (new_tstate != tstate) {
329-
Py_FatalError("PyEval_ReleaseThread: wrong thread state");
329+
Py_FatalError("wrong thread state");
330330
}
331331
drop_gil(&runtime->ceval, tstate);
332332
}
@@ -373,7 +373,7 @@ PyEval_SaveThread(void)
373373
struct _ceval_runtime_state *ceval = &runtime->ceval;
374374
PyThreadState *tstate = _PyThreadState_Swap(&runtime->gilstate, NULL);
375375
if (tstate == NULL) {
376-
Py_FatalError("PyEval_SaveThread: NULL tstate");
376+
Py_FatalError("NULL tstate");
377377
}
378378
assert(gil_created(&ceval->gil));
379379
drop_gil(ceval, tstate);
@@ -1236,7 +1236,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
12361236
if (_Py_atomic_load_relaxed(&ceval->gil_drop_request)) {
12371237
/* Give another thread a chance */
12381238
if (_PyThreadState_Swap(&runtime->gilstate, NULL) != tstate) {
1239-
Py_FatalError("ceval: tstate mix-up");
1239+
Py_FatalError("tstate mix-up");
12401240
}
12411241
drop_gil(ceval, tstate);
12421242

@@ -1248,7 +1248,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
12481248
exit_thread_if_finalizing(tstate);
12491249

12501250
if (_PyThreadState_Swap(&runtime->gilstate, tstate) != NULL) {
1251-
Py_FatalError("ceval: orphan tstate");
1251+
Py_FatalError("orphan tstate");
12521252
}
12531253
}
12541254
/* Check for asynchronous exceptions. */

Python/import.c

+2-3
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ PyImport_GetModuleDict(void)
310310
{
311311
PyInterpreterState *interp = _PyInterpreterState_GET_UNSAFE();
312312
if (interp->modules == NULL) {
313-
Py_FatalError("PyImport_GetModuleDict: no module dictionary!");
313+
Py_FatalError("no module dictionary");
314314
}
315315
return interp->modules;
316316
}
@@ -982,8 +982,7 @@ PyImport_ExecCodeModuleWithPathnames(const char *name, PyObject *co,
982982
_Py_IDENTIFIER(_get_sourcefile);
983983

984984
if (interp == NULL) {
985-
Py_FatalError("PyImport_ExecCodeModuleWithPathnames: "
986-
"no interpreter!");
985+
Py_FatalError("no interpreter!");
987986
}
988987

989988
external= PyObject_GetAttrString(interp->importlib,

0 commit comments

Comments
 (0)