bpo-43950: Add option to opt-out of PEP-657 (GH-27023)

Co-authored-by: Pablo Galindo <Pablogsal@gmail.com>
Co-authored-by: Batuhan Taskaya <batuhanosmantaskaya@gmail.com>
Co-authored-by: Ammar Askar <ammar@ammaraskar.com>
This commit is contained in:
Ammar Askar 2021-07-07 15:07:12 -04:00 committed by GitHub
parent 3d3027c5fc
commit 4823d9a512
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 276 additions and 64 deletions

View file

@ -596,6 +596,16 @@ PyConfig
.. versionadded:: 3.10 .. versionadded:: 3.10
.. c:member:: int no_debug_ranges
If equals to ``1``, disables the inclusion of the end line and column
mappings in code objects. Also disables traceback printing carets to
specific error locations.
Default: ``0``.
.. versionadded:: 3.11
.. c:member:: wchar_t* check_hash_pycs_mode .. c:member:: wchar_t* check_hash_pycs_mode
Control the validation behavior of hash-based ``.pyc`` files: Control the validation behavior of hash-based ``.pyc`` files:

View file

@ -474,6 +474,12 @@ Miscellaneous options
* ``-X warn_default_encoding`` issues a :class:`EncodingWarning` when the * ``-X warn_default_encoding`` issues a :class:`EncodingWarning` when the
locale-specific default encoding is used for opening files. locale-specific default encoding is used for opening files.
See also :envvar:`PYTHONWARNDEFAULTENCODING`. See also :envvar:`PYTHONWARNDEFAULTENCODING`.
* ``-X no_debug_ranges`` disables the inclusion of the tables mapping extra
location information (end line, start column offset and end column offset)
to every instruction in code objects. This is useful when smaller code
objects and pyc files are desired as well as supressing the extra visual
location indicators when the interpreter displays tracebacks. See also
:envvar:`PYTHONNODEBUGRANGES`.
It also allows passing arbitrary values and retrieving them through the It also allows passing arbitrary values and retrieving them through the
:data:`sys._xoptions` dictionary. :data:`sys._xoptions` dictionary.
@ -509,6 +515,9 @@ Miscellaneous options
.. deprecated-removed:: 3.9 3.10 .. deprecated-removed:: 3.9 3.10
The ``-X oldparser`` option. The ``-X oldparser`` option.
.. versionadded:: 3.11
The ``-X no_debug_ranges`` option.
Options you shouldn't use Options you shouldn't use
~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~
@ -936,6 +945,17 @@ conflict.
.. versionadded:: 3.10 .. versionadded:: 3.10
.. envvar:: PYTHONNODEBUGRANGES
If this variable is set, it disables the inclusion of the tables mapping
extra location information (end line, start column offset and end column
offset) to every instruction in code objects. This is useful when smaller
code objects and pyc files are desired as well as supressing the extra visual
location indicators when the interpreter displays tracebacks.
.. versionadded:: 3.11
Debug-mode variables Debug-mode variables
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~

View file

@ -140,6 +140,7 @@ typedef struct PyConfig {
int faulthandler; int faulthandler;
int tracemalloc; int tracemalloc;
int import_time; int import_time;
int no_debug_ranges;
int show_ref_count; int show_ref_count;
int dump_refs; int dump_refs;
int malloc_stats; int malloc_stats;

View file

@ -3,7 +3,7 @@
from idlelib import run from idlelib import run
import io import io
import sys import sys
from test.support import captured_output, captured_stderr from test.support import captured_output, captured_stderr, has_no_debug_ranges
import unittest import unittest
from unittest import mock from unittest import mock
import idlelib import idlelib
@ -33,9 +33,14 @@ def __eq__(self, other):
run.print_exception() run.print_exception()
tb = output.getvalue().strip().splitlines() tb = output.getvalue().strip().splitlines()
self.assertEqual(13, len(tb)) if has_no_debug_ranges():
self.assertIn('UnhashableException: ex2', tb[4]) self.assertEqual(11, len(tb))
self.assertIn('UnhashableException: ex1', tb[12]) self.assertIn('UnhashableException: ex2', tb[3])
self.assertIn('UnhashableException: ex1', tb[10])
else:
self.assertEqual(13, len(tb))
self.assertIn('UnhashableException: ex2', tb[4])
self.assertIn('UnhashableException: ex1', tb[12])
data = (('1/0', ZeroDivisionError, "division by zero\n"), data = (('1/0', ZeroDivisionError, "division by zero\n"),
('abc', NameError, "name 'abc' is not defined. " ('abc', NameError, "name 'abc' is not defined. "

View file

@ -61,6 +61,7 @@ def test_set_invalid(self):
'faulthandler', 'faulthandler',
'tracemalloc', 'tracemalloc',
'import_time', 'import_time',
'no_debug_ranges',
'show_ref_count', 'show_ref_count',
'dump_refs', 'dump_refs',
'malloc_stats', 'malloc_stats',

View file

@ -415,6 +415,14 @@ def requires_lzma(reason='requires lzma'):
lzma = None lzma = None
return unittest.skipUnless(lzma, reason) return unittest.skipUnless(lzma, reason)
def has_no_debug_ranges():
import _testinternalcapi
config = _testinternalcapi.get_config()
return bool(config['no_debug_ranges'])
def requires_debug_ranges(reason='requires co_positions / debug_ranges'):
return unittest.skipIf(has_no_debug_ranges(), reason)
requires_legacy_unicode_capi = unittest.skipUnless(unicode_legacy_string, requires_legacy_unicode_capi = unittest.skipUnless(unicode_legacy_string,
'requires legacy Unicode C API') 'requires legacy Unicode C API')

View file

@ -137,7 +137,8 @@
except ImportError: except ImportError:
ctypes = None ctypes = None
from test.support import (run_doctest, run_unittest, cpython_only, from test.support import (run_doctest, run_unittest, cpython_only,
check_impl_detail) check_impl_detail, requires_debug_ranges)
from test.support.script_helper import assert_python_ok
def consts(t): def consts(t):
@ -325,6 +326,7 @@ def func():
new_code = code = func.__code__.replace(co_linetable=b'') new_code = code = func.__code__.replace(co_linetable=b'')
self.assertEqual(list(new_code.co_lines()), []) self.assertEqual(list(new_code.co_lines()), [])
@requires_debug_ranges()
def test_co_positions_artificial_instructions(self): def test_co_positions_artificial_instructions(self):
import dis import dis
@ -372,8 +374,32 @@ def test_co_positions_artificial_instructions(self):
] ]
) )
def test_endline_and_columntable_none_when_no_debug_ranges(self):
# Make sure that if `-X no_debug_ranges` is used, the endlinetable and
# columntable are None.
code = textwrap.dedent("""
def f():
pass
assert f.__code__.co_endlinetable is None
assert f.__code__.co_columntable is None
""")
assert_python_ok('-X', 'no_debug_ranges', '-c', code, __cleanenv=True)
def test_endline_and_columntable_none_when_no_debug_ranges_env(self):
# Same as above but using the environment variable opt out.
code = textwrap.dedent("""
def f():
pass
assert f.__code__.co_endlinetable is None
assert f.__code__.co_columntable is None
""")
assert_python_ok('-c', code, PYTHONNODEBUGRANGES='1', __cleanenv=True)
# co_positions behavior when info is missing. # co_positions behavior when info is missing.
@requires_debug_ranges()
def test_co_positions_empty_linetable(self): def test_co_positions_empty_linetable(self):
def func(): def func():
x = 1 x = 1
@ -382,6 +408,7 @@ def func():
self.assertIsNone(line) self.assertIsNone(line)
self.assertEqual(end_line, new_code.co_firstlineno + 1) self.assertEqual(end_line, new_code.co_firstlineno + 1)
@requires_debug_ranges()
def test_co_positions_empty_endlinetable(self): def test_co_positions_empty_endlinetable(self):
def func(): def func():
x = 1 x = 1
@ -390,6 +417,7 @@ def func():
self.assertEqual(line, new_code.co_firstlineno + 1) self.assertEqual(line, new_code.co_firstlineno + 1)
self.assertIsNone(end_line) self.assertIsNone(end_line)
@requires_debug_ranges()
def test_co_positions_empty_columntable(self): def test_co_positions_empty_columntable(self):
def func(): def func():
x = 1 x = 1

View file

@ -9,7 +9,7 @@
import types import types
import textwrap import textwrap
from test import support from test import support
from test.support import script_helper from test.support import script_helper, requires_debug_ranges
from test.support.os_helper import FakePath from test.support.os_helper import FakePath
@ -985,7 +985,7 @@ def if_else_break():
elif instr.opname in HANDLED_JUMPS: elif instr.opname in HANDLED_JUMPS:
self.assertNotEqual(instr.arg, (line + 1)*INSTR_SIZE) self.assertNotEqual(instr.arg, (line + 1)*INSTR_SIZE)
@requires_debug_ranges()
class TestSourcePositions(unittest.TestCase): class TestSourcePositions(unittest.TestCase):
# Ensure that compiled code snippets have correct line and column numbers # Ensure that compiled code snippets have correct line and column numbers
# in `co_positions()`. # in `co_positions()`.

View file

@ -1,6 +1,6 @@
# Minimal tests for dis module # Minimal tests for dis module
from test.support import captured_stdout from test.support import captured_stdout, requires_debug_ranges
from test.support.bytecode_helper import BytecodeTestCase from test.support.bytecode_helper import BytecodeTestCase
import unittest import unittest
import sys import sys
@ -1192,6 +1192,7 @@ def test_jumpy(self):
actual = dis.get_instructions(jumpy, first_line=expected_jumpy_line) actual = dis.get_instructions(jumpy, first_line=expected_jumpy_line)
self.assertInstructionsEqual(list(actual), expected_opinfo_jumpy) self.assertInstructionsEqual(list(actual), expected_opinfo_jumpy)
@requires_debug_ranges()
def test_co_positions(self): def test_co_positions(self):
code = compile('f(\n x, y, z\n)', '<test>', 'exec') code = compile('f(\n x, y, z\n)', '<test>', 'exec')
positions = [ positions = [
@ -1210,6 +1211,7 @@ def test_co_positions(self):
] ]
self.assertEqual(positions, expected) self.assertEqual(positions, expected)
@requires_debug_ranges()
def test_co_positions_missing_info(self): def test_co_positions_missing_info(self):
code = compile('x, y, z', '<test>', 'exec') code = compile('x, y, z', '<test>', 'exec')
code_without_column_table = code.replace(co_columntable=b'') code_without_column_table = code.replace(co_columntable=b'')

View file

@ -2808,10 +2808,12 @@ def test_testmod(): r"""
try: try:
os.fsencode("foo-bär@baz.py") os.fsencode("foo-bär@baz.py")
supports_unicode = True
except UnicodeEncodeError: except UnicodeEncodeError:
# Skip the test: the filesystem encoding is unable to encode the filename # Skip the test: the filesystem encoding is unable to encode the filename
pass supports_unicode = False
else:
if supports_unicode and not support.has_no_debug_ranges():
def test_unicode(): """ def test_unicode(): """
Check doctest with a non-ascii filename: Check doctest with a non-ascii filename:

View file

@ -369,6 +369,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'faulthandler': 0, 'faulthandler': 0,
'tracemalloc': 0, 'tracemalloc': 0,
'import_time': 0, 'import_time': 0,
'no_debug_ranges': 0,
'show_ref_count': 0, 'show_ref_count': 0,
'dump_refs': 0, 'dump_refs': 0,
'malloc_stats': 0, 'malloc_stats': 0,
@ -798,6 +799,7 @@ def test_init_from_config(self):
'hash_seed': 123, 'hash_seed': 123,
'tracemalloc': 2, 'tracemalloc': 2,
'import_time': 1, 'import_time': 1,
'no_debug_ranges': 1,
'show_ref_count': 1, 'show_ref_count': 1,
'malloc_stats': 1, 'malloc_stats': 1,
@ -858,6 +860,7 @@ def test_init_compat_env(self):
'hash_seed': 42, 'hash_seed': 42,
'tracemalloc': 2, 'tracemalloc': 2,
'import_time': 1, 'import_time': 1,
'no_debug_ranges': 1,
'malloc_stats': 1, 'malloc_stats': 1,
'inspect': 1, 'inspect': 1,
'optimization_level': 2, 'optimization_level': 2,
@ -887,6 +890,7 @@ def test_init_python_env(self):
'hash_seed': 42, 'hash_seed': 42,
'tracemalloc': 2, 'tracemalloc': 2,
'import_time': 1, 'import_time': 1,
'no_debug_ranges': 1,
'malloc_stats': 1, 'malloc_stats': 1,
'inspect': 1, 'inspect': 1,
'optimization_level': 2, 'optimization_level': 2,

View file

@ -1,5 +1,6 @@
from test import support from test import support
from test.support import os_helper from test.support import os_helper, requires_debug_ranges
from test.support.script_helper import assert_python_ok
import array import array
import io import io
import marshal import marshal
@ -7,6 +8,7 @@
import unittest import unittest
import os import os
import types import types
import textwrap
try: try:
import _testcapi import _testcapi
@ -126,6 +128,31 @@ def test_different_filenames(self):
self.assertEqual(co1.co_filename, "f1") self.assertEqual(co1.co_filename, "f1")
self.assertEqual(co2.co_filename, "f2") self.assertEqual(co2.co_filename, "f2")
@requires_debug_ranges()
def test_no_columntable_and_endlinetable_with_no_debug_ranges(self):
# Make sure when demarshalling objects with `-X no_debug_ranges`
# that the columntable and endlinetable are None.
co = ExceptionTestCase.test_exceptions.__code__
code = textwrap.dedent("""
import sys
import marshal
with open(sys.argv[1], 'rb') as f:
co = marshal.load(f)
assert co.co_endlinetable is None
assert co.co_columntable is None
""")
try:
with open(os_helper.TESTFN, 'wb') as f:
marshal.dump(co, f)
assert_python_ok('-X', 'no_debug_ranges',
'-c', code, os_helper.TESTFN,
__cleanenv=True)
finally:
os_helper.unlink(os_helper.TESTFN)
@support.cpython_only @support.cpython_only
def test_same_filename_used(self): def test_same_filename_used(self):
s = """def f(): pass\ndef g(): pass""" s = """def f(): pass\ndef g(): pass"""

View file

@ -8,9 +8,10 @@
import unittest import unittest
import re import re
from test import support from test import support
from test.support import Error, captured_output, cpython_only, ALWAYS_EQ from test.support import (Error, captured_output, cpython_only, ALWAYS_EQ,
requires_debug_ranges, has_no_debug_ranges)
from test.support.os_helper import TESTFN, unlink from test.support.os_helper import TESTFN, unlink
from test.support.script_helper import assert_python_ok from test.support.script_helper import assert_python_ok, assert_python_failure
import textwrap import textwrap
import traceback import traceback
@ -75,6 +76,49 @@ def test_nocaret(self):
self.assertEqual(len(err), 3) self.assertEqual(len(err), 3)
self.assertEqual(err[1].strip(), "bad syntax") self.assertEqual(err[1].strip(), "bad syntax")
def test_no_caret_with_no_debug_ranges_flag(self):
# Make sure that if `-X no_debug_ranges` is used, there are no carets
# in the traceback.
try:
with open(TESTFN, 'w') as f:
f.write("x = 1 / 0\n")
_, _, stderr = assert_python_failure(
'-X', 'no_debug_ranges', TESTFN, __cleanenv=True)
lines = stderr.splitlines()
self.assertEqual(len(lines), 4)
self.assertEqual(lines[0], b'Traceback (most recent call last):')
self.assertIn(b'line 1, in <module>', lines[1])
self.assertEqual(lines[2], b' x = 1 / 0')
self.assertEqual(lines[3], b'ZeroDivisionError: division by zero')
finally:
unlink(TESTFN)
def test_no_caret_with_no_debug_ranges_flag_python_traceback(self):
code = textwrap.dedent("""
import traceback
try:
x = 1 / 0
except:
traceback.print_exc()
""")
try:
with open(TESTFN, 'w') as f:
f.write(code)
_, _, stderr = assert_python_ok(
'-X', 'no_debug_ranges', TESTFN, __cleanenv=True)
lines = stderr.splitlines()
self.assertEqual(len(lines), 4)
self.assertEqual(lines[0], b'Traceback (most recent call last):')
self.assertIn(b'line 4, in <module>', lines[1])
self.assertEqual(lines[2], b' x = 1 / 0')
self.assertEqual(lines[3], b'ZeroDivisionError: division by zero')
finally:
unlink(TESTFN)
def test_bad_indentation(self): def test_bad_indentation(self):
err = self.get_exception_format(self.syntax_error_bad_indentation, err = self.get_exception_format(self.syntax_error_bad_indentation,
IndentationError) IndentationError)
@ -155,9 +199,10 @@ def do_test(firstlines, message, charset, lineno):
self.assertTrue(stdout[2].endswith(err_line), self.assertTrue(stdout[2].endswith(err_line),
"Invalid traceback line: {0!r} instead of {1!r}".format( "Invalid traceback line: {0!r} instead of {1!r}".format(
stdout[2], err_line)) stdout[2], err_line))
self.assertTrue(stdout[4] == err_msg, actual_err_msg = stdout[3 if has_no_debug_ranges() else 4]
self.assertTrue(actual_err_msg == err_msg,
"Invalid error message: {0!r} instead of {1!r}".format( "Invalid error message: {0!r} instead of {1!r}".format(
stdout[4], err_msg)) actual_err_msg, err_msg))
do_test("", "foo", "ascii", 3) do_test("", "foo", "ascii", 3)
for charset in ("ascii", "iso-8859-1", "utf-8", "GBK"): for charset in ("ascii", "iso-8859-1", "utf-8", "GBK"):
@ -273,6 +318,7 @@ def test_signatures(self):
'(exc, /, value=<implicit>)') '(exc, /, value=<implicit>)')
@requires_debug_ranges()
class TracebackErrorLocationCaretTests(unittest.TestCase): class TracebackErrorLocationCaretTests(unittest.TestCase):
""" """
Tests for printing code error expressions as part of PEP 657 Tests for printing code error expressions as part of PEP 657
@ -362,6 +408,7 @@ def f_with_multiline():
@cpython_only @cpython_only
@requires_debug_ranges()
class CPythonTracebackErrorCaretTests(TracebackErrorLocationCaretTests): class CPythonTracebackErrorCaretTests(TracebackErrorLocationCaretTests):
""" """
Same set of tests as above but with Python's internal traceback printing. Same set of tests as above but with Python's internal traceback printing.
@ -424,9 +471,13 @@ def check_traceback_format(self, cleanup_func=None):
# Make sure that the traceback is properly indented. # Make sure that the traceback is properly indented.
tb_lines = python_fmt.splitlines() tb_lines = python_fmt.splitlines()
self.assertEqual(len(tb_lines), 7)
banner = tb_lines[0] banner = tb_lines[0]
location, source_line = tb_lines[-3], tb_lines[-2] if has_no_debug_ranges():
self.assertEqual(len(tb_lines), 5)
location, source_line = tb_lines[-2], tb_lines[-1]
else:
self.assertEqual(len(tb_lines), 7)
location, source_line = tb_lines[-3], tb_lines[-2]
self.assertTrue(banner.startswith('Traceback')) self.assertTrue(banner.startswith('Traceback'))
self.assertTrue(location.startswith(' File')) self.assertTrue(location.startswith(' File'))
self.assertTrue(source_line.startswith(' raise')) self.assertTrue(source_line.startswith(' raise'))
@ -668,10 +719,12 @@ def h(count=10):
actual = stderr_g.getvalue().splitlines() actual = stderr_g.getvalue().splitlines()
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
@requires_debug_ranges()
def test_recursive_traceback_python(self): def test_recursive_traceback_python(self):
self._check_recursive_traceback_display(traceback.print_exc) self._check_recursive_traceback_display(traceback.print_exc)
@cpython_only @cpython_only
@requires_debug_ranges()
def test_recursive_traceback_cpython_internal(self): def test_recursive_traceback_cpython_internal(self):
from _testcapi import exception_print from _testcapi import exception_print
def render_exc(): def render_exc():
@ -713,11 +766,16 @@ def __eq__(self, other):
exception_print(exc_val) exception_print(exc_val)
tb = stderr_f.getvalue().strip().splitlines() tb = stderr_f.getvalue().strip().splitlines()
self.assertEqual(13, len(tb)) if has_no_debug_ranges():
self.assertEqual(context_message.strip(), tb[6]) self.assertEqual(11, len(tb))
self.assertIn('UnhashableException: ex2', tb[4]) self.assertEqual(context_message.strip(), tb[5])
self.assertIn('UnhashableException: ex1', tb[12]) self.assertIn('UnhashableException: ex2', tb[3])
self.assertIn('UnhashableException: ex1', tb[10])
else:
self.assertEqual(13, len(tb))
self.assertEqual(context_message.strip(), tb[6])
self.assertIn('UnhashableException: ex2', tb[4])
self.assertIn('UnhashableException: ex1', tb[12])
cause_message = ( cause_message = (
"\nThe above exception was the direct cause " "\nThe above exception was the direct cause "
@ -746,8 +804,12 @@ def zero_div(self):
def check_zero_div(self, msg): def check_zero_div(self, msg):
lines = msg.splitlines() lines = msg.splitlines()
self.assertTrue(lines[-4].startswith(' File')) if has_no_debug_ranges():
self.assertIn('1/0 # In zero_div', lines[-3]) self.assertTrue(lines[-3].startswith(' File'))
self.assertIn('1/0 # In zero_div', lines[-2])
else:
self.assertTrue(lines[-4].startswith(' File'))
self.assertIn('1/0 # In zero_div', lines[-3])
self.assertTrue(lines[-1].startswith('ZeroDivisionError'), lines[-1]) self.assertTrue(lines[-1].startswith('ZeroDivisionError'), lines[-1])
def test_simple(self): def test_simple(self):
@ -756,11 +818,15 @@ def test_simple(self):
except ZeroDivisionError as _: except ZeroDivisionError as _:
e = _ e = _
lines = self.get_report(e).splitlines() lines = self.get_report(e).splitlines()
self.assertEqual(len(lines), 5) if has_no_debug_ranges():
self.assertEqual(len(lines), 4)
self.assertTrue(lines[3].startswith('ZeroDivisionError'))
else:
self.assertEqual(len(lines), 5)
self.assertTrue(lines[4].startswith('ZeroDivisionError'))
self.assertTrue(lines[0].startswith('Traceback')) self.assertTrue(lines[0].startswith('Traceback'))
self.assertTrue(lines[1].startswith(' File')) self.assertTrue(lines[1].startswith(' File'))
self.assertIn('1/0 # Marker', lines[2]) self.assertIn('1/0 # Marker', lines[2])
self.assertTrue(lines[4].startswith('ZeroDivisionError'))
def test_cause(self): def test_cause(self):
def inner_raise(): def inner_raise():
@ -799,11 +865,15 @@ def test_context_suppression(self):
except ZeroDivisionError as _: except ZeroDivisionError as _:
e = _ e = _
lines = self.get_report(e).splitlines() lines = self.get_report(e).splitlines()
self.assertEqual(len(lines), 5) if has_no_debug_ranges():
self.assertEqual(len(lines), 4)
self.assertTrue(lines[3].startswith('ZeroDivisionError'))
else:
self.assertEqual(len(lines), 5)
self.assertTrue(lines[4].startswith('ZeroDivisionError'))
self.assertTrue(lines[0].startswith('Traceback')) self.assertTrue(lines[0].startswith('Traceback'))
self.assertTrue(lines[1].startswith(' File')) self.assertTrue(lines[1].startswith(' File'))
self.assertIn('ZeroDivisionError from None', lines[2]) self.assertIn('ZeroDivisionError from None', lines[2])
self.assertTrue(lines[4].startswith('ZeroDivisionError'))
def test_cause_and_context(self): def test_cause_and_context(self):
# When both a cause and a context are set, only the cause should be # When both a cause and a context are set, only the cause should be
@ -1527,6 +1597,7 @@ def test_traceback_header(self):
exc = traceback.TracebackException(Exception, Exception("haven"), None) exc = traceback.TracebackException(Exception, Exception("haven"), None)
self.assertEqual(list(exc.format()), ["Exception: haven\n"]) self.assertEqual(list(exc.format()), ["Exception: haven\n"])
@requires_debug_ranges()
def test_print(self): def test_print(self):
def f(): def f():
x = 12 x = 12

View file

@ -718,6 +718,7 @@ def doTraceback(self, module):
print_tb(tb, 1, s) print_tb(tb, 1, s)
self.assertTrue(s.getvalue().endswith( self.assertTrue(s.getvalue().endswith(
' def do_raise(): raise TypeError\n' ' def do_raise(): raise TypeError\n'
'' if support.has_no_debug_ranges() else
' ^^^^^^^^^^^^^^^\n' ' ^^^^^^^^^^^^^^^\n'
)) ))
else: else:

View file

@ -130,15 +130,7 @@ code_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
goto exit; goto exit;
} }
linetable = PyTuple_GET_ITEM(args, 14); linetable = PyTuple_GET_ITEM(args, 14);
if (!PyBytes_Check(PyTuple_GET_ITEM(args, 15))) {
_PyArg_BadArgument("code", "argument 16", "bytes", PyTuple_GET_ITEM(args, 15));
goto exit;
}
endlinetable = PyTuple_GET_ITEM(args, 15); endlinetable = PyTuple_GET_ITEM(args, 15);
if (!PyBytes_Check(PyTuple_GET_ITEM(args, 16))) {
_PyArg_BadArgument("code", "argument 17", "bytes", PyTuple_GET_ITEM(args, 16));
goto exit;
}
columntable = PyTuple_GET_ITEM(args, 16); columntable = PyTuple_GET_ITEM(args, 16);
if (!PyBytes_Check(PyTuple_GET_ITEM(args, 17))) { if (!PyBytes_Check(PyTuple_GET_ITEM(args, 17))) {
_PyArg_BadArgument("code", "argument 18", "bytes", PyTuple_GET_ITEM(args, 17)); _PyArg_BadArgument("code", "argument 18", "bytes", PyTuple_GET_ITEM(args, 17));
@ -192,10 +184,8 @@ code_replace_impl(PyCodeObject *self, int co_argcount,
PyObject *co_varnames, PyObject *co_freevars, PyObject *co_varnames, PyObject *co_freevars,
PyObject *co_cellvars, PyObject *co_filename, PyObject *co_cellvars, PyObject *co_filename,
PyObject *co_name, PyObject *co_qualname, PyObject *co_name, PyObject *co_qualname,
PyBytesObject *co_linetable, PyBytesObject *co_linetable, PyObject *co_endlinetable,
PyBytesObject *co_endlinetable, PyObject *co_columntable, PyBytesObject *co_exceptiontable);
PyBytesObject *co_columntable,
PyBytesObject *co_exceptiontable);
static PyObject * static PyObject *
code_replace(PyCodeObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) code_replace(PyCodeObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
@ -222,8 +212,8 @@ code_replace(PyCodeObject *self, PyObject *const *args, Py_ssize_t nargs, PyObje
PyObject *co_name = self->co_name; PyObject *co_name = self->co_name;
PyObject *co_qualname = self->co_qualname; PyObject *co_qualname = self->co_qualname;
PyBytesObject *co_linetable = (PyBytesObject *)self->co_linetable; PyBytesObject *co_linetable = (PyBytesObject *)self->co_linetable;
PyBytesObject *co_endlinetable = (PyBytesObject *)self->co_endlinetable; PyObject *co_endlinetable = self->co_endlinetable;
PyBytesObject *co_columntable = (PyBytesObject *)self->co_columntable; PyObject *co_columntable = self->co_columntable;
PyBytesObject *co_exceptiontable = (PyBytesObject *)self->co_exceptiontable; PyBytesObject *co_exceptiontable = (PyBytesObject *)self->co_exceptiontable;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 0, 0, argsbuf); args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 0, 0, argsbuf);
@ -406,21 +396,13 @@ code_replace(PyCodeObject *self, PyObject *const *args, Py_ssize_t nargs, PyObje
} }
} }
if (args[17]) { if (args[17]) {
if (!PyBytes_Check(args[17])) { co_endlinetable = args[17];
_PyArg_BadArgument("replace", "argument 'co_endlinetable'", "bytes", args[17]);
goto exit;
}
co_endlinetable = (PyBytesObject *)args[17];
if (!--noptargs) { if (!--noptargs) {
goto skip_optional_kwonly; goto skip_optional_kwonly;
} }
} }
if (args[18]) { if (args[18]) {
if (!PyBytes_Check(args[18])) { co_columntable = args[18];
_PyArg_BadArgument("replace", "argument 'co_columntable'", "bytes", args[18]);
goto exit;
}
co_columntable = (PyBytesObject *)args[18];
if (!--noptargs) { if (!--noptargs) {
goto skip_optional_kwonly; goto skip_optional_kwonly;
} }
@ -473,4 +455,4 @@ code__varname_from_oparg(PyCodeObject *self, PyObject *const *args, Py_ssize_t n
exit: exit:
return return_value; return return_value;
} }
/*[clinic end generated code: output=12b394f0212b1c1e input=a9049054013a1b77]*/ /*[clinic end generated code: output=18b9ddc86714e56e input=a9049054013a1b77]*/

View file

@ -379,6 +379,13 @@ _PyCode_New(struct _PyCodeConstructor *con)
return NULL; return NULL;
} }
// Discard the endlinetable and columntable if we are opted out of debug
// ranges.
if (_Py_GetConfig()->no_debug_ranges) {
con->endlinetable = Py_None;
con->columntable = Py_None;
}
PyCodeObject *co = PyObject_New(PyCodeObject, &PyCode_Type); PyCodeObject *co = PyObject_New(PyCodeObject, &PyCode_Type);
if (co == NULL) { if (co == NULL) {
PyErr_NoMemory(); PyErr_NoMemory();
@ -1222,8 +1229,8 @@ code.__new__ as code_new
qualname: unicode qualname: unicode
firstlineno: int firstlineno: int
linetable: object(subclass_of="&PyBytes_Type") linetable: object(subclass_of="&PyBytes_Type")
endlinetable: object(subclass_of="&PyBytes_Type") endlinetable: object
columntable: object(subclass_of="&PyBytes_Type") columntable: object
exceptiontable: object(subclass_of="&PyBytes_Type") exceptiontable: object(subclass_of="&PyBytes_Type")
freevars: object(subclass_of="&PyTuple_Type", c_default="NULL") = () freevars: object(subclass_of="&PyTuple_Type", c_default="NULL") = ()
cellvars: object(subclass_of="&PyTuple_Type", c_default="NULL") = () cellvars: object(subclass_of="&PyTuple_Type", c_default="NULL") = ()
@ -1241,7 +1248,7 @@ code_new_impl(PyTypeObject *type, int argcount, int posonlyargcount,
PyObject *endlinetable, PyObject *columntable, PyObject *endlinetable, PyObject *columntable,
PyObject *exceptiontable, PyObject *freevars, PyObject *exceptiontable, PyObject *freevars,
PyObject *cellvars) PyObject *cellvars)
/*[clinic end generated code: output=e1d2086aa8da7c08 input=ba12d68bd8fa0620]*/ /*[clinic end generated code: output=e1d2086aa8da7c08 input=a06cd92369134063]*/
{ {
PyObject *co = NULL; PyObject *co = NULL;
PyObject *ournames = NULL; PyObject *ournames = NULL;
@ -1282,6 +1289,17 @@ code_new_impl(PyTypeObject *type, int argcount, int posonlyargcount,
goto cleanup; goto cleanup;
} }
if (!Py_IsNone(endlinetable) && !PyBytes_Check(endlinetable)) {
PyErr_SetString(PyExc_ValueError,
"code: endlinetable must be None or bytes");
goto cleanup;
}
if (!Py_IsNone(columntable) && !PyBytes_Check(columntable)) {
PyErr_SetString(PyExc_ValueError,
"code: columntable must be None or bytes");
goto cleanup;
}
ournames = validate_and_copy_tuple(names); ournames = validate_and_copy_tuple(names);
if (ournames == NULL) if (ournames == NULL)
goto cleanup; goto cleanup;
@ -1585,8 +1603,8 @@ code.replace
co_name: unicode(c_default="self->co_name") = None co_name: unicode(c_default="self->co_name") = None
co_qualname: unicode(c_default="self->co_qualname") = None co_qualname: unicode(c_default="self->co_qualname") = None
co_linetable: PyBytesObject(c_default="(PyBytesObject *)self->co_linetable") = None co_linetable: PyBytesObject(c_default="(PyBytesObject *)self->co_linetable") = None
co_endlinetable: PyBytesObject(c_default="(PyBytesObject *)self->co_endlinetable") = None co_endlinetable: object(c_default="self->co_endlinetable") = None
co_columntable: PyBytesObject(c_default="(PyBytesObject *)self->co_columntable") = None co_columntable: object(c_default="self->co_columntable") = None
co_exceptiontable: PyBytesObject(c_default="(PyBytesObject *)self->co_exceptiontable") = None co_exceptiontable: PyBytesObject(c_default="(PyBytesObject *)self->co_exceptiontable") = None
Return a copy of the code object with new values for the specified fields. Return a copy of the code object with new values for the specified fields.
@ -1601,11 +1619,9 @@ code_replace_impl(PyCodeObject *self, int co_argcount,
PyObject *co_varnames, PyObject *co_freevars, PyObject *co_varnames, PyObject *co_freevars,
PyObject *co_cellvars, PyObject *co_filename, PyObject *co_cellvars, PyObject *co_filename,
PyObject *co_name, PyObject *co_qualname, PyObject *co_name, PyObject *co_qualname,
PyBytesObject *co_linetable, PyBytesObject *co_linetable, PyObject *co_endlinetable,
PyBytesObject *co_endlinetable, PyObject *co_columntable, PyBytesObject *co_exceptiontable)
PyBytesObject *co_columntable, /*[clinic end generated code: output=f046bf0be3bab91f input=a63d09f248f00794]*/
PyBytesObject *co_exceptiontable)
/*[clinic end generated code: output=da699b6261fddc13 input=a8e93823df0aec35]*/
{ {
#define CHECK_INT_ARG(ARG) \ #define CHECK_INT_ARG(ARG) \
if (ARG < 0) { \ if (ARG < 0) { \
@ -1657,6 +1673,17 @@ code_replace_impl(PyCodeObject *self, int co_argcount,
co_freevars = freevars; co_freevars = freevars;
} }
if (!Py_IsNone(co_endlinetable) && !PyBytes_Check(co_endlinetable)) {
PyErr_SetString(PyExc_ValueError,
"co_endlinetable must be None or bytes");
goto error;
}
if (!Py_IsNone(co_columntable) && !PyBytes_Check(co_columntable)) {
PyErr_SetString(PyExc_ValueError,
"co_columntable must be None or bytes");
goto error;
}
co = PyCode_NewWithPosOnlyArgs( co = PyCode_NewWithPosOnlyArgs(
co_argcount, co_posonlyargcount, co_kwonlyargcount, co_nlocals, co_argcount, co_posonlyargcount, co_kwonlyargcount, co_nlocals,
co_stacksize, co_flags, (PyObject*)co_code, co_consts, co_names, co_stacksize, co_flags, (PyObject*)co_code, co_consts, co_names,

View file

@ -528,6 +528,9 @@ static int test_init_from_config(void)
putenv("PYTHONPROFILEIMPORTTIME=0"); putenv("PYTHONPROFILEIMPORTTIME=0");
config.import_time = 1; config.import_time = 1;
putenv("PYTHONNODEBUGRANGES=0");
config.no_debug_ranges = 1;
config.show_ref_count = 1; config.show_ref_count = 1;
/* FIXME: test dump_refs: bpo-34223 */ /* FIXME: test dump_refs: bpo-34223 */
@ -686,6 +689,7 @@ static void set_most_env_vars(void)
putenv("PYTHONMALLOC=malloc"); putenv("PYTHONMALLOC=malloc");
putenv("PYTHONTRACEMALLOC=2"); putenv("PYTHONTRACEMALLOC=2");
putenv("PYTHONPROFILEIMPORTTIME=1"); putenv("PYTHONPROFILEIMPORTTIME=1");
putenv("PYTHONNODEBUGRANGES=1");
putenv("PYTHONMALLOCSTATS=1"); putenv("PYTHONMALLOCSTATS=1");
putenv("PYTHONUTF8=1"); putenv("PYTHONUTF8=1");
putenv("PYTHONVERBOSE=1"); putenv("PYTHONVERBOSE=1");

View file

@ -95,6 +95,11 @@ static const char usage_3[] = "\
-X pycache_prefix=PATH: enable writing .pyc files to a parallel tree rooted at the\n\ -X pycache_prefix=PATH: enable writing .pyc files to a parallel tree rooted at the\n\
given directory instead of to the code tree\n\ given directory instead of to the code tree\n\
-X warn_default_encoding: enable opt-in EncodingWarning for 'encoding=None'\n\ -X warn_default_encoding: enable opt-in EncodingWarning for 'encoding=None'\n\
-X no_debug_ranges: disable the inclusion of the tables mapping extra location \n\
information (end line, start column offset and end column offset) to every \n\
instruction in code objects. This is useful when smaller code objects and pyc \n\
files are desired as well as supressing the extra visual location indicators \n\
when the interpreter displays tracebacks.\n\
\n\ \n\
--check-hash-based-pycs always|default|never:\n\ --check-hash-based-pycs always|default|never:\n\
control how Python invalidates hash-based .pyc files\n\ control how Python invalidates hash-based .pyc files\n\
@ -131,7 +136,12 @@ static const char usage_6[] =
" debugger. It can be set to the callable of your debugger of choice.\n" " debugger. It can be set to the callable of your debugger of choice.\n"
"PYTHONDEVMODE: enable the development mode.\n" "PYTHONDEVMODE: enable the development mode.\n"
"PYTHONPYCACHEPREFIX: root directory for bytecode cache (pyc) files.\n" "PYTHONPYCACHEPREFIX: root directory for bytecode cache (pyc) files.\n"
"PYTHONWARNDEFAULTENCODING: enable opt-in EncodingWarning for 'encoding=None'.\n"; "PYTHONWARNDEFAULTENCODING: enable opt-in EncodingWarning for 'encoding=None'.\n"
"PYTHONNODEBUGRANGES: If this variable is set, it disables the inclusion of the \n"
" tables mapping extra location information (end line, start column offset \n"
" and end column offset) to every instruction in code objects. This is useful \n"
" when smaller cothe de objects and pyc files are desired as well as supressing the \n"
" extra visual location indicators when the interpreter displays tracebacks.\n";
#if defined(MS_WINDOWS) #if defined(MS_WINDOWS)
# define PYTHONHOMEHELP "<prefix>\\python{major}{minor}" # define PYTHONHOMEHELP "<prefix>\\python{major}{minor}"
@ -597,6 +607,7 @@ config_check_consistency(const PyConfig *config)
assert(config->faulthandler >= 0); assert(config->faulthandler >= 0);
assert(config->tracemalloc >= 0); assert(config->tracemalloc >= 0);
assert(config->import_time >= 0); assert(config->import_time >= 0);
assert(config->no_debug_ranges >= 0);
assert(config->show_ref_count >= 0); assert(config->show_ref_count >= 0);
assert(config->dump_refs >= 0); assert(config->dump_refs >= 0);
assert(config->malloc_stats >= 0); assert(config->malloc_stats >= 0);
@ -884,6 +895,7 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2)
COPY_ATTR(faulthandler); COPY_ATTR(faulthandler);
COPY_ATTR(tracemalloc); COPY_ATTR(tracemalloc);
COPY_ATTR(import_time); COPY_ATTR(import_time);
COPY_ATTR(no_debug_ranges);
COPY_ATTR(show_ref_count); COPY_ATTR(show_ref_count);
COPY_ATTR(dump_refs); COPY_ATTR(dump_refs);
COPY_ATTR(malloc_stats); COPY_ATTR(malloc_stats);
@ -988,6 +1000,7 @@ _PyConfig_AsDict(const PyConfig *config)
SET_ITEM_INT(faulthandler); SET_ITEM_INT(faulthandler);
SET_ITEM_INT(tracemalloc); SET_ITEM_INT(tracemalloc);
SET_ITEM_INT(import_time); SET_ITEM_INT(import_time);
SET_ITEM_INT(no_debug_ranges);
SET_ITEM_INT(show_ref_count); SET_ITEM_INT(show_ref_count);
SET_ITEM_INT(dump_refs); SET_ITEM_INT(dump_refs);
SET_ITEM_INT(malloc_stats); SET_ITEM_INT(malloc_stats);
@ -1264,6 +1277,7 @@ _PyConfig_FromDict(PyConfig *config, PyObject *dict)
GET_UINT(faulthandler); GET_UINT(faulthandler);
GET_UINT(tracemalloc); GET_UINT(tracemalloc);
GET_UINT(import_time); GET_UINT(import_time);
GET_UINT(no_debug_ranges);
GET_UINT(show_ref_count); GET_UINT(show_ref_count);
GET_UINT(dump_refs); GET_UINT(dump_refs);
GET_UINT(malloc_stats); GET_UINT(malloc_stats);
@ -1802,6 +1816,11 @@ config_read_complex_options(PyConfig *config)
config->import_time = 1; config->import_time = 1;
} }
if (config_get_env(config, "PYTHONNODEBUGRANGES")
|| config_get_xoption(config, L"no_debug_ranges")) {
config->no_debug_ranges = 1;
}
PyStatus status; PyStatus status;
if (config->tracemalloc < 0) { if (config->tracemalloc < 0) {
status = config_init_tracemalloc(config); status = config_init_tracemalloc(config);