Skip to content

Commit b9cbdde

Browse files
[3.14] gh-142737: Handle lost io.open in _Py_FindSourceFile (GH-142747) (GH-142773)
gh-142737: Handle lost `io.open` in `_Py_FindSourceFile` (GH-142747) (cherry picked from commit f277781) Co-authored-by: Bartosz Sławecki <[email protected]>
1 parent 88ac995 commit b9cbdde

File tree

4 files changed

+35
-1
lines changed

4 files changed

+35
-1
lines changed

Lib/test/test_traceback.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from test.support import (Error, captured_output, cpython_only, ALWAYS_EQ,
1919
requires_debug_ranges, has_no_debug_ranges,
2020
requires_subprocess)
21-
from test.support.os_helper import TESTFN, unlink
21+
from test.support.os_helper import TESTFN, temp_dir, unlink
2222
from test.support.script_helper import assert_python_ok, assert_python_failure, make_script
2323
from test.support.import_helper import forget
2424
from test.support import force_not_colorized, force_not_colorized_test_class
@@ -504,6 +504,33 @@ def __del__(self):
504504
b'ZeroDivisionError: division by zero']
505505
self.assertEqual(stderr.splitlines(), expected)
506506

507+
@cpython_only
508+
def test_lost_io_open(self):
509+
# GH-142737: Display the traceback even if io.open is lost
510+
crasher = textwrap.dedent("""\
511+
import io
512+
import traceback
513+
# Trigger fallback mode
514+
traceback._print_exception_bltin = None
515+
del io.open
516+
raise RuntimeError("should not crash")
517+
""")
518+
519+
# Create a temporary script to exercise _Py_FindSourceFile
520+
with temp_dir() as script_dir:
521+
script = make_script(
522+
script_dir=script_dir,
523+
script_basename='tb_test_no_io_open',
524+
source=crasher)
525+
rc, stdout, stderr = assert_python_failure(script)
526+
527+
self.assertEqual(rc, 1) # Make sure it's not a crash
528+
529+
expected = [b'Traceback (most recent call last):',
530+
f' File "{script}", line 6, in <module>'.encode(),
531+
b'RuntimeError: should not crash']
532+
self.assertEqual(stderr.splitlines(), expected)
533+
507534
def test_print_exception(self):
508535
output = StringIO()
509536
traceback.print_exception(
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Tracebacks will be displayed in fallback mode even if :func:`io.open` is lost.
2+
Previously, this would crash the interpreter.
3+
Patch by Bartosz Sławecki.

Objects/call.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,7 @@ _PyObject_CallMethodId(PyObject *obj, _Py_Identifier *name,
726726
PyObject * _PyObject_CallMethodFormat(PyThreadState *tstate, PyObject *callable,
727727
const char *format, ...)
728728
{
729+
assert(callable != NULL);
729730
va_list va;
730731
va_start(va, format);
731732
PyObject *retval = callmethod(tstate, callable, format, va);

Python/traceback.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,9 @@ _Py_FindSourceFile(PyObject *filename, char* namebuf, size_t namelen, PyObject *
416416
npath = PyList_Size(syspath);
417417

418418
open = PyObject_GetAttr(io, &_Py_ID(open));
419+
if (open == NULL) {
420+
goto error;
421+
}
419422
for (i = 0; i < npath; i++) {
420423
v = PyList_GetItem(syspath, i);
421424
if (v == NULL) {

0 commit comments

Comments
 (0)