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) 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 return 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_lnotab", code2.co_lnotab), 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 268def isinterned(s): 269 return s is sys.intern(('_' + s + '_')[1:-1]) 270 271class CodeConstsTest(unittest.TestCase): 272 273 def find_const(self, consts, value): 274 for v in consts: 275 if v == value: 276 return v 277 self.assertIn(value, consts) # raises an exception 278 self.fail('Should never be reached') 279 280 def assertIsInterned(self, s): 281 if not isinterned(s): 282 self.fail('String %r is not interned' % (s,)) 283 284 def assertIsNotInterned(self, s): 285 if isinterned(s): 286 self.fail('String %r is interned' % (s,)) 287 288 @cpython_only 289 def test_interned_string(self): 290 co = compile('res = "str_value"', '?', 'exec') 291 v = self.find_const(co.co_consts, 'str_value') 292 self.assertIsInterned(v) 293 294 @cpython_only 295 def test_interned_string_in_tuple(self): 296 co = compile('res = ("str_value",)', '?', 'exec') 297 v = self.find_const(co.co_consts, ('str_value',)) 298 self.assertIsInterned(v[0]) 299 300 @cpython_only 301 def test_interned_string_in_frozenset(self): 302 co = compile('res = a in {"str_value"}', '?', 'exec') 303 v = self.find_const(co.co_consts, frozenset(('str_value',))) 304 self.assertIsInterned(tuple(v)[0]) 305 306 @cpython_only 307 def test_interned_string_default(self): 308 def f(a='str_value'): 309 return a 310 self.assertIsInterned(f()) 311 312 @cpython_only 313 def test_interned_string_with_null(self): 314 co = compile(r'res = "str\0value!"', '?', 'exec') 315 v = self.find_const(co.co_consts, 'str\0value!') 316 self.assertIsNotInterned(v) 317 318 319class CodeWeakRefTest(unittest.TestCase): 320 321 def test_basic(self): 322 # Create a code object in a clean environment so that we know we have 323 # the only reference to it left. 324 namespace = {} 325 exec("def f(): pass", globals(), namespace) 326 f = namespace["f"] 327 del namespace 328 329 self.called = False 330 def callback(code): 331 self.called = True 332 333 # f is now the last reference to the function, and through it, the code 334 # object. While we hold it, check that we can create a weakref and 335 # deref it. Then delete it, and check that the callback gets called and 336 # the reference dies. 337 coderef = weakref.ref(f.__code__, callback) 338 self.assertTrue(bool(coderef())) 339 del f 340 self.assertFalse(bool(coderef())) 341 self.assertTrue(self.called) 342 343 344if check_impl_detail(cpython=True) and ctypes is not None: 345 py = ctypes.pythonapi 346 freefunc = ctypes.CFUNCTYPE(None,ctypes.c_voidp) 347 348 RequestCodeExtraIndex = py._PyEval_RequestCodeExtraIndex 349 RequestCodeExtraIndex.argtypes = (freefunc,) 350 RequestCodeExtraIndex.restype = ctypes.c_ssize_t 351 352 SetExtra = py._PyCode_SetExtra 353 SetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t, ctypes.c_voidp) 354 SetExtra.restype = ctypes.c_int 355 356 GetExtra = py._PyCode_GetExtra 357 GetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t, 358 ctypes.POINTER(ctypes.c_voidp)) 359 GetExtra.restype = ctypes.c_int 360 361 LAST_FREED = None 362 def myfree(ptr): 363 global LAST_FREED 364 LAST_FREED = ptr 365 366 FREE_FUNC = freefunc(myfree) 367 FREE_INDEX = RequestCodeExtraIndex(FREE_FUNC) 368 369 class CoExtra(unittest.TestCase): 370 def get_func(self): 371 # Defining a function causes the containing function to have a 372 # reference to the code object. We need the code objects to go 373 # away, so we eval a lambda. 374 return eval('lambda:42') 375 376 def test_get_non_code(self): 377 f = self.get_func() 378 379 self.assertRaises(SystemError, SetExtra, 42, FREE_INDEX, 380 ctypes.c_voidp(100)) 381 self.assertRaises(SystemError, GetExtra, 42, FREE_INDEX, 382 ctypes.c_voidp(100)) 383 384 def test_bad_index(self): 385 f = self.get_func() 386 self.assertRaises(SystemError, SetExtra, f.__code__, 387 FREE_INDEX+100, ctypes.c_voidp(100)) 388 self.assertEqual(GetExtra(f.__code__, FREE_INDEX+100, 389 ctypes.c_voidp(100)), 0) 390 391 def test_free_called(self): 392 # Verify that the provided free function gets invoked 393 # when the code object is cleaned up. 394 f = self.get_func() 395 396 SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(100)) 397 del f 398 self.assertEqual(LAST_FREED, 100) 399 400 def test_get_set(self): 401 # Test basic get/set round tripping. 402 f = self.get_func() 403 404 extra = ctypes.c_voidp() 405 406 SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(200)) 407 # reset should free... 408 SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(300)) 409 self.assertEqual(LAST_FREED, 200) 410 411 extra = ctypes.c_voidp() 412 GetExtra(f.__code__, FREE_INDEX, extra) 413 self.assertEqual(extra.value, 300) 414 del f 415 416 def test_free_different_thread(self): 417 # Freeing a code object on a different thread then 418 # where the co_extra was set should be safe. 419 f = self.get_func() 420 class ThreadTest(threading.Thread): 421 def __init__(self, f, test): 422 super().__init__() 423 self.f = f 424 self.test = test 425 def run(self): 426 del self.f 427 self.test.assertEqual(LAST_FREED, 500) 428 429 SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(500)) 430 tt = ThreadTest(f, self) 431 del f 432 tt.start() 433 tt.join() 434 self.assertEqual(LAST_FREED, 500) 435 436 437def test_main(verbose=None): 438 from test import test_code 439 run_doctest(test_code, verbose) 440 tests = [CodeTest, CodeConstsTest, CodeWeakRefTest] 441 if check_impl_detail(cpython=True) and ctypes is not None: 442 tests.append(CoExtra) 443 run_unittest(*tests) 444 445if __name__ == "__main__": 446 test_main() 447