Skip to content

Commit 9369942

Browse files
authored
[3.10] gh-91924: Fix __ltrace__ for non-UTF-8 stdout encoding (#93214)
Fix __ltrace__ debug feature if the stdout encoding is not UTF-8. If the stdout encoding is not UTF-8, the first call to lltrace_resume_frame() indirectly sets lltrace to 0 when calling unicode_check_encoding_errors() which calls encodings.search_function(). Add test_lltrace.test_lltrace() test.
1 parent 02d35fc commit 9369942

File tree

3 files changed

+73
-2
lines changed

3 files changed

+73
-2
lines changed

Lib/test/test_lltrace.py

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
import os
1+
import opcode
2+
import re
3+
import sys
24
import textwrap
35
import unittest
46

5-
from test.support import os_helper
7+
from test.support import os_helper, verbose
68
from test.support.script_helper import assert_python_ok
79

810

11+
Py_DEBUG = hasattr(sys, 'gettotalrefcount')
12+
13+
@unittest.skipUnless(Py_DEBUG, "lltrace requires Py_DEBUG")
914
class TestLLTrace(unittest.TestCase):
1015

1116
def test_lltrace_does_not_crash_on_subscript_operator(self):
@@ -27,5 +32,67 @@ def test_lltrace_does_not_crash_on_subscript_operator(self):
2732

2833
assert_python_ok(os_helper.TESTFN)
2934

35+
def run_code(self, code):
36+
code = textwrap.dedent(code).strip()
37+
with open(os_helper.TESTFN, 'w', encoding='utf-8') as fd:
38+
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
39+
fd.write(code)
40+
status, stdout, stderr = assert_python_ok(os_helper.TESTFN)
41+
self.assertEqual(stderr, b"")
42+
self.assertEqual(status, 0)
43+
result = stdout.decode('utf-8')
44+
if verbose:
45+
print("\n\n--- code ---")
46+
print(code)
47+
print("\n--- stdout ---")
48+
print(result)
49+
print()
50+
return result
51+
52+
def check_op(self, op, stdout, present):
53+
op = opcode.opmap[op]
54+
regex = re.compile(f': {op}($|, )', re.MULTILINE)
55+
if present:
56+
self.assertTrue(regex.search(stdout),
57+
f'": {op}" not found in: {stdout}')
58+
else:
59+
self.assertFalse(regex.search(stdout),
60+
f'": {op}" found in: {stdout}')
61+
62+
def check_op_in(self, op, stdout):
63+
self.check_op(op, stdout, True)
64+
65+
def check_op_not_in(self, op, stdout):
66+
self.check_op(op, stdout, False)
67+
68+
def test_lltrace(self):
69+
stdout = self.run_code("""
70+
def dont_trace_1():
71+
a = "a"
72+
a = 10 * a
73+
def trace_me():
74+
for i in range(3):
75+
+i
76+
def dont_trace_2():
77+
x = 42
78+
y = -x
79+
dont_trace_1()
80+
__ltrace__ = 1
81+
trace_me()
82+
del __ltrace__
83+
dont_trace_2()
84+
""")
85+
self.check_op_in("GET_ITER", stdout)
86+
self.check_op_in("FOR_ITER", stdout)
87+
self.check_op_in("UNARY_POSITIVE", stdout)
88+
self.check_op_in("POP_TOP", stdout)
89+
90+
# before: dont_trace_1() is not traced
91+
self.check_op_not_in("BINARY_MULTIPLY", stdout)
92+
93+
# after: dont_trace_2() is not traced
94+
self.check_op_not_in("UNARY_NEGATIVE", stdout)
95+
96+
3097
if __name__ == "__main__":
3198
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix ``__ltrace__`` debug feature if the stdout encoding is not UTF-8. Patch
2+
by Victor Stinner.

Python/ceval.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5377,6 +5377,8 @@ prtrace(PyThreadState *tstate, PyObject *v, const char *str)
53775377
}
53785378
printf("\n");
53795379
PyErr_Restore(type, value, traceback);
5380+
// gh-91924: PyObject_Print() can indirectly set lltrace to 0
5381+
lltrace = 1;
53805382
return 1;
53815383
}
53825384
#endif

0 commit comments

Comments
 (0)