• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
133import opcode
134try:
135    import ctypes
136except ImportError:
137    ctypes = None
138from test.support import (run_doctest, run_unittest, cpython_only,
139                          check_impl_detail)
140
141
142def consts(t):
143    """Yield a doctest-safe sequence of object reprs."""
144    for elt in t:
145        r = repr(elt)
146        if r.startswith("<code object"):
147            yield "<code object %s>" % elt.co_name
148        else:
149            yield r
150
151def dump(co):
152    """Print out a text representation of a code object."""
153    for attr in ["name", "argcount", "posonlyargcount",
154                 "kwonlyargcount", "names", "varnames",
155                 "cellvars", "freevars", "nlocals", "flags"]:
156        print("%s: %s" % (attr, getattr(co, "co_" + attr)))
157    print("consts:", tuple(consts(co.co_consts)))
158
159# Needed for test_closure_injection below
160# Defined at global scope to avoid implicitly closing over __class__
161def external_getitem(self, i):
162    return f"Foreign getitem: {super().__getitem__(i)}"
163
164class CodeTest(unittest.TestCase):
165
166    @cpython_only
167    def test_newempty(self):
168        import _testcapi
169        co = _testcapi.code_newempty("filename", "funcname", 15)
170        self.assertEqual(co.co_filename, "filename")
171        self.assertEqual(co.co_name, "funcname")
172        self.assertEqual(co.co_firstlineno, 15)
173
174    @cpython_only
175    def test_closure_injection(self):
176        # From https://bugs.python.org/issue32176
177        from types import FunctionType
178
179        def create_closure(__class__):
180            return (lambda: __class__).__closure__
181
182        def new_code(c):
183            '''A new code object with a __class__ cell added to freevars'''
184            return c.replace(co_freevars=c.co_freevars + ('__class__',))
185
186        def add_foreign_method(cls, name, f):
187            code = new_code(f.__code__)
188            assert not f.__closure__
189            closure = create_closure(cls)
190            defaults = f.__defaults__
191            setattr(cls, name, FunctionType(code, globals(), name, defaults, closure))
192
193        class List(list):
194            pass
195
196        add_foreign_method(List, "__getitem__", external_getitem)
197
198        # Ensure the closure injection actually worked
199        function = List.__getitem__
200        class_ref = function.__closure__[0].cell_contents
201        self.assertIs(class_ref, List)
202
203        # Ensure the code correctly indicates it accesses a free variable
204        self.assertFalse(function.__code__.co_flags & inspect.CO_NOFREE,
205                         hex(function.__code__.co_flags))
206
207        # Ensure the zero-arg super() call in the injected method works
208        obj = List([1, 2, 3])
209        self.assertEqual(obj[0], "Foreign getitem: 1")
210
211    def test_constructor(self):
212        def func(): pass
213        co = func.__code__
214        CodeType = type(co)
215
216        # test code constructor
217        return CodeType(co.co_argcount,
218                        co.co_posonlyargcount,
219                        co.co_kwonlyargcount,
220                        co.co_nlocals,
221                        co.co_stacksize,
222                        co.co_flags,
223                        co.co_code,
224                        co.co_consts,
225                        co.co_names,
226                        co.co_varnames,
227                        co.co_filename,
228                        co.co_name,
229                        co.co_firstlineno,
230                        co.co_lnotab,
231                        co.co_freevars,
232                        co.co_cellvars)
233
234    def test_replace(self):
235        def func():
236            x = 1
237            return x
238        code = func.__code__
239
240        # different co_name, co_varnames, co_consts
241        def func2():
242            y = 2
243            return y
244        code2 = func.__code__
245
246        for attr, value in (
247            ("co_argcount", 0),
248            ("co_posonlyargcount", 0),
249            ("co_kwonlyargcount", 0),
250            ("co_nlocals", 0),
251            ("co_stacksize", 0),
252            ("co_flags", code.co_flags | inspect.CO_COROUTINE),
253            ("co_firstlineno", 100),
254            ("co_code", code2.co_code),
255            ("co_consts", code2.co_consts),
256            ("co_names", ("myname",)),
257            ("co_varnames", code2.co_varnames),
258            ("co_freevars", ("freevar",)),
259            ("co_cellvars", ("cellvar",)),
260            ("co_filename", "newfilename"),
261            ("co_name", "newname"),
262            ("co_lnotab", code2.co_lnotab),
263        ):
264            with self.subTest(attr=attr, value=value):
265                new_code = code.replace(**{attr: value})
266                self.assertEqual(getattr(new_code, attr), value)
267
268
269def isinterned(s):
270    return s is sys.intern(('_' + s + '_')[1:-1])
271
272class CodeConstsTest(unittest.TestCase):
273
274    def find_const(self, consts, value):
275        for v in consts:
276            if v == value:
277                return v
278        self.assertIn(value, consts)  # raises an exception
279        self.fail('Should never be reached')
280
281    def assertIsInterned(self, s):
282        if not isinterned(s):
283            self.fail('String %r is not interned' % (s,))
284
285    def assertIsNotInterned(self, s):
286        if isinterned(s):
287            self.fail('String %r is interned' % (s,))
288
289    @cpython_only
290    def test_interned_string(self):
291        co = compile('res = "str_value"', '?', 'exec')
292        v = self.find_const(co.co_consts, 'str_value')
293        self.assertIsInterned(v)
294
295    @cpython_only
296    def test_interned_string_in_tuple(self):
297        co = compile('res = ("str_value",)', '?', 'exec')
298        v = self.find_const(co.co_consts, ('str_value',))
299        self.assertIsInterned(v[0])
300
301    @cpython_only
302    def test_interned_string_in_frozenset(self):
303        co = compile('res = a in {"str_value"}', '?', 'exec')
304        v = self.find_const(co.co_consts, frozenset(('str_value',)))
305        self.assertIsInterned(tuple(v)[0])
306
307    @cpython_only
308    def test_interned_string_default(self):
309        def f(a='str_value'):
310            return a
311        self.assertIsInterned(f())
312
313    @cpython_only
314    def test_interned_string_with_null(self):
315        co = compile(r'res = "str\0value!"', '?', 'exec')
316        v = self.find_const(co.co_consts, 'str\0value!')
317        self.assertIsNotInterned(v)
318
319
320class CodeWeakRefTest(unittest.TestCase):
321
322    def test_basic(self):
323        # Create a code object in a clean environment so that we know we have
324        # the only reference to it left.
325        namespace = {}
326        exec("def f(): pass", globals(), namespace)
327        f = namespace["f"]
328        del namespace
329
330        self.called = False
331        def callback(code):
332            self.called = True
333
334        # f is now the last reference to the function, and through it, the code
335        # object.  While we hold it, check that we can create a weakref and
336        # deref it.  Then delete it, and check that the callback gets called and
337        # the reference dies.
338        coderef = weakref.ref(f.__code__, callback)
339        self.assertTrue(bool(coderef()))
340        del f
341        self.assertFalse(bool(coderef()))
342        self.assertTrue(self.called)
343
344
345if check_impl_detail(cpython=True) and ctypes is not None:
346    py = ctypes.pythonapi
347    freefunc = ctypes.CFUNCTYPE(None,ctypes.c_voidp)
348
349    RequestCodeExtraIndex = py._PyEval_RequestCodeExtraIndex
350    RequestCodeExtraIndex.argtypes = (freefunc,)
351    RequestCodeExtraIndex.restype = ctypes.c_ssize_t
352
353    SetExtra = py._PyCode_SetExtra
354    SetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t, ctypes.c_voidp)
355    SetExtra.restype = ctypes.c_int
356
357    GetExtra = py._PyCode_GetExtra
358    GetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t,
359                         ctypes.POINTER(ctypes.c_voidp))
360    GetExtra.restype = ctypes.c_int
361
362    LAST_FREED = None
363    def myfree(ptr):
364        global LAST_FREED
365        LAST_FREED = ptr
366
367    FREE_FUNC = freefunc(myfree)
368    FREE_INDEX = RequestCodeExtraIndex(FREE_FUNC)
369
370    class CoExtra(unittest.TestCase):
371        def get_func(self):
372            # Defining a function causes the containing function to have a
373            # reference to the code object.  We need the code objects to go
374            # away, so we eval a lambda.
375            return eval('lambda:42')
376
377        def test_get_non_code(self):
378            f = self.get_func()
379
380            self.assertRaises(SystemError, SetExtra, 42, FREE_INDEX,
381                              ctypes.c_voidp(100))
382            self.assertRaises(SystemError, GetExtra, 42, FREE_INDEX,
383                              ctypes.c_voidp(100))
384
385        def test_bad_index(self):
386            f = self.get_func()
387            self.assertRaises(SystemError, SetExtra, f.__code__,
388                              FREE_INDEX+100, ctypes.c_voidp(100))
389            self.assertEqual(GetExtra(f.__code__, FREE_INDEX+100,
390                              ctypes.c_voidp(100)), 0)
391
392        def test_free_called(self):
393            # Verify that the provided free function gets invoked
394            # when the code object is cleaned up.
395            f = self.get_func()
396
397            SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(100))
398            del f
399            self.assertEqual(LAST_FREED, 100)
400
401        def test_get_set(self):
402            # Test basic get/set round tripping.
403            f = self.get_func()
404
405            extra = ctypes.c_voidp()
406
407            SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(200))
408            # reset should free...
409            SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(300))
410            self.assertEqual(LAST_FREED, 200)
411
412            extra = ctypes.c_voidp()
413            GetExtra(f.__code__, FREE_INDEX, extra)
414            self.assertEqual(extra.value, 300)
415            del f
416
417        def test_free_different_thread(self):
418            # Freeing a code object on a different thread then
419            # where the co_extra was set should be safe.
420            f = self.get_func()
421            class ThreadTest(threading.Thread):
422                def __init__(self, f, test):
423                    super().__init__()
424                    self.f = f
425                    self.test = test
426                def run(self):
427                    del self.f
428                    self.test.assertEqual(LAST_FREED, 500)
429
430            SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(500))
431            tt = ThreadTest(f, self)
432            del f
433            tt.start()
434            tt.join()
435            self.assertEqual(LAST_FREED, 500)
436
437        @cpython_only
438        def test_clean_stack_on_return(self):
439
440            def f(x):
441                return x
442
443            code = f.__code__
444            ct = type(f.__code__)
445
446            # Insert an extra LOAD_FAST, this duplicates the value of
447            # 'x' in the stack, leaking it if the frame is not properly
448            # cleaned up upon exit.
449
450            bytecode = list(code.co_code)
451            bytecode.insert(-2, opcode.opmap['LOAD_FAST'])
452            bytecode.insert(-2, 0)
453
454            c = ct(code.co_argcount, code.co_posonlyargcount,
455                   code.co_kwonlyargcount, code.co_nlocals, code.co_stacksize+1,
456                   code.co_flags, bytes(bytecode),
457                   code.co_consts, code.co_names, code.co_varnames,
458                   code.co_filename, code.co_name, code.co_firstlineno,
459                   code.co_lnotab, code.co_freevars, code.co_cellvars)
460            new_function = type(f)(c, f.__globals__, 'nf', f.__defaults__, f.__closure__)
461
462            class Var:
463                pass
464            the_object = Var()
465            var = weakref.ref(the_object)
466
467            new_function(the_object)
468
469            # Check if the_object is leaked
470            del the_object
471            assert var() is None
472
473
474def test_main(verbose=None):
475    from test import test_code
476    run_doctest(test_code, verbose)
477    tests = [CodeTest, CodeConstsTest, CodeWeakRefTest]
478    if check_impl_detail(cpython=True) and ctypes is not None:
479        tests.append(CoExtra)
480    run_unittest(*tests)
481
482if __name__ == "__main__":
483    test_main()
484