Skip to content

Commit f9b7457

Browse files
authored
bpo-37467: Fix PyErr_Display() for bytes filename (GH-14504)
Fix sys.excepthook() and PyErr_Display() if a filename is a bytes string. For example, for a SyntaxError exception where the filename attribute is a bytes string. Cleanup also test_sys: * Sort imports. * Rename numruns global var to INTERN_NUMRUNS. * Add DisplayHookTest and ExceptHookTest test case classes. * Don't save/restore sys.stdout and sys.displayhook using setUp()/tearDown(): do it in each test method. * Test error case (call hook with no argument) after the success case.
1 parent ec6c1bd commit f9b7457

File tree

3 files changed

+80
-48
lines changed

3 files changed

+80
-48
lines changed

Lib/test/test_sys.py

Lines changed: 76 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,104 @@
1-
import unittest, test.support
1+
from test import support
22
from test.support.script_helper import assert_python_ok, assert_python_failure
3-
import sys, io, os
3+
import builtins
4+
import codecs
5+
import gc
6+
import io
7+
import locale
8+
import operator
9+
import os
410
import struct
511
import subprocess
12+
import sys
13+
import sysconfig
14+
import test.support
615
import textwrap
16+
import unittest
717
import warnings
8-
import operator
9-
import codecs
10-
import gc
11-
import sysconfig
12-
import locale
18+
1319

1420
# count the number of test runs, used to create unique
1521
# strings to intern in test_intern()
16-
numruns = 0
22+
INTERN_NUMRUNS = 0
1723

1824

19-
class SysModuleTest(unittest.TestCase):
25+
class DisplayHookTest(unittest.TestCase):
2026

21-
def setUp(self):
22-
self.orig_stdout = sys.stdout
23-
self.orig_stderr = sys.stderr
24-
self.orig_displayhook = sys.displayhook
27+
def test_original_displayhook(self):
28+
dh = sys.__displayhook__
2529

26-
def tearDown(self):
27-
sys.stdout = self.orig_stdout
28-
sys.stderr = self.orig_stderr
29-
sys.displayhook = self.orig_displayhook
30-
test.support.reap_children()
30+
with support.captured_stdout() as out:
31+
dh(42)
3132

32-
def test_original_displayhook(self):
33-
import builtins
34-
out = io.StringIO()
35-
sys.stdout = out
33+
self.assertEqual(out.getvalue(), "42\n")
34+
self.assertEqual(builtins._, 42)
3635

37-
dh = sys.__displayhook__
36+
del builtins._
3837

39-
self.assertRaises(TypeError, dh)
40-
if hasattr(builtins, "_"):
41-
del builtins._
38+
with support.captured_stdout() as out:
39+
dh(None)
4240

43-
dh(None)
4441
self.assertEqual(out.getvalue(), "")
4542
self.assertTrue(not hasattr(builtins, "_"))
46-
dh(42)
47-
self.assertEqual(out.getvalue(), "42\n")
48-
self.assertEqual(builtins._, 42)
4943

50-
del sys.stdout
51-
self.assertRaises(RuntimeError, dh, 42)
44+
# sys.displayhook() requires arguments
45+
self.assertRaises(TypeError, dh)
46+
47+
stdout = sys.stdout
48+
try:
49+
del sys.stdout
50+
self.assertRaises(RuntimeError, dh, 42)
51+
finally:
52+
sys.stdout = stdout
5253

5354
def test_lost_displayhook(self):
54-
del sys.displayhook
55-
code = compile("42", "<string>", "single")
56-
self.assertRaises(RuntimeError, eval, code)
55+
displayhook = sys.displayhook
56+
try:
57+
del sys.displayhook
58+
code = compile("42", "<string>", "single")
59+
self.assertRaises(RuntimeError, eval, code)
60+
finally:
61+
sys.displayhook = displayhook
5762

5863
def test_custom_displayhook(self):
5964
def baddisplayhook(obj):
6065
raise ValueError
61-
sys.displayhook = baddisplayhook
62-
code = compile("42", "<string>", "single")
63-
self.assertRaises(ValueError, eval, code)
6466

65-
def test_original_excepthook(self):
66-
err = io.StringIO()
67-
sys.stderr = err
67+
with support.swap_attr(sys, 'displayhook', baddisplayhook):
68+
code = compile("42", "<string>", "single")
69+
self.assertRaises(ValueError, eval, code)
6870

69-
eh = sys.__excepthook__
7071

71-
self.assertRaises(TypeError, eh)
72+
class ExceptHookTest(unittest.TestCase):
73+
74+
def test_original_excepthook(self):
7275
try:
7376
raise ValueError(42)
7477
except ValueError as exc:
75-
eh(*sys.exc_info())
78+
with support.captured_stderr() as err:
79+
sys.__excepthook__(*sys.exc_info())
7680

7781
self.assertTrue(err.getvalue().endswith("ValueError: 42\n"))
7882

83+
self.assertRaises(TypeError, sys.__excepthook__)
84+
85+
def test_excepthook_bytes_filename(self):
86+
# bpo-37467: sys.excepthook() must not crash if a filename
87+
# is a bytes string
88+
with warnings.catch_warnings():
89+
warnings.simplefilter('ignore', BytesWarning)
90+
91+
try:
92+
raise SyntaxError("msg", (b"bytes_filename", 123, 0, "text"))
93+
except SyntaxError as exc:
94+
with support.captured_stderr() as err:
95+
sys.__excepthook__(*sys.exc_info())
96+
97+
err = err.getvalue()
98+
self.assertIn(""" File "b'bytes_filename'", line 123\n""", err)
99+
self.assertIn(""" text\n""", err)
100+
self.assertTrue(err.endswith("SyntaxError: msg\n"))
101+
79102
def test_excepthook(self):
80103
with test.support.captured_output("stderr") as stderr:
81104
sys.excepthook(1, '1', 1)
@@ -85,6 +108,12 @@ def test_excepthook(self):
85108
# FIXME: testing the code for a lost or replaced excepthook in
86109
# Python/pythonrun.c::PyErr_PrintEx() is tricky.
87110

111+
112+
class SysModuleTest(unittest.TestCase):
113+
114+
def tearDown(self):
115+
test.support.reap_children()
116+
88117
def test_exit(self):
89118
# call with two arguments
90119
self.assertRaises(TypeError, sys.exit, 42, 42)
@@ -492,10 +521,10 @@ def test_43581(self):
492521
self.assertEqual(sys.__stdout__.encoding, sys.__stderr__.encoding)
493522

494523
def test_intern(self):
495-
global numruns
496-
numruns += 1
524+
global INTERN_NUMRUNS
525+
INTERN_NUMRUNS += 1
497526
self.assertRaises(TypeError, sys.intern)
498-
s = "never interned before" + str(numruns)
527+
s = "never interned before" + str(INTERN_NUMRUNS)
499528
self.assertTrue(sys.intern(s) is s)
500529
s2 = s.swapcase().swapcase()
501530
self.assertTrue(sys.intern(s2) is s)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :func:`sys.excepthook` and :c:func:`PyErr_Display` if a filename is a
2+
bytes string. For example, for a SyntaxError exception where the filename
3+
attribute is a bytes string.

Python/pythonrun.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -797,7 +797,7 @@ print_exception(PyObject *f, PyObject *value)
797797
Py_DECREF(value);
798798
value = message;
799799

800-
line = PyUnicode_FromFormat(" File \"%U\", line %d\n",
800+
line = PyUnicode_FromFormat(" File \"%S\", line %d\n",
801801
filename, lineno);
802802
Py_DECREF(filename);
803803
if (line != NULL) {

0 commit comments

Comments
 (0)