Skip to content

Commit 66f77ca

Browse files
authored
bpo-42923: _Py_DumpExtensionModules() ignores stdlib ext (GH-24254)
1 parent cad8020 commit 66f77ca

File tree

5 files changed

+65
-26
lines changed

5 files changed

+65
-26
lines changed

Include/internal/pycore_traceback.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ PyAPI_FUNC(void) _Py_DumpASCII(int fd, PyObject *text);
7474
This function is signal safe. */
7575
PyAPI_FUNC(void) _Py_DumpDecimal(
7676
int fd,
77-
unsigned long value);
77+
size_t value);
7878

7979
/* Format an integer as hexadecimal with width digits into fd file descriptor.
8080
The function is signal safe. */

Lib/test/test_capi.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -547,24 +547,33 @@ def test_pynumber_tobase(self):
547547
self.assertRaises(TypeError, pynumber_tobase, '123', 10)
548548
self.assertRaises(SystemError, pynumber_tobase, 123, 0)
549549

550-
def test_fatal_error(self):
551-
code = 'import _testcapi; _testcapi.fatal_error(b"MESSAGE")'
550+
def check_fatal_error(self, code, expected, not_expected=()):
552551
with support.SuppressCrashReport():
553552
rc, out, err = assert_python_failure('-sSI', '-c', code)
554553

555554
err = err.replace(b'\r', b'').decode('ascii', 'replace')
556555
self.assertIn('Fatal Python error: test_fatal_error: MESSAGE\n',
557556
err)
558557

559-
match = re.search('^Extension modules:(.*)$', err, re.MULTILINE)
558+
match = re.search(r'^Extension modules:(.*) \(total: ([0-9]+)\)$',
559+
err, re.MULTILINE)
560560
if not match:
561561
self.fail(f"Cannot find 'Extension modules:' in {err!r}")
562562
modules = set(match.group(1).strip().split(', '))
563-
# Test _PyModule_IsExtension(): the list doesn't have to
564-
# be exhaustive.
565-
for name in ('sys', 'builtins', '_imp', '_thread', '_weakref',
566-
'_io', 'marshal', '_signal', '_abc', '_testcapi'):
563+
total = int(match.group(2))
564+
565+
for name in expected:
567566
self.assertIn(name, modules)
567+
for name in not_expected:
568+
self.assertNotIn(name, modules)
569+
self.assertEqual(len(modules), total)
570+
571+
def test_fatal_error(self):
572+
expected = ('_testcapi',)
573+
not_expected = ('sys', 'builtins', '_imp', '_thread', '_weakref',
574+
'_io', 'marshal', '_signal', '_abc')
575+
code = 'import _testcapi; _testcapi.fatal_error(b"MESSAGE")'
576+
self.check_fatal_error(code, expected, not_expected)
568577

569578

570579
class TestPendingCalls(unittest.TestCase):

Lib/test/test_faulthandler.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -334,19 +334,19 @@ def test_disable(self):
334334
def test_dump_ext_modules(self):
335335
code = """
336336
import faulthandler
337+
# _testcapi is a test module and not considered as a stdlib module
338+
import _testcapi
337339
faulthandler.enable()
338340
faulthandler._sigsegv()
339341
"""
340342
stderr, exitcode = self.get_output(code)
341343
stderr = '\n'.join(stderr)
342-
match = re.search('^Extension modules:(.*)$', stderr, re.MULTILINE)
344+
match = re.search(r'^Extension modules:(.*) \(total: [0-9]+\)$',
345+
stderr, re.MULTILINE)
343346
if not match:
344347
self.fail(f"Cannot find 'Extension modules:' in {stderr!r}")
345348
modules = set(match.group(1).strip().split(', '))
346-
# Only check for a few extensions, the list doesn't have to be
347-
# exhaustive.
348-
for ext in ('sys', 'builtins', '_io', 'faulthandler'):
349-
self.assertIn(ext, modules)
349+
self.assertIn('_testcapi', modules)
350350

351351
def test_is_enabled(self):
352352
orig_stderr = sys.stderr

Python/pylifecycle.c

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#include "pycore_sysmodule.h" // _PySys_ClearAuditHooks()
1919
#include "pycore_traceback.h" // _Py_DumpTracebackThreads()
2020

21+
#include "module_names.h" // _Py_module_names
22+
2123
#include <locale.h> // setlocale()
2224

2325
#ifdef HAVE_SIGNAL_H
@@ -2496,26 +2498,27 @@ fatal_error_exit(int status)
24962498
}
24972499

24982500

2499-
// Dump the list of extension modules of sys.modules into fd file descriptor.
2501+
// Dump the list of extension modules of sys.modules, excluding stdlib modules
2502+
// (_Py_module_names), into fd file descriptor.
2503+
//
25002504
// This function is called by a signal handler in faulthandler: avoid memory
2501-
// allocations and keep the implementation simple. For example, the list
2502-
// is not sorted on purpose.
2505+
// allocations and keep the implementation simple. For example, the list is not
2506+
// sorted on purpose.
25032507
void
25042508
_Py_DumpExtensionModules(int fd, PyInterpreterState *interp)
25052509
{
25062510
if (interp == NULL) {
25072511
return;
25082512
}
25092513
PyObject *modules = interp->modules;
2510-
if (!PyDict_Check(modules)) {
2514+
if (modules == NULL || !PyDict_Check(modules)) {
25112515
return;
25122516
}
25132517

2514-
PUTS(fd, "\nExtension modules: ");
2515-
2518+
int header = 1;
2519+
Py_ssize_t count = 0;
25162520
Py_ssize_t pos = 0;
25172521
PyObject *key, *value;
2518-
int comma = 0;
25192522
while (PyDict_Next(modules, &pos, &key, &value)) {
25202523
if (!PyUnicode_Check(key)) {
25212524
continue;
@@ -2524,14 +2527,41 @@ _Py_DumpExtensionModules(int fd, PyInterpreterState *interp)
25242527
continue;
25252528
}
25262529

2527-
if (comma) {
2530+
// Check if it is a stdlib extension module.
2531+
// Use the module name from the sys.modules key,
2532+
// don't attempt to get the module object name.
2533+
const Py_ssize_t names_len = Py_ARRAY_LENGTH(_Py_module_names);
2534+
int is_stdlib_mod = 0;
2535+
for (Py_ssize_t i=0; i < names_len; i++) {
2536+
const char *name = _Py_module_names[i];
2537+
if (PyUnicode_CompareWithASCIIString(key, name) == 0) {
2538+
is_stdlib_mod = 1;
2539+
break;
2540+
}
2541+
}
2542+
if (is_stdlib_mod) {
2543+
// Ignore stdlib extension module.
2544+
continue;
2545+
}
2546+
2547+
if (header) {
2548+
PUTS(fd, "\nExtension modules: ");
2549+
header = 0;
2550+
}
2551+
else {
25282552
PUTS(fd, ", ");
25292553
}
2530-
comma = 1;
25312554

25322555
_Py_DumpASCII(fd, key);
2556+
count++;
2557+
}
2558+
2559+
if (count) {
2560+
PUTS(fd, " (total: ");
2561+
_Py_DumpDecimal(fd, count);
2562+
PUTS(fd, ")");
2563+
PUTS(fd, "\n");
25332564
}
2534-
PUTS(fd, "\n");
25352565
}
25362566

25372567

Python/traceback.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -628,12 +628,12 @@ PyTraceBack_Print(PyObject *v, PyObject *f)
628628
This function is signal safe. */
629629

630630
void
631-
_Py_DumpDecimal(int fd, unsigned long value)
631+
_Py_DumpDecimal(int fd, size_t value)
632632
{
633633
/* maximum number of characters required for output of %lld or %p.
634634
We need at most ceil(log10(256)*SIZEOF_LONG_LONG) digits,
635635
plus 1 for the null byte. 53/22 is an upper bound for log10(256). */
636-
char buffer[1 + (sizeof(unsigned long)*53-1) / 22 + 1];
636+
char buffer[1 + (sizeof(size_t)*53-1) / 22 + 1];
637637
char *ptr, *end;
638638

639639
end = &buffer[Py_ARRAY_LENGTH(buffer) - 1];
@@ -767,7 +767,7 @@ dump_frame(int fd, PyFrameObject *frame)
767767
int lineno = PyCode_Addr2Line(code, frame->f_lasti);
768768
PUTS(fd, ", line ");
769769
if (lineno >= 0) {
770-
_Py_DumpDecimal(fd, (unsigned long)lineno);
770+
_Py_DumpDecimal(fd, (size_t)lineno);
771771
}
772772
else {
773773
PUTS(fd, "???");

0 commit comments

Comments
 (0)