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

pablogsal webhook-mailer at python.org
Wed Jul 7 15:07:21 EDT 2021


https://github.com/python/cpython/commit/4823d9a51281ebbc8e8d82a0dd3edc7d13ea8ac7
commit: 4823d9a51281ebbc8e8d82a0dd3edc7d13ea8ac7
branch: main
author: Ammar Askar <ammar at ammaraskar.com>
committer: pablogsal <Pablogsal at gmail.com>
date: 2021-07-07T20:07:12+01:00
summary:

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

Co-authored-by: Pablo Galindo <Pablogsal at gmail.com>
Co-authored-by: Batuhan Taskaya <batuhanosmantaskaya at gmail.com>
Co-authored-by: Ammar Askar <ammar at ammaraskar.com>

files:
M Doc/c-api/init_config.rst
M Doc/using/cmdline.rst
M Include/cpython/initconfig.h
M Lib/idlelib/idle_test/test_run.py
M Lib/test/_test_embed_set_config.py
M Lib/test/support/__init__.py
M Lib/test/test_code.py
M Lib/test/test_compile.py
M Lib/test/test_dis.py
M Lib/test/test_doctest.py
M Lib/test/test_embed.py
M Lib/test/test_marshal.py
M Lib/test/test_traceback.py
M Lib/test/test_zipimport.py
M Objects/clinic/codeobject.c.h
M Objects/codeobject.c
M Programs/_testembed.c
M Python/initconfig.c

diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst
index fe5b83aa8dc95a..2e52679ebc5b5d 100644
--- a/Doc/c-api/init_config.rst
+++ b/Doc/c-api/init_config.rst
@@ -596,6 +596,16 @@ PyConfig
 
       .. 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
 
       Control the validation behavior of hash-based ``.pyc`` files:
diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst
index 25e05d413de877..98fdba24e28bf4 100644
--- a/Doc/using/cmdline.rst
+++ b/Doc/using/cmdline.rst
@@ -474,6 +474,12 @@ Miscellaneous options
    * ``-X warn_default_encoding`` issues a :class:`EncodingWarning` when the
      locale-specific default encoding is used for opening files.
      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
    :data:`sys._xoptions` dictionary.
@@ -509,6 +515,9 @@ Miscellaneous options
    .. deprecated-removed:: 3.9 3.10
       The ``-X oldparser`` option.
 
+   .. versionadded:: 3.11
+      The ``-X no_debug_ranges`` option.
+
 
 Options you shouldn't use
 ~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -936,6 +945,17 @@ conflict.
 
    .. 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
 ~~~~~~~~~~~~~~~~~~~~
diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h
index 09f9a2947efef3..5f03b8c57f8bc8 100644
--- a/Include/cpython/initconfig.h
+++ b/Include/cpython/initconfig.h
@@ -140,6 +140,7 @@ typedef struct PyConfig {
     int faulthandler;
     int tracemalloc;
     int import_time;
+    int no_debug_ranges;
     int show_ref_count;
     int dump_refs;
     int malloc_stats;
diff --git a/Lib/idlelib/idle_test/test_run.py b/Lib/idlelib/idle_test/test_run.py
index b289fa7cbd6481..d859ffc153fcdd 100644
--- a/Lib/idlelib/idle_test/test_run.py
+++ b/Lib/idlelib/idle_test/test_run.py
@@ -3,7 +3,7 @@
 from idlelib import run
 import io
 import sys
-from test.support import captured_output, captured_stderr
+from test.support import captured_output, captured_stderr, has_no_debug_ranges
 import unittest
 from unittest import mock
 import idlelib
@@ -33,9 +33,14 @@ def __eq__(self, other):
                         run.print_exception()
 
         tb = output.getvalue().strip().splitlines()
-        self.assertEqual(13, len(tb))
-        self.assertIn('UnhashableException: ex2', tb[4])
-        self.assertIn('UnhashableException: ex1', tb[12])
+        if has_no_debug_ranges():
+            self.assertEqual(11, len(tb))
+            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"),
             ('abc', NameError, "name 'abc' is not defined. "
diff --git a/Lib/test/_test_embed_set_config.py b/Lib/test/_test_embed_set_config.py
index 82c5d829823652..23c927e2646bc0 100644
--- a/Lib/test/_test_embed_set_config.py
+++ b/Lib/test/_test_embed_set_config.py
@@ -61,6 +61,7 @@ def test_set_invalid(self):
             'faulthandler',
             'tracemalloc',
             'import_time',
+            'no_debug_ranges',
             'show_ref_count',
             'dump_refs',
             'malloc_stats',
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index 933c2c99b292e5..59b8f441bae8dc 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -415,6 +415,14 @@ def requires_lzma(reason='requires lzma'):
         lzma = None
     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 C API')
 
diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py
index ccb8da636f647a..988790b3a593e3 100644
--- a/Lib/test/test_code.py
+++ b/Lib/test/test_code.py
@@ -137,7 +137,8 @@
 except ImportError:
     ctypes = None
 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):
@@ -325,6 +326,7 @@ def func():
         new_code = code = func.__code__.replace(co_linetable=b'')
         self.assertEqual(list(new_code.co_lines()), [])
 
+    @requires_debug_ranges()
     def test_co_positions_artificial_instructions(self):
         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.
 
+    @requires_debug_ranges()
     def test_co_positions_empty_linetable(self):
         def func():
             x = 1
@@ -382,6 +408,7 @@ def func():
             self.assertIsNone(line)
             self.assertEqual(end_line, new_code.co_firstlineno + 1)
 
+    @requires_debug_ranges()
     def test_co_positions_empty_endlinetable(self):
         def func():
             x = 1
@@ -390,6 +417,7 @@ def func():
             self.assertEqual(line, new_code.co_firstlineno + 1)
             self.assertIsNone(end_line)
 
+    @requires_debug_ranges()
     def test_co_positions_empty_columntable(self):
         def func():
             x = 1
diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py
index bc8c57d9e1d2f4..c994741176dc24 100644
--- a/Lib/test/test_compile.py
+++ b/Lib/test/test_compile.py
@@ -9,7 +9,7 @@
 import types
 import textwrap
 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
 
 
@@ -985,7 +985,7 @@ def if_else_break():
             elif instr.opname in HANDLED_JUMPS:
                 self.assertNotEqual(instr.arg, (line + 1)*INSTR_SIZE)
 
-
+ at requires_debug_ranges()
 class TestSourcePositions(unittest.TestCase):
     # Ensure that compiled code snippets have correct line and column numbers
     # in `co_positions()`.
diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py
index 54a123ed4cf277..d1f1eeebb21383 100644
--- a/Lib/test/test_dis.py
+++ b/Lib/test/test_dis.py
@@ -1,6 +1,6 @@
 # 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
 import unittest
 import sys
@@ -1192,6 +1192,7 @@ def test_jumpy(self):
         actual = dis.get_instructions(jumpy, first_line=expected_jumpy_line)
         self.assertInstructionsEqual(list(actual), expected_opinfo_jumpy)
 
+    @requires_debug_ranges()
     def test_co_positions(self):
         code = compile('f(\n  x, y, z\n)', '<test>', 'exec')
         positions = [
@@ -1210,6 +1211,7 @@ def test_co_positions(self):
         ]
         self.assertEqual(positions, expected)
 
+    @requires_debug_ranges()
     def test_co_positions_missing_info(self):
         code = compile('x, y, z', '<test>', 'exec')
         code_without_column_table = code.replace(co_columntable=b'')
diff --git a/Lib/test/test_doctest.py b/Lib/test/test_doctest.py
index 06d9d5d4ade83c..642188fd5ea48e 100644
--- a/Lib/test/test_doctest.py
+++ b/Lib/test/test_doctest.py
@@ -2808,10 +2808,12 @@ def test_testmod(): r"""
 
 try:
     os.fsencode("foo-bär at baz.py")
+    supports_unicode = True
 except UnicodeEncodeError:
     # Skip the test: the filesystem encoding is unable to encode the filename
-    pass
-else:
+    supports_unicode = False
+
+if supports_unicode and not support.has_no_debug_ranges():
     def test_unicode(): """
 Check doctest with a non-ascii filename:
 
diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py
index c50defd9a6a3fb..8e3dd50c1f8be0 100644
--- a/Lib/test/test_embed.py
+++ b/Lib/test/test_embed.py
@@ -369,6 +369,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
         'faulthandler': 0,
         'tracemalloc': 0,
         'import_time': 0,
+        'no_debug_ranges': 0,
         'show_ref_count': 0,
         'dump_refs': 0,
         'malloc_stats': 0,
@@ -798,6 +799,7 @@ def test_init_from_config(self):
             'hash_seed': 123,
             'tracemalloc': 2,
             'import_time': 1,
+            'no_debug_ranges': 1,
             'show_ref_count': 1,
             'malloc_stats': 1,
 
@@ -858,6 +860,7 @@ def test_init_compat_env(self):
             'hash_seed': 42,
             'tracemalloc': 2,
             'import_time': 1,
+            'no_debug_ranges': 1,
             'malloc_stats': 1,
             'inspect': 1,
             'optimization_level': 2,
@@ -887,6 +890,7 @@ def test_init_python_env(self):
             'hash_seed': 42,
             'tracemalloc': 2,
             'import_time': 1,
+            'no_debug_ranges': 1,
             'malloc_stats': 1,
             'inspect': 1,
             'optimization_level': 2,
diff --git a/Lib/test/test_marshal.py b/Lib/test/test_marshal.py
index 7bcf8e8399a817..152301f16a95b3 100644
--- a/Lib/test/test_marshal.py
+++ b/Lib/test/test_marshal.py
@@ -1,5 +1,6 @@
 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 io
 import marshal
@@ -7,6 +8,7 @@
 import unittest
 import os
 import types
+import textwrap
 
 try:
     import _testcapi
@@ -126,6 +128,31 @@ def test_different_filenames(self):
         self.assertEqual(co1.co_filename, "f1")
         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
     def test_same_filename_used(self):
         s = """def f(): pass\ndef g(): pass"""
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index 54f592a8ea5001..610ae3f407c140 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -8,9 +8,10 @@
 import unittest
 import re
 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.script_helper import assert_python_ok
+from test.support.script_helper import assert_python_ok, assert_python_failure
 import textwrap
 
 import traceback
@@ -75,6 +76,49 @@ def test_nocaret(self):
         self.assertEqual(len(err), 3)
         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):
         err = self.get_exception_format(self.syntax_error_bad_indentation,
                                         IndentationError)
@@ -155,9 +199,10 @@ def do_test(firstlines, message, charset, lineno):
             self.assertTrue(stdout[2].endswith(err_line),
                 "Invalid traceback line: {0!r} instead of {1!r}".format(
                     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(
-                    stdout[4], err_msg))
+                    actual_err_msg, err_msg))
 
         do_test("", "foo", "ascii", 3)
         for charset in ("ascii", "iso-8859-1", "utf-8", "GBK"):
@@ -273,6 +318,7 @@ def test_signatures(self):
             '(exc, /, value=<implicit>)')
 
 
+ at requires_debug_ranges()
 class TracebackErrorLocationCaretTests(unittest.TestCase):
     """
     Tests for printing code error expressions as part of PEP 657
@@ -362,6 +408,7 @@ def f_with_multiline():
 
 
 @cpython_only
+ at requires_debug_ranges()
 class CPythonTracebackErrorCaretTests(TracebackErrorLocationCaretTests):
     """
     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.
         tb_lines = python_fmt.splitlines()
-        self.assertEqual(len(tb_lines), 7)
         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(location.startswith('  File'))
         self.assertTrue(source_line.startswith('    raise'))
@@ -668,10 +719,12 @@ def h(count=10):
         actual = stderr_g.getvalue().splitlines()
         self.assertEqual(actual, expected)
 
+    @requires_debug_ranges()
     def test_recursive_traceback_python(self):
         self._check_recursive_traceback_display(traceback.print_exc)
 
     @cpython_only
+    @requires_debug_ranges()
     def test_recursive_traceback_cpython_internal(self):
         from _testcapi import exception_print
         def render_exc():
@@ -713,11 +766,16 @@ def __eq__(self, other):
             exception_print(exc_val)
 
         tb = stderr_f.getvalue().strip().splitlines()
-        self.assertEqual(13, len(tb))
-        self.assertEqual(context_message.strip(), tb[6])
-        self.assertIn('UnhashableException: ex2', tb[4])
-        self.assertIn('UnhashableException: ex1', tb[12])
-
+        if has_no_debug_ranges():
+            self.assertEqual(11, len(tb))
+            self.assertEqual(context_message.strip(), tb[5])
+            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 = (
     "\nThe above exception was the direct cause "
@@ -746,8 +804,12 @@ def zero_div(self):
 
     def check_zero_div(self, msg):
         lines = msg.splitlines()
-        self.assertTrue(lines[-4].startswith('  File'))
-        self.assertIn('1/0 # In zero_div', lines[-3])
+        if has_no_debug_ranges():
+            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])
 
     def test_simple(self):
@@ -756,11 +818,15 @@ def test_simple(self):
         except ZeroDivisionError as _:
             e = _
         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[1].startswith('  File'))
         self.assertIn('1/0 # Marker', lines[2])
-        self.assertTrue(lines[4].startswith('ZeroDivisionError'))
 
     def test_cause(self):
         def inner_raise():
@@ -799,11 +865,15 @@ def test_context_suppression(self):
         except ZeroDivisionError as _:
             e = _
         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[1].startswith('  File'))
         self.assertIn('ZeroDivisionError from None', lines[2])
-        self.assertTrue(lines[4].startswith('ZeroDivisionError'))
 
     def test_cause_and_context(self):
         # 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)
         self.assertEqual(list(exc.format()), ["Exception: haven\n"])
 
+    @requires_debug_ranges()
     def test_print(self):
         def f():
             x = 12
diff --git a/Lib/test/test_zipimport.py b/Lib/test/test_zipimport.py
index 861ebe347b3ee9..4e88902e3e31d8 100644
--- a/Lib/test/test_zipimport.py
+++ b/Lib/test/test_zipimport.py
@@ -718,6 +718,7 @@ def doTraceback(self, module):
             print_tb(tb, 1, s)
             self.assertTrue(s.getvalue().endswith(
                 '    def do_raise(): raise TypeError\n'
+                '' if support.has_no_debug_ranges() else
                 '                    ^^^^^^^^^^^^^^^\n'
             ))
         else:
diff --git a/Objects/clinic/codeobject.c.h b/Objects/clinic/codeobject.c.h
index d8a95cabebd399..ac844b133c4878 100644
--- a/Objects/clinic/codeobject.c.h
+++ b/Objects/clinic/codeobject.c.h
@@ -130,15 +130,7 @@ code_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
         goto exit;
     }
     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);
-    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);
     if (!PyBytes_Check(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_cellvars, PyObject *co_filename,
                   PyObject *co_name, PyObject *co_qualname,
-                  PyBytesObject *co_linetable,
-                  PyBytesObject *co_endlinetable,
-                  PyBytesObject *co_columntable,
-                  PyBytesObject *co_exceptiontable);
+                  PyBytesObject *co_linetable, PyObject *co_endlinetable,
+                  PyObject *co_columntable, PyBytesObject *co_exceptiontable);
 
 static PyObject *
 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_qualname = self->co_qualname;
     PyBytesObject *co_linetable = (PyBytesObject *)self->co_linetable;
-    PyBytesObject *co_endlinetable = (PyBytesObject *)self->co_endlinetable;
-    PyBytesObject *co_columntable = (PyBytesObject *)self->co_columntable;
+    PyObject *co_endlinetable = self->co_endlinetable;
+    PyObject *co_columntable = self->co_columntable;
     PyBytesObject *co_exceptiontable = (PyBytesObject *)self->co_exceptiontable;
 
     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 (!PyBytes_Check(args[17])) {
-            _PyArg_BadArgument("replace", "argument 'co_endlinetable'", "bytes", args[17]);
-            goto exit;
-        }
-        co_endlinetable = (PyBytesObject *)args[17];
+        co_endlinetable = args[17];
         if (!--noptargs) {
             goto skip_optional_kwonly;
         }
     }
     if (args[18]) {
-        if (!PyBytes_Check(args[18])) {
-            _PyArg_BadArgument("replace", "argument 'co_columntable'", "bytes", args[18]);
-            goto exit;
-        }
-        co_columntable = (PyBytesObject *)args[18];
+        co_columntable = args[18];
         if (!--noptargs) {
             goto skip_optional_kwonly;
         }
@@ -473,4 +455,4 @@ code__varname_from_oparg(PyCodeObject *self, PyObject *const *args, Py_ssize_t n
 exit:
     return return_value;
 }
-/*[clinic end generated code: output=12b394f0212b1c1e input=a9049054013a1b77]*/
+/*[clinic end generated code: output=18b9ddc86714e56e input=a9049054013a1b77]*/
diff --git a/Objects/codeobject.c b/Objects/codeobject.c
index 140d5a0231da7e..a5120ec01f5cb0 100644
--- a/Objects/codeobject.c
+++ b/Objects/codeobject.c
@@ -379,6 +379,13 @@ _PyCode_New(struct _PyCodeConstructor *con)
         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);
     if (co == NULL) {
         PyErr_NoMemory();
@@ -1222,8 +1229,8 @@ code.__new__ as code_new
     qualname: unicode
     firstlineno: int
     linetable: object(subclass_of="&PyBytes_Type")
-    endlinetable: object(subclass_of="&PyBytes_Type")
-    columntable: object(subclass_of="&PyBytes_Type")
+    endlinetable: object
+    columntable: object
     exceptiontable: object(subclass_of="&PyBytes_Type")
     freevars: 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 *exceptiontable, PyObject *freevars,
               PyObject *cellvars)
-/*[clinic end generated code: output=e1d2086aa8da7c08 input=ba12d68bd8fa0620]*/
+/*[clinic end generated code: output=e1d2086aa8da7c08 input=a06cd92369134063]*/
 {
     PyObject *co = NULL;
     PyObject *ournames = NULL;
@@ -1282,6 +1289,17 @@ code_new_impl(PyTypeObject *type, int argcount, int posonlyargcount,
         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);
     if (ournames == NULL)
         goto cleanup;
@@ -1585,8 +1603,8 @@ code.replace
     co_name: unicode(c_default="self->co_name") = None
     co_qualname: unicode(c_default="self->co_qualname") = None
     co_linetable: PyBytesObject(c_default="(PyBytesObject *)self->co_linetable") = None
-    co_endlinetable: PyBytesObject(c_default="(PyBytesObject *)self->co_endlinetable") = None
-    co_columntable: PyBytesObject(c_default="(PyBytesObject *)self->co_columntable") = None
+    co_endlinetable: object(c_default="self->co_endlinetable") = None
+    co_columntable: object(c_default="self->co_columntable") = 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.
@@ -1601,11 +1619,9 @@ code_replace_impl(PyCodeObject *self, int co_argcount,
                   PyObject *co_varnames, PyObject *co_freevars,
                   PyObject *co_cellvars, PyObject *co_filename,
                   PyObject *co_name, PyObject *co_qualname,
-                  PyBytesObject *co_linetable,
-                  PyBytesObject *co_endlinetable,
-                  PyBytesObject *co_columntable,
-                  PyBytesObject *co_exceptiontable)
-/*[clinic end generated code: output=da699b6261fddc13 input=a8e93823df0aec35]*/
+                  PyBytesObject *co_linetable, PyObject *co_endlinetable,
+                  PyObject *co_columntable, PyBytesObject *co_exceptiontable)
+/*[clinic end generated code: output=f046bf0be3bab91f input=a63d09f248f00794]*/
 {
 #define CHECK_INT_ARG(ARG) \
         if (ARG < 0) { \
@@ -1657,6 +1673,17 @@ code_replace_impl(PyCodeObject *self, int co_argcount,
         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_argcount, co_posonlyargcount, co_kwonlyargcount, co_nlocals,
         co_stacksize, co_flags, (PyObject*)co_code, co_consts, co_names,
diff --git a/Programs/_testembed.c b/Programs/_testembed.c
index 64a8714db02f3c..73e1f382d7024d 100644
--- a/Programs/_testembed.c
+++ b/Programs/_testembed.c
@@ -528,6 +528,9 @@ static int test_init_from_config(void)
     putenv("PYTHONPROFILEIMPORTTIME=0");
     config.import_time = 1;
 
+    putenv("PYTHONNODEBUGRANGES=0");
+    config.no_debug_ranges = 1;
+
     config.show_ref_count = 1;
     /* FIXME: test dump_refs: bpo-34223 */
 
@@ -686,6 +689,7 @@ static void set_most_env_vars(void)
     putenv("PYTHONMALLOC=malloc");
     putenv("PYTHONTRACEMALLOC=2");
     putenv("PYTHONPROFILEIMPORTTIME=1");
+    putenv("PYTHONNODEBUGRANGES=1");
     putenv("PYTHONMALLOCSTATS=1");
     putenv("PYTHONUTF8=1");
     putenv("PYTHONVERBOSE=1");
diff --git a/Python/initconfig.c b/Python/initconfig.c
index 27ae48dd3c97c8..d328f227cead25 100644
--- a/Python/initconfig.c
+++ b/Python/initconfig.c
@@ -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\
              given directory instead of to the code tree\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\
 --check-hash-based-pycs always|default|never:\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"
 "PYTHONDEVMODE: enable the development mode.\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)
 #  define PYTHONHOMEHELP "<prefix>\\python{major}{minor}"
@@ -597,6 +607,7 @@ config_check_consistency(const PyConfig *config)
     assert(config->faulthandler >= 0);
     assert(config->tracemalloc >= 0);
     assert(config->import_time >= 0);
+    assert(config->no_debug_ranges >= 0);
     assert(config->show_ref_count >= 0);
     assert(config->dump_refs >= 0);
     assert(config->malloc_stats >= 0);
@@ -884,6 +895,7 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2)
     COPY_ATTR(faulthandler);
     COPY_ATTR(tracemalloc);
     COPY_ATTR(import_time);
+    COPY_ATTR(no_debug_ranges);
     COPY_ATTR(show_ref_count);
     COPY_ATTR(dump_refs);
     COPY_ATTR(malloc_stats);
@@ -988,6 +1000,7 @@ _PyConfig_AsDict(const PyConfig *config)
     SET_ITEM_INT(faulthandler);
     SET_ITEM_INT(tracemalloc);
     SET_ITEM_INT(import_time);
+    SET_ITEM_INT(no_debug_ranges);
     SET_ITEM_INT(show_ref_count);
     SET_ITEM_INT(dump_refs);
     SET_ITEM_INT(malloc_stats);
@@ -1264,6 +1277,7 @@ _PyConfig_FromDict(PyConfig *config, PyObject *dict)
     GET_UINT(faulthandler);
     GET_UINT(tracemalloc);
     GET_UINT(import_time);
+    GET_UINT(no_debug_ranges);
     GET_UINT(show_ref_count);
     GET_UINT(dump_refs);
     GET_UINT(malloc_stats);
@@ -1802,6 +1816,11 @@ config_read_complex_options(PyConfig *config)
         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;
     if (config->tracemalloc < 0) {
         status = config_init_tracemalloc(config);



More information about the Python-checkins mailing list