summaryrefslogtreecommitdiffstats
path: root/Lib/test
diff options
context:
space:
mode:
authorNick Coghlan <ncoghlan@gmail.com>2017-12-03 13:32:54 (GMT)
committerGitHub <noreply@github.com>2017-12-03 13:32:54 (GMT)
commitc8f32aae0aa173e122cf4c0592caec620d0d1de9 (patch)
treea12510b536a9b6bb07c9370c3ef1eb89144dc991 /Lib/test
parent2ad350a713360e89ae6d264924cd28f519b8b22c (diff)
downloadcpython-c8f32aae0aa173e122cf4c0592caec620d0d1de9.zip
cpython-c8f32aae0aa173e122cf4c0592caec620d0d1de9.tar.gz
cpython-c8f32aae0aa173e122cf4c0592caec620d0d1de9.tar.bz2
[3.6] bpo-32176: Set CO_NOFREE in the code object constructor (GH-4684)
Previously, CO_NOFREE was set in the compiler, which meant it could end up being set incorrectly when code objects were created directly. Setting it in the constructor based on freevars and cellvars ensures it is always accurate, regardless of how the code object is defined. (cherry picked from commit 078f1814f1a4413a2a0fdb8cf4490ee0fc98ef34)
Diffstat (limited to 'Lib/test')
-rw-r--r--Lib/test/test_code.py45
1 files changed, 45 insertions, 0 deletions
diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py
index 90cb584..55faf4c 100644
--- a/Lib/test/test_code.py
+++ b/Lib/test/test_code.py
@@ -102,6 +102,7 @@ consts: ('None',)
"""
+import inspect
import sys
import threading
import unittest
@@ -130,6 +131,10 @@ def dump(co):
print("%s: %s" % (attr, getattr(co, "co_" + attr)))
print("consts:", tuple(consts(co.co_consts)))
+# Needed for test_closure_injection below
+# Defined at global scope to avoid implicitly closing over __class__
+def external_getitem(self, i):
+ return f"Foreign getitem: {super().__getitem__(i)}"
class CodeTest(unittest.TestCase):
@@ -141,6 +146,46 @@ class CodeTest(unittest.TestCase):
self.assertEqual(co.co_name, "funcname")
self.assertEqual(co.co_firstlineno, 15)
+ @cpython_only
+ def test_closure_injection(self):
+ # From https://bugs.python.org/issue32176
+ from types import FunctionType, CodeType
+
+ def create_closure(__class__):
+ return (lambda: __class__).__closure__
+
+ def new_code(c):
+ '''A new code object with a __class__ cell added to freevars'''
+ return CodeType(
+ c.co_argcount, c.co_kwonlyargcount, c.co_nlocals,
+ c.co_stacksize, c.co_flags, c.co_code, c.co_consts, c.co_names,
+ c.co_varnames, c.co_filename, c.co_name, c.co_firstlineno,
+ c.co_lnotab, c.co_freevars + ('__class__',), c.co_cellvars)
+
+ def add_foreign_method(cls, name, f):
+ code = new_code(f.__code__)
+ assert not f.__closure__
+ closure = create_closure(cls)
+ defaults = f.__defaults__
+ setattr(cls, name, FunctionType(code, globals(), name, defaults, closure))
+
+ class List(list):
+ pass
+
+ add_foreign_method(List, "__getitem__", external_getitem)
+
+ # Ensure the closure injection actually worked
+ function = List.__getitem__
+ class_ref = function.__closure__[0].cell_contents
+ self.assertIs(class_ref, List)
+
+ # Ensure the code correctly indicates it accesses a free variable
+ self.assertFalse(function.__code__.co_flags & inspect.CO_NOFREE,
+ hex(function.__code__.co_flags))
+
+ # Ensure the zero-arg super() call in the injected method works
+ obj = List([1, 2, 3])
+ self.assertEqual(obj[0], "Foreign getitem: 1")
def isinterned(s):
return s is sys.intern(('_' + s + '_')[1:-1])