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__) 10name: f 11argcount: 1 12posonlyargcount: 0 13kwonlyargcount: 0 14names: () 15varnames: ('x', 'g') 16cellvars: ('x',) 17freevars: () 18nlocals: 2 19flags: 3 20consts: ('None', '<code object g>', "'f.<locals>.g'") 21 22>>> dump(f(4).__code__) 23name: g 24argcount: 1 25posonlyargcount: 0 26kwonlyargcount: 0 27names: () 28varnames: ('y',) 29cellvars: () 30freevars: ('x',) 31nlocals: 1 32flags: 19 33consts: ('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__) 43name: h 44argcount: 2 45posonlyargcount: 0 46kwonlyargcount: 0 47names: () 48varnames: ('x', 'y', 'a', 'b', 'c') 49cellvars: () 50freevars: () 51nlocals: 5 52flags: 67 53consts: ('None',) 54 55>>> def attrs(obj): 56... print(obj.attr1) 57... print(obj.attr2) 58... print(obj.attr3) 59 60>>> dump(attrs.__code__) 61name: attrs 62argcount: 1 63posonlyargcount: 0 64kwonlyargcount: 0 65names: ('print', 'attr1', 'attr2', 'attr3') 66varnames: ('obj',) 67cellvars: () 68freevars: () 69nlocals: 1 70flags: 67 71consts: ('None',) 72 73>>> def optimize_away(): 74... 'doc string' 75... 'not a docstring' 76... 53 77... 0x53 78 79>>> dump(optimize_away.__code__) 80name: optimize_away 81argcount: 0 82posonlyargcount: 0 83kwonlyargcount: 0 84names: () 85varnames: () 86cellvars: () 87freevars: () 88nlocals: 0 89flags: 67 90consts: ("'doc string'", 'None') 91 92>>> def keywordonly_args(a,b,*,k1): 93... return a,b,k1 94... 95 96>>> dump(keywordonly_args.__code__) 97name: keywordonly_args 98argcount: 2 99posonlyargcount: 0 100kwonlyargcount: 1 101names: () 102varnames: ('a', 'b', 'k1') 103cellvars: () 104freevars: () 105nlocals: 3 106flags: 67 107consts: ('None',) 108 109>>> def posonly_args(a,b,/,c): 110... return a,b,c 111... 112 113>>> dump(posonly_args.__code__) 114name: posonly_args 115argcount: 3 116posonlyargcount: 2 117kwonlyargcount: 0 118names: () 119varnames: ('a', 'b', 'c') 120cellvars: () 121freevars: () 122nlocals: 3 123flags: 67 124consts: ('None',) 125 126""" 127 128import inspect 129import sys 130import threading 131import unittest 132import weakref 133try: 134 import ctypes 135except ImportError: 136 ctypes = None 137from test.support import (run_doctest, run_unittest, cpython_only, 138 check_impl_detail, gc_collect) 139 140 141def consts(t): 142 """Yield a doctest-safe sequence of object reprs.""" 143 for elt in t: 144 r = repr(elt) 145 if r.startswith("<code object"): 146 yield "<code object %s>" % elt.co_name 147 else: 148 yield r 149 150def dump(co): 151 """Print out a text representation of a code object.""" 152 for attr in ["name", "argcount", "posonlyargcount", 153 "kwonlyargcount", "names", "varnames", 154 "cellvars", "freevars", "nlocals", "flags"]: 155 print("%s: %s" % (attr, getattr(co, "co_" + attr))) 156 print("consts:", tuple(consts(co.co_consts))) 157 158# Needed for test_closure_injection below 159# Defined at global scope to avoid implicitly closing over __class__ 160def external_getitem(self, i): 161 return f"Foreign getitem: {super().__getitem__(i)}" 162 163class CodeTest(unittest.TestCase): 164 165 @cpython_only 166 def test_newempty(self): 167 import _testcapi 168 co = _testcapi.code_newempty("filename", "funcname", 15) 169 self.assertEqual(co.co_filename, "filename") 170 self.assertEqual(co.co_name, "funcname") 171 self.assertEqual(co.co_firstlineno, 15) 172 173 @cpython_only 174 def test_closure_injection(self): 175 # From https://bugs.python.org/issue32176 176 from types import FunctionType 177 178 def create_closure(__class__): 179 return (lambda: __class__).__closure__ 180 181 def new_code(c): 182 '''A new code object with a __class__ cell added to freevars''' 183 return c.replace(co_freevars=c.co_freevars + ('__class__',)) 184 185 def add_foreign_method(cls, name, f): 186 code = new_code(f.__code__) 187 assert not f.__closure__ 188 closure = create_closure(cls) 189 defaults = f.__defaults__ 190 setattr(cls, name, FunctionType(code, globals(), name, defaults, closure)) 191 192 class List(list): 193 pass 194 195 add_foreign_method(List, "__getitem__", external_getitem) 196 197 # Ensure the closure injection actually worked 198 function = List.__getitem__ 199 class_ref = function.__closure__[0].cell_contents 200 self.assertIs(class_ref, List) 201 202 # Ensure the code correctly indicates it accesses a free variable 203 self.assertFalse(function.__code__.co_flags & inspect.CO_NOFREE, 204 hex(function.__code__.co_flags)) 205 206 # Ensure the zero-arg super() call in the injected method works 207 obj = List([1, 2, 3]) 208 self.assertEqual(obj[0], "Foreign getitem: 1") 209 210 def test_constructor(self): 211 def func(): pass 212 co = func.__code__ 213 CodeType = type(co) 214 215 # test code constructor 216 CodeType(co.co_argcount, 217 co.co_posonlyargcount, 218 co.co_kwonlyargcount, 219 co.co_nlocals, 220 co.co_stacksize, 221 co.co_flags, 222 co.co_code, 223 co.co_consts, 224 co.co_names, 225 co.co_varnames, 226 co.co_filename, 227 co.co_name, 228 co.co_firstlineno, 229 co.co_lnotab, 230 co.co_freevars, 231 co.co_cellvars) 232 233 def test_replace(self): 234 def func(): 235 x = 1 236 return x 237 code = func.__code__ 238 239 # different co_name, co_varnames, co_consts 240 def func2(): 241 y = 2 242 return y 243 code2 = func2.__code__ 244 245 for attr, value in ( 246 ("co_argcount", 0), 247 ("co_posonlyargcount", 0), 248 ("co_kwonlyargcount", 0), 249 ("co_nlocals", 0), 250 ("co_stacksize", 0), 251 ("co_flags", code.co_flags | inspect.CO_COROUTINE), 252 ("co_firstlineno", 100), 253 ("co_code", code2.co_code), 254 ("co_consts", code2.co_consts), 255 ("co_names", ("myname",)), 256 ("co_varnames", code2.co_varnames), 257 ("co_freevars", ("freevar",)), 258 ("co_cellvars", ("cellvar",)), 259 ("co_filename", "newfilename"), 260 ("co_name", "newname"), 261 ("co_linetable", code2.co_linetable), 262 ): 263 with self.subTest(attr=attr, value=value): 264 new_code = code.replace(**{attr: value}) 265 self.assertEqual(getattr(new_code, attr), value) 266 267 def test_empty_linetable(self): 268 def func(): 269 pass 270 new_code = code = func.__code__.replace(co_linetable=b'') 271 self.assertEqual(list(new_code.co_lines()), []) 272 273 274def isinterned(s): 275 return s is sys.intern(('_' + s + '_')[1:-1]) 276 277class CodeConstsTest(unittest.TestCase): 278 279 def find_const(self, consts, value): 280 for v in consts: 281 if v == value: 282 return v 283 self.assertIn(value, consts) # raises an exception 284 self.fail('Should never be reached') 285 286 def assertIsInterned(self, s): 287 if not isinterned(s): 288 self.fail('String %r is not interned' % (s,)) 289 290 def assertIsNotInterned(self, s): 291 if isinterned(s): 292 self.fail('String %r is interned' % (s,)) 293 294 @cpython_only 295 def test_interned_string(self): 296 co = compile('res = "str_value"', '?', 'exec') 297 v = self.find_const(co.co_consts, 'str_value') 298 self.assertIsInterned(v) 299 300 @cpython_only 301 def test_interned_string_in_tuple(self): 302 co = compile('res = ("str_value",)', '?', 'exec') 303 v = self.find_const(co.co_consts, ('str_value',)) 304 self.assertIsInterned(v[0]) 305 306 @cpython_only 307 def test_interned_string_in_frozenset(self): 308 co = compile('res = a in {"str_value"}', '?', 'exec') 309 v = self.find_const(co.co_consts, frozenset(('str_value',))) 310 self.assertIsInterned(tuple(v)[0]) 311 312 @cpython_only 313 def test_interned_string_default(self): 314 def f(a='str_value'): 315 return a 316 self.assertIsInterned(f()) 317 318 @cpython_only 319 def test_interned_string_with_null(self): 320 co = compile(r'res = "str\0value!"', '?', 'exec') 321 v = self.find_const(co.co_consts, 'str\0value!') 322 self.assertIsNotInterned(v) 323 324 325class CodeWeakRefTest(unittest.TestCase): 326 327 def test_basic(self): 328 # Create a code object in a clean environment so that we know we have 329 # the only reference to it left. 330 namespace = {} 331 exec("def f(): pass", globals(), namespace) 332 f = namespace["f"] 333 del namespace 334 335 self.called = False 336 def callback(code): 337 self.called = True 338 339 # f is now the last reference to the function, and through it, the code 340 # object. While we hold it, check that we can create a weakref and 341 # deref it. Then delete it, and check that the callback gets called and 342 # the reference dies. 343 coderef = weakref.ref(f.__code__, callback) 344 self.assertTrue(bool(coderef())) 345 del f 346 gc_collect() # For PyPy or other GCs. 347 self.assertFalse(bool(coderef())) 348 self.assertTrue(self.called) 349 350 351if check_impl_detail(cpython=True) and ctypes is not None: 352 py = ctypes.pythonapi 353 freefunc = ctypes.CFUNCTYPE(None,ctypes.c_voidp) 354 355 RequestCodeExtraIndex = py._PyEval_RequestCodeExtraIndex 356 RequestCodeExtraIndex.argtypes = (freefunc,) 357 RequestCodeExtraIndex.restype = ctypes.c_ssize_t 358 359 SetExtra = py._PyCode_SetExtra 360 SetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t, ctypes.c_voidp) 361 SetExtra.restype = ctypes.c_int 362 363 GetExtra = py._PyCode_GetExtra 364 GetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t, 365 ctypes.POINTER(ctypes.c_voidp)) 366 GetExtra.restype = ctypes.c_int 367 368 LAST_FREED = None 369 def myfree(ptr): 370 global LAST_FREED 371 LAST_FREED = ptr 372 373 FREE_FUNC = freefunc(myfree) 374 FREE_INDEX = RequestCodeExtraIndex(FREE_FUNC) 375 376 class CoExtra(unittest.TestCase): 377 def get_func(self): 378 # Defining a function causes the containing function to have a 379 # reference to the code object. We need the code objects to go 380 # away, so we eval a lambda. 381 return eval('lambda:42') 382 383 def test_get_non_code(self): 384 f = self.get_func() 385 386 self.assertRaises(SystemError, SetExtra, 42, FREE_INDEX, 387 ctypes.c_voidp(100)) 388 self.assertRaises(SystemError, GetExtra, 42, FREE_INDEX, 389 ctypes.c_voidp(100)) 390 391 def test_bad_index(self): 392 f = self.get_func() 393 self.assertRaises(SystemError, SetExtra, f.__code__, 394 FREE_INDEX+100, ctypes.c_voidp(100)) 395 self.assertEqual(GetExtra(f.__code__, FREE_INDEX+100, 396 ctypes.c_voidp(100)), 0) 397 398 def test_free_called(self): 399 # Verify that the provided free function gets invoked 400 # when the code object is cleaned up. 401 f = self.get_func() 402 403 SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(100)) 404 del f 405 self.assertEqual(LAST_FREED, 100) 406 407 def test_get_set(self): 408 # Test basic get/set round tripping. 409 f = self.get_func() 410 411 extra = ctypes.c_voidp() 412 413 SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(200)) 414 # reset should free... 415 SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(300)) 416 self.assertEqual(LAST_FREED, 200) 417 418 extra = ctypes.c_voidp() 419 GetExtra(f.__code__, FREE_INDEX, extra) 420 self.assertEqual(extra.value, 300) 421 del f 422 423 def test_free_different_thread(self): 424 # Freeing a code object on a different thread then 425 # where the co_extra was set should be safe. 426 f = self.get_func() 427 class ThreadTest(threading.Thread): 428 def __init__(self, f, test): 429 super().__init__() 430 self.f = f 431 self.test = test 432 def run(self): 433 del self.f 434 self.test.assertEqual(LAST_FREED, 500) 435 436 SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(500)) 437 tt = ThreadTest(f, self) 438 del f 439 tt.start() 440 tt.join() 441 self.assertEqual(LAST_FREED, 500) 442 443 444def test_main(verbose=None): 445 from test import test_code 446 run_doctest(test_code, verbose) 447 tests = [CodeTest, CodeConstsTest, CodeWeakRefTest] 448 if check_impl_detail(cpython=True) and ctypes is not None: 449 tests.append(CoExtra) 450 run_unittest(*tests) 451 452if __name__ == "__main__": 453 test_main() 454