• 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
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