]> granicus.if.org Git - python/commitdiff
bpo-33261: guard access to __code__ attribute in inspect (GH-6448)
authorJeroen Demeyer <J.Demeyer@UGent.be>
Tue, 2 Apr 2019 14:03:42 +0000 (16:03 +0200)
committerPetr Viktorin <encukou@gmail.com>
Tue, 2 Apr 2019 14:03:42 +0000 (16:03 +0200)
Lib/inspect.py
Lib/test/inspect_fodder.py
Lib/test/test_inspect.py
Misc/NEWS.d/next/Library/2018-04-11-11-41-52.bpo-33291.-xLGf8.rst [new file with mode: 0644]

index 8c398bd3534cb6a7fb949ad1c66d890a4bcd305c..d8475c63f9017f15a5782077bdbb79a72782e2d3 100644 (file)
@@ -168,23 +168,30 @@ def isfunction(object):
         __kwdefaults__  dict of keyword only parameters with defaults"""
     return isinstance(object, types.FunctionType)
 
+def _has_code_flag(f, flag):
+    """Return true if ``f`` is a function (or a method or functools.partial
+    wrapper wrapping a function) whose code object has the given ``flag``
+    set in its flags."""
+    while ismethod(f):
+        f = f.__func__
+    f = functools._unwrap_partial(f)
+    if not isfunction(f):
+        return False
+    return bool(f.__code__.co_flags & flag)
+
 def isgeneratorfunction(obj):
     """Return true if the object is a user-defined generator function.
 
     Generator function objects provide the same attributes as functions.
     See help(isfunction) for a list of attributes."""
-    obj = functools._unwrap_partial(obj)
-    return bool((isfunction(obj) or ismethod(obj)) and
-                obj.__code__.co_flags & CO_GENERATOR)
+    return _has_code_flag(obj, CO_GENERATOR)
 
 def iscoroutinefunction(obj):
     """Return true if the object is a coroutine function.
 
     Coroutine functions are defined with "async def" syntax.
     """
-    obj = functools._unwrap_partial(obj)
-    return bool(((isfunction(obj) or ismethod(obj)) and
-                obj.__code__.co_flags & CO_COROUTINE))
+    return _has_code_flag(obj, CO_COROUTINE)
 
 def isasyncgenfunction(obj):
     """Return true if the object is an asynchronous generator function.
@@ -192,9 +199,7 @@ def isasyncgenfunction(obj):
     Asynchronous generator functions are defined with "async def"
     syntax and have "yield" expressions in their body.
     """
-    obj = functools._unwrap_partial(obj)
-    return bool((isfunction(obj) or ismethod(obj)) and
-                obj.__code__.co_flags & CO_ASYNC_GENERATOR)
+    return _has_code_flag(obj, CO_ASYNC_GENERATOR)
 
 def isasyncgen(object):
     """Return true if the object is an asynchronous generator."""
index ff3f0e4b73b9ad644fed7ba42951d2850d6d6e8c..667507768ccb69fbe20dd54ab9f6ba39b5d48623 100644 (file)
@@ -80,3 +80,14 @@ try:
     raise Exception()
 except:
     tb = sys.exc_info()[2]
+
+class Callable:
+    def __call__(self, *args):
+        return args
+
+    def as_method_of(self, obj):
+        from types import MethodType
+        return MethodType(self, obj)
+
+custom_method = Callable().as_method_of(42)
+del Callable
index bc675aa5df21dd38c4c4f93eb2423637ed837deb..7d74746b48b499d576b31785af1bbd41ce51e562 100644 (file)
@@ -146,6 +146,7 @@ class TestPredicates(IsTestBase):
         self.istest(inspect.isfunction, 'mod.spam')
         self.istest(inspect.isfunction, 'mod.StupidGit.abuse')
         self.istest(inspect.ismethod, 'git.argue')
+        self.istest(inspect.ismethod, 'mod.custom_method')
         self.istest(inspect.ismodule, 'mod')
         self.istest(inspect.isdatadescriptor, 'collections.defaultdict.default_factory')
         self.istest(inspect.isgenerator, '(x for x in range(2))')
diff --git a/Misc/NEWS.d/next/Library/2018-04-11-11-41-52.bpo-33291.-xLGf8.rst b/Misc/NEWS.d/next/Library/2018-04-11-11-41-52.bpo-33291.-xLGf8.rst
new file mode 100644 (file)
index 0000000..1ffb9dd
--- /dev/null
@@ -0,0 +1,3 @@
+Do not raise AttributeError when calling the inspect functions
+isgeneratorfunction, iscoroutinefunction, isasyncgenfunction on a method
+created from an arbitrary callable. Instead, return False.