python (3.12.0)

(root)/
lib/
python3.12/
test/
test_code.py
       1  """This module includes tests of the code object representation.
       2  
       3  >>> def f(x):
       4  ...     def g(y):
       5  ...         return x + y
       6  ...     return g
       7  ...
       8  
       9  >>> dump(f.__code__)
      10  name: f
      11  argcount: 1
      12  posonlyargcount: 0
      13  kwonlyargcount: 0
      14  names: ()
      15  varnames: ('x', 'g')
      16  cellvars: ('x',)
      17  freevars: ()
      18  nlocals: 2
      19  flags: 3
      20  consts: ('None', '<code object g>')
      21  
      22  >>> dump(f(4).__code__)
      23  name: g
      24  argcount: 1
      25  posonlyargcount: 0
      26  kwonlyargcount: 0
      27  names: ()
      28  varnames: ('y',)
      29  cellvars: ()
      30  freevars: ('x',)
      31  nlocals: 1
      32  flags: 19
      33  consts: ('None',)
      34  
      35  >>> def h(x, y):
      36  ...     a = x + y
      37  ...     b = x - y
      38  ...     c = a * b
      39  ...     return c
      40  ...
      41  
      42  >>> dump(h.__code__)
      43  name: h
      44  argcount: 2
      45  posonlyargcount: 0
      46  kwonlyargcount: 0
      47  names: ()
      48  varnames: ('x', 'y', 'a', 'b', 'c')
      49  cellvars: ()
      50  freevars: ()
      51  nlocals: 5
      52  flags: 3
      53  consts: ('None',)
      54  
      55  >>> def attrs(obj):
      56  ...     print(obj.attr1)
      57  ...     print(obj.attr2)
      58  ...     print(obj.attr3)
      59  
      60  >>> dump(attrs.__code__)
      61  name: attrs
      62  argcount: 1
      63  posonlyargcount: 0
      64  kwonlyargcount: 0
      65  names: ('print', 'attr1', 'attr2', 'attr3')
      66  varnames: ('obj',)
      67  cellvars: ()
      68  freevars: ()
      69  nlocals: 1
      70  flags: 3
      71  consts: ('None',)
      72  
      73  >>> def optimize_away():
      74  ...     'doc string'
      75  ...     'not a docstring'
      76  ...     53
      77  ...     0x53
      78  
      79  >>> dump(optimize_away.__code__)
      80  name: optimize_away
      81  argcount: 0
      82  posonlyargcount: 0
      83  kwonlyargcount: 0
      84  names: ()
      85  varnames: ()
      86  cellvars: ()
      87  freevars: ()
      88  nlocals: 0
      89  flags: 3
      90  consts: ("'doc string'", 'None')
      91  
      92  >>> def keywordonly_args(a,b,*,k1):
      93  ...     return a,b,k1
      94  ...
      95  
      96  >>> dump(keywordonly_args.__code__)
      97  name: keywordonly_args
      98  argcount: 2
      99  posonlyargcount: 0
     100  kwonlyargcount: 1
     101  names: ()
     102  varnames: ('a', 'b', 'k1')
     103  cellvars: ()
     104  freevars: ()
     105  nlocals: 3
     106  flags: 3
     107  consts: ('None',)
     108  
     109  >>> def posonly_args(a,b,/,c):
     110  ...     return a,b,c
     111  ...
     112  
     113  >>> dump(posonly_args.__code__)
     114  name: posonly_args
     115  argcount: 3
     116  posonlyargcount: 2
     117  kwonlyargcount: 0
     118  names: ()
     119  varnames: ('a', 'b', 'c')
     120  cellvars: ()
     121  freevars: ()
     122  nlocals: 3
     123  flags: 3
     124  consts: ('None',)
     125  
     126  """
     127  
     128  import inspect
     129  import sys
     130  import threading
     131  import doctest
     132  import unittest
     133  import textwrap
     134  import weakref
     135  import dis
     136  
     137  try:
     138      import ctypes
     139  except ImportError:
     140      ctypes = None
     141  from test.support import (cpython_only,
     142                            check_impl_detail, requires_debug_ranges,
     143                            gc_collect)
     144  from test.support.script_helper import assert_python_ok
     145  from test.support import threading_helper
     146  from opcode import opmap, opname
     147  COPY_FREE_VARS = opmap['COPY_FREE_VARS']
     148  
     149  
     150  def consts(t):
     151      """Yield a doctest-safe sequence of object reprs."""
     152      for elt in t:
     153          r = repr(elt)
     154          if r.startswith("<code object"):
     155              yield "<code object %s>" % elt.co_name
     156          else:
     157              yield r
     158  
     159  def dump(co):
     160      """Print out a text representation of a code object."""
     161      for attr in ["name", "argcount", "posonlyargcount",
     162                   "kwonlyargcount", "names", "varnames",
     163                   "cellvars", "freevars", "nlocals", "flags"]:
     164          print("%s: %s" % (attr, getattr(co, "co_" + attr)))
     165      print("consts:", tuple(consts(co.co_consts)))
     166  
     167  # Needed for test_closure_injection below
     168  # Defined at global scope to avoid implicitly closing over __class__
     169  def external_getitem(self, i):
     170      return f"Foreign getitem: {super().__getitem__(i)}"
     171  
     172  class ESC[4;38;5;81mCodeTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     173  
     174      @cpython_only
     175      def test_newempty(self):
     176          import _testcapi
     177          co = _testcapi.code_newempty("filename", "funcname", 15)
     178          self.assertEqual(co.co_filename, "filename")
     179          self.assertEqual(co.co_name, "funcname")
     180          self.assertEqual(co.co_firstlineno, 15)
     181          #Empty code object should raise, but not crash the VM
     182          with self.assertRaises(Exception):
     183              exec(co)
     184  
     185      @cpython_only
     186      def test_closure_injection(self):
     187          # From https://bugs.python.org/issue32176
     188          from types import FunctionType
     189  
     190          def create_closure(__class__):
     191              return (lambda: __class__).__closure__
     192  
     193          def new_code(c):
     194              '''A new code object with a __class__ cell added to freevars'''
     195              return c.replace(co_freevars=c.co_freevars + ('__class__',), co_code=bytes([COPY_FREE_VARS, 1])+c.co_code)
     196  
     197          def add_foreign_method(cls, name, f):
     198              code = new_code(f.__code__)
     199              assert not f.__closure__
     200              closure = create_closure(cls)
     201              defaults = f.__defaults__
     202              setattr(cls, name, FunctionType(code, globals(), name, defaults, closure))
     203  
     204          class ESC[4;38;5;81mList(ESC[4;38;5;149mlist):
     205              pass
     206  
     207          add_foreign_method(List, "__getitem__", external_getitem)
     208  
     209          # Ensure the closure injection actually worked
     210          function = List.__getitem__
     211          class_ref = function.__closure__[0].cell_contents
     212          self.assertIs(class_ref, List)
     213  
     214          # Ensure the zero-arg super() call in the injected method works
     215          obj = List([1, 2, 3])
     216          self.assertEqual(obj[0], "Foreign getitem: 1")
     217  
     218      def test_constructor(self):
     219          def func(): pass
     220          co = func.__code__
     221          CodeType = type(co)
     222  
     223          # test code constructor
     224          CodeType(co.co_argcount,
     225                          co.co_posonlyargcount,
     226                          co.co_kwonlyargcount,
     227                          co.co_nlocals,
     228                          co.co_stacksize,
     229                          co.co_flags,
     230                          co.co_code,
     231                          co.co_consts,
     232                          co.co_names,
     233                          co.co_varnames,
     234                          co.co_filename,
     235                          co.co_name,
     236                          co.co_qualname,
     237                          co.co_firstlineno,
     238                          co.co_linetable,
     239                          co.co_exceptiontable,
     240                          co.co_freevars,
     241                          co.co_cellvars)
     242  
     243      def test_qualname(self):
     244          self.assertEqual(
     245              CodeTest.test_qualname.__code__.co_qualname,
     246              CodeTest.test_qualname.__qualname__
     247          )
     248  
     249      def test_replace(self):
     250          def func():
     251              x = 1
     252              return x
     253          code = func.__code__
     254  
     255          # different co_name, co_varnames, co_consts
     256          def func2():
     257              y = 2
     258              z = 3
     259              return y
     260          code2 = func2.__code__
     261  
     262          for attr, value in (
     263              ("co_argcount", 0),
     264              ("co_posonlyargcount", 0),
     265              ("co_kwonlyargcount", 0),
     266              ("co_nlocals", 1),
     267              ("co_stacksize", 0),
     268              ("co_flags", code.co_flags | inspect.CO_COROUTINE),
     269              ("co_firstlineno", 100),
     270              ("co_code", code2.co_code),
     271              ("co_consts", code2.co_consts),
     272              ("co_names", ("myname",)),
     273              ("co_varnames", ('spam',)),
     274              ("co_freevars", ("freevar",)),
     275              ("co_cellvars", ("cellvar",)),
     276              ("co_filename", "newfilename"),
     277              ("co_name", "newname"),
     278              ("co_linetable", code2.co_linetable),
     279          ):
     280              with self.subTest(attr=attr, value=value):
     281                  new_code = code.replace(**{attr: value})
     282                  self.assertEqual(getattr(new_code, attr), value)
     283  
     284          new_code = code.replace(co_varnames=code2.co_varnames,
     285                                  co_nlocals=code2.co_nlocals)
     286          self.assertEqual(new_code.co_varnames, code2.co_varnames)
     287          self.assertEqual(new_code.co_nlocals, code2.co_nlocals)
     288  
     289      def test_nlocals_mismatch(self):
     290          def func():
     291              x = 1
     292              return x
     293          co = func.__code__
     294          assert co.co_nlocals > 0;
     295  
     296          # First we try the constructor.
     297          CodeType = type(co)
     298          for diff in (-1, 1):
     299              with self.assertRaises(ValueError):
     300                  CodeType(co.co_argcount,
     301                           co.co_posonlyargcount,
     302                           co.co_kwonlyargcount,
     303                           # This is the only change.
     304                           co.co_nlocals + diff,
     305                           co.co_stacksize,
     306                           co.co_flags,
     307                           co.co_code,
     308                           co.co_consts,
     309                           co.co_names,
     310                           co.co_varnames,
     311                           co.co_filename,
     312                           co.co_name,
     313                           co.co_qualname,
     314                           co.co_firstlineno,
     315                           co.co_linetable,
     316                           co.co_exceptiontable,
     317                           co.co_freevars,
     318                           co.co_cellvars,
     319                           )
     320          # Then we try the replace method.
     321          with self.assertRaises(ValueError):
     322              co.replace(co_nlocals=co.co_nlocals - 1)
     323          with self.assertRaises(ValueError):
     324              co.replace(co_nlocals=co.co_nlocals + 1)
     325  
     326      def test_shrinking_localsplus(self):
     327          # Check that PyCode_NewWithPosOnlyArgs resizes both
     328          # localsplusnames and localspluskinds, if an argument is a cell.
     329          def func(arg):
     330              return lambda: arg
     331          code = func.__code__
     332          newcode = code.replace(co_name="func")  # Should not raise SystemError
     333          self.assertEqual(code, newcode)
     334  
     335      def test_empty_linetable(self):
     336          def func():
     337              pass
     338          new_code = code = func.__code__.replace(co_linetable=b'')
     339          self.assertEqual(list(new_code.co_lines()), [])
     340  
     341      def test_co_lnotab_is_deprecated(self):  # TODO: remove in 3.14
     342          def func():
     343              pass
     344  
     345          with self.assertWarns(DeprecationWarning):
     346              func.__code__.co_lnotab
     347  
     348      def test_invalid_bytecode(self):
     349          def foo():
     350              pass
     351  
     352          # assert that opcode 229 is invalid
     353          self.assertEqual(opname[229], '<229>')
     354  
     355          # change first opcode to 0xeb (=229)
     356          foo.__code__ = foo.__code__.replace(
     357              co_code=b'\xe5' + foo.__code__.co_code[1:])
     358  
     359          msg = "unknown opcode 229"
     360          with self.assertRaisesRegex(SystemError, msg):
     361              foo()
     362  
     363      @requires_debug_ranges()
     364      def test_co_positions_artificial_instructions(self):
     365          import dis
     366  
     367          namespace = {}
     368          exec(textwrap.dedent("""\
     369          try:
     370              1/0
     371          except Exception as e:
     372              exc = e
     373          """), namespace)
     374  
     375          exc = namespace['exc']
     376          traceback = exc.__traceback__
     377          code = traceback.tb_frame.f_code
     378  
     379          artificial_instructions = []
     380          for instr, positions in zip(
     381              dis.get_instructions(code, show_caches=True),
     382              code.co_positions(),
     383              strict=True
     384          ):
     385              # If any of the positions is None, then all have to
     386              # be None as well for the case above. There are still
     387              # some places in the compiler, where the artificial instructions
     388              # get assigned the first_lineno but they don't have other positions.
     389              # There is no easy way of inferring them at that stage, so for now
     390              # we don't support it.
     391              self.assertIn(positions.count(None), [0, 3, 4])
     392  
     393              if not any(positions):
     394                  artificial_instructions.append(instr)
     395  
     396          self.assertEqual(
     397              [
     398                  (instruction.opname, instruction.argval)
     399                  for instruction in artificial_instructions
     400              ],
     401              [
     402                  ("PUSH_EXC_INFO", None),
     403                  ("LOAD_CONST", None), # artificial 'None'
     404                  ("STORE_NAME", "e"),  # XX: we know the location for this
     405                  ("DELETE_NAME", "e"),
     406                  ("RERAISE", 1),
     407                  ("COPY", 3),
     408                  ("POP_EXCEPT", None),
     409                  ("RERAISE", 1)
     410              ]
     411          )
     412  
     413      def test_endline_and_columntable_none_when_no_debug_ranges(self):
     414          # Make sure that if `-X no_debug_ranges` is used, there is
     415          # minimal debug info
     416          code = textwrap.dedent("""
     417              def f():
     418                  pass
     419  
     420              positions = f.__code__.co_positions()
     421              for line, end_line, column, end_column in positions:
     422                  assert line == end_line
     423                  assert column is None
     424                  assert end_column is None
     425              """)
     426          assert_python_ok('-X', 'no_debug_ranges', '-c', code)
     427  
     428      def test_endline_and_columntable_none_when_no_debug_ranges_env(self):
     429          # Same as above but using the environment variable opt out.
     430          code = textwrap.dedent("""
     431              def f():
     432                  pass
     433  
     434              positions = f.__code__.co_positions()
     435              for line, end_line, column, end_column in positions:
     436                  assert line == end_line
     437                  assert column is None
     438                  assert end_column is None
     439              """)
     440          assert_python_ok('-c', code, PYTHONNODEBUGRANGES='1')
     441  
     442      # co_positions behavior when info is missing.
     443  
     444      @requires_debug_ranges()
     445      def test_co_positions_empty_linetable(self):
     446          def func():
     447              x = 1
     448          new_code = func.__code__.replace(co_linetable=b'')
     449          positions = new_code.co_positions()
     450          for line, end_line, column, end_column in positions:
     451              self.assertIsNone(line)
     452              self.assertEqual(end_line, new_code.co_firstlineno + 1)
     453  
     454      def test_code_equality(self):
     455          def f():
     456              try:
     457                  a()
     458              except:
     459                  b()
     460              else:
     461                  c()
     462              finally:
     463                  d()
     464          code_a = f.__code__
     465          code_b = code_a.replace(co_linetable=b"")
     466          code_c = code_a.replace(co_exceptiontable=b"")
     467          code_d = code_b.replace(co_exceptiontable=b"")
     468          self.assertNotEqual(code_a, code_b)
     469          self.assertNotEqual(code_a, code_c)
     470          self.assertNotEqual(code_a, code_d)
     471          self.assertNotEqual(code_b, code_c)
     472          self.assertNotEqual(code_b, code_d)
     473          self.assertNotEqual(code_c, code_d)
     474  
     475      def test_code_hash_uses_firstlineno(self):
     476          c1 = (lambda: 1).__code__
     477          c2 = (lambda: 1).__code__
     478          self.assertNotEqual(c1, c2)
     479          self.assertNotEqual(hash(c1), hash(c2))
     480          c3 = c1.replace(co_firstlineno=17)
     481          self.assertNotEqual(c1, c3)
     482          self.assertNotEqual(hash(c1), hash(c3))
     483  
     484      def test_code_hash_uses_order(self):
     485          # Swapping posonlyargcount and kwonlyargcount should change the hash.
     486          c = (lambda x, y, *, z=1, w=1: 1).__code__
     487          self.assertEqual(c.co_argcount, 2)
     488          self.assertEqual(c.co_posonlyargcount, 0)
     489          self.assertEqual(c.co_kwonlyargcount, 2)
     490          swapped = c.replace(co_posonlyargcount=2, co_kwonlyargcount=0)
     491          self.assertNotEqual(c, swapped)
     492          self.assertNotEqual(hash(c), hash(swapped))
     493  
     494      def test_code_hash_uses_bytecode(self):
     495          c = (lambda x, y: x + y).__code__
     496          d = (lambda x, y: x * y).__code__
     497          c1 = c.replace(co_code=d.co_code)
     498          self.assertNotEqual(c, c1)
     499          self.assertNotEqual(hash(c), hash(c1))
     500  
     501  
     502  def isinterned(s):
     503      return s is sys.intern(('_' + s + '_')[1:-1])
     504  
     505  class ESC[4;38;5;81mCodeConstsTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     506  
     507      def find_const(self, consts, value):
     508          for v in consts:
     509              if v == value:
     510                  return v
     511          self.assertIn(value, consts)  # raises an exception
     512          self.fail('Should never be reached')
     513  
     514      def assertIsInterned(self, s):
     515          if not isinterned(s):
     516              self.fail('String %r is not interned' % (s,))
     517  
     518      def assertIsNotInterned(self, s):
     519          if isinterned(s):
     520              self.fail('String %r is interned' % (s,))
     521  
     522      @cpython_only
     523      def test_interned_string(self):
     524          co = compile('res = "str_value"', '?', 'exec')
     525          v = self.find_const(co.co_consts, 'str_value')
     526          self.assertIsInterned(v)
     527  
     528      @cpython_only
     529      def test_interned_string_in_tuple(self):
     530          co = compile('res = ("str_value",)', '?', 'exec')
     531          v = self.find_const(co.co_consts, ('str_value',))
     532          self.assertIsInterned(v[0])
     533  
     534      @cpython_only
     535      def test_interned_string_in_frozenset(self):
     536          co = compile('res = a in {"str_value"}', '?', 'exec')
     537          v = self.find_const(co.co_consts, frozenset(('str_value',)))
     538          self.assertIsInterned(tuple(v)[0])
     539  
     540      @cpython_only
     541      def test_interned_string_default(self):
     542          def f(a='str_value'):
     543              return a
     544          self.assertIsInterned(f())
     545  
     546      @cpython_only
     547      def test_interned_string_with_null(self):
     548          co = compile(r'res = "str\0value!"', '?', 'exec')
     549          v = self.find_const(co.co_consts, 'str\0value!')
     550          self.assertIsNotInterned(v)
     551  
     552  
     553  class ESC[4;38;5;81mCodeWeakRefTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     554  
     555      def test_basic(self):
     556          # Create a code object in a clean environment so that we know we have
     557          # the only reference to it left.
     558          namespace = {}
     559          exec("def f(): pass", globals(), namespace)
     560          f = namespace["f"]
     561          del namespace
     562  
     563          self.called = False
     564          def callback(code):
     565              self.called = True
     566  
     567          # f is now the last reference to the function, and through it, the code
     568          # object.  While we hold it, check that we can create a weakref and
     569          # deref it.  Then delete it, and check that the callback gets called and
     570          # the reference dies.
     571          coderef = weakref.ref(f.__code__, callback)
     572          self.assertTrue(bool(coderef()))
     573          del f
     574          gc_collect()  # For PyPy or other GCs.
     575          self.assertFalse(bool(coderef()))
     576          self.assertTrue(self.called)
     577  
     578  # Python implementation of location table parsing algorithm
     579  def read(it):
     580      return next(it)
     581  
     582  def read_varint(it):
     583      b = read(it)
     584      val = b & 63;
     585      shift = 0;
     586      while b & 64:
     587          b = read(it)
     588          shift += 6
     589          val |= (b&63) << shift
     590      return val
     591  
     592  def read_signed_varint(it):
     593      uval = read_varint(it)
     594      if uval & 1:
     595          return -(uval >> 1)
     596      else:
     597          return uval >> 1
     598  
     599  def parse_location_table(code):
     600      line = code.co_firstlineno
     601      it = iter(code.co_linetable)
     602      while True:
     603          try:
     604              first_byte = read(it)
     605          except StopIteration:
     606              return
     607          code = (first_byte >> 3) & 15
     608          length = (first_byte & 7) + 1
     609          if code == 15:
     610              yield (code, length, None, None, None, None)
     611          elif code == 14:
     612              line_delta = read_signed_varint(it)
     613              line += line_delta
     614              end_line = line + read_varint(it)
     615              col = read_varint(it)
     616              if col == 0:
     617                  col = None
     618              else:
     619                  col -= 1
     620              end_col = read_varint(it)
     621              if end_col == 0:
     622                  end_col = None
     623              else:
     624                  end_col -= 1
     625              yield (code, length, line, end_line, col, end_col)
     626          elif code == 13: # No column
     627              line_delta = read_signed_varint(it)
     628              line += line_delta
     629              yield (code, length, line, line, None, None)
     630          elif code in (10, 11, 12): # new line
     631              line_delta = code - 10
     632              line += line_delta
     633              column = read(it)
     634              end_column = read(it)
     635              yield (code, length, line, line, column, end_column)
     636          else:
     637              assert (0 <= code < 10)
     638              second_byte = read(it)
     639              column = code << 3 | (second_byte >> 4)
     640              yield (code, length, line, line, column, column + (second_byte & 15))
     641  
     642  def positions_from_location_table(code):
     643      for _, length, line, end_line, col, end_col in parse_location_table(code):
     644          for _ in range(length):
     645              yield (line, end_line, col, end_col)
     646  
     647  def dedup(lst, prev=object()):
     648      for item in lst:
     649          if item != prev:
     650              yield item
     651              prev = item
     652  
     653  def lines_from_postions(positions):
     654      return dedup(l for (l, _, _, _) in positions)
     655  
     656  def misshappen():
     657      """
     658  
     659  
     660  
     661  
     662  
     663      """
     664      x = (
     665  
     666  
     667          4
     668  
     669          +
     670  
     671          y
     672  
     673      )
     674      y = (
     675          a
     676          +
     677              b
     678                  +
     679  
     680                  d
     681          )
     682      return q if (
     683  
     684          x
     685  
     686          ) else p
     687  
     688  def bug93662():
     689      example_report_generation_message= (
     690              """
     691              """
     692      ).strip()
     693      raise ValueError()
     694  
     695  
     696  class ESC[4;38;5;81mCodeLocationTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     697  
     698      def check_positions(self, func):
     699          pos1 = list(func.__code__.co_positions())
     700          pos2 = list(positions_from_location_table(func.__code__))
     701          for l1, l2 in zip(pos1, pos2):
     702              self.assertEqual(l1, l2)
     703          self.assertEqual(len(pos1), len(pos2))
     704  
     705      def test_positions(self):
     706          self.check_positions(parse_location_table)
     707          self.check_positions(misshappen)
     708          self.check_positions(bug93662)
     709  
     710      def check_lines(self, func):
     711          co = func.__code__
     712          lines1 = [line for _, _, line in co.co_lines()]
     713          self.assertEqual(lines1, list(dedup(lines1)))
     714          lines2 = list(lines_from_postions(positions_from_location_table(co)))
     715          for l1, l2 in zip(lines1, lines2):
     716              self.assertEqual(l1, l2)
     717          self.assertEqual(len(lines1), len(lines2))
     718  
     719      def test_lines(self):
     720          self.check_lines(parse_location_table)
     721          self.check_lines(misshappen)
     722          self.check_lines(bug93662)
     723  
     724      @cpython_only
     725      def test_code_new_empty(self):
     726          # If this test fails, it means that the construction of PyCode_NewEmpty
     727          # needs to be modified! Please update this test *and* PyCode_NewEmpty,
     728          # so that they both stay in sync.
     729          def f():
     730              pass
     731          PY_CODE_LOCATION_INFO_NO_COLUMNS = 13
     732          f.__code__ = f.__code__.replace(
     733              co_stacksize=1,
     734              co_firstlineno=42,
     735              co_code=bytes(
     736                  [
     737                      dis.opmap["RESUME"], 0,
     738                      dis.opmap["LOAD_ASSERTION_ERROR"], 0,
     739                      dis.opmap["RAISE_VARARGS"], 1,
     740                  ]
     741              ),
     742              co_linetable=bytes(
     743                  [
     744                      (1 << 7)
     745                      | (PY_CODE_LOCATION_INFO_NO_COLUMNS << 3)
     746                      | (3 - 1),
     747                      0,
     748                  ]
     749              ),
     750          )
     751          self.assertRaises(AssertionError, f)
     752          self.assertEqual(
     753              list(f.__code__.co_positions()),
     754              3 * [(42, 42, None, None)],
     755          )
     756  
     757  
     758  if check_impl_detail(cpython=True) and ctypes is not None:
     759      py = ctypes.pythonapi
     760      freefunc = ctypes.CFUNCTYPE(None,ctypes.c_voidp)
     761  
     762      RequestCodeExtraIndex = py.PyUnstable_Eval_RequestCodeExtraIndex
     763      RequestCodeExtraIndex.argtypes = (freefunc,)
     764      RequestCodeExtraIndex.restype = ctypes.c_ssize_t
     765  
     766      SetExtra = py.PyUnstable_Code_SetExtra
     767      SetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t, ctypes.c_voidp)
     768      SetExtra.restype = ctypes.c_int
     769  
     770      GetExtra = py.PyUnstable_Code_GetExtra
     771      GetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t,
     772                           ctypes.POINTER(ctypes.c_voidp))
     773      GetExtra.restype = ctypes.c_int
     774  
     775      LAST_FREED = None
     776      def myfree(ptr):
     777          global LAST_FREED
     778          LAST_FREED = ptr
     779  
     780      FREE_FUNC = freefunc(myfree)
     781      FREE_INDEX = RequestCodeExtraIndex(FREE_FUNC)
     782  
     783      class ESC[4;38;5;81mCoExtra(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     784          def get_func(self):
     785              # Defining a function causes the containing function to have a
     786              # reference to the code object.  We need the code objects to go
     787              # away, so we eval a lambda.
     788              return eval('lambda:42')
     789  
     790          def test_get_non_code(self):
     791              f = self.get_func()
     792  
     793              self.assertRaises(SystemError, SetExtra, 42, FREE_INDEX,
     794                                ctypes.c_voidp(100))
     795              self.assertRaises(SystemError, GetExtra, 42, FREE_INDEX,
     796                                ctypes.c_voidp(100))
     797  
     798          def test_bad_index(self):
     799              f = self.get_func()
     800              self.assertRaises(SystemError, SetExtra, f.__code__,
     801                                FREE_INDEX+100, ctypes.c_voidp(100))
     802              self.assertEqual(GetExtra(f.__code__, FREE_INDEX+100,
     803                                ctypes.c_voidp(100)), 0)
     804  
     805          def test_free_called(self):
     806              # Verify that the provided free function gets invoked
     807              # when the code object is cleaned up.
     808              f = self.get_func()
     809  
     810              SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(100))
     811              del f
     812              self.assertEqual(LAST_FREED, 100)
     813  
     814          def test_get_set(self):
     815              # Test basic get/set round tripping.
     816              f = self.get_func()
     817  
     818              extra = ctypes.c_voidp()
     819  
     820              SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(200))
     821              # reset should free...
     822              SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(300))
     823              self.assertEqual(LAST_FREED, 200)
     824  
     825              extra = ctypes.c_voidp()
     826              GetExtra(f.__code__, FREE_INDEX, extra)
     827              self.assertEqual(extra.value, 300)
     828              del f
     829  
     830          @threading_helper.requires_working_threading()
     831          def test_free_different_thread(self):
     832              # Freeing a code object on a different thread then
     833              # where the co_extra was set should be safe.
     834              f = self.get_func()
     835              class ESC[4;38;5;81mThreadTest(ESC[4;38;5;149mthreadingESC[4;38;5;149m.ESC[4;38;5;149mThread):
     836                  def __init__(self, f, test):
     837                      super().__init__()
     838                      self.f = f
     839                      self.test = test
     840                  def run(self):
     841                      del self.f
     842                      self.test.assertEqual(LAST_FREED, 500)
     843  
     844              SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(500))
     845              tt = ThreadTest(f, self)
     846              del f
     847              tt.start()
     848              tt.join()
     849              self.assertEqual(LAST_FREED, 500)
     850  
     851  
     852  def load_tests(loader, tests, pattern):
     853      tests.addTest(doctest.DocTestSuite())
     854      return tests
     855  
     856  
     857  if __name__ == "__main__":
     858      unittest.main()