From: Jeroen Demeyer Date: Tue, 2 Apr 2019 14:03:42 +0000 (+0200) Subject: bpo-33261: guard access to __code__ attribute in inspect (GH-6448) X-Git-Tag: v3.8.0a4~270 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=fcef60f59d04c63b3540b4c4886226098c1bacd1;p=thirdparty%2FPython%2Fcpython.git bpo-33261: guard access to __code__ attribute in inspect (GH-6448) --- diff --git a/Lib/inspect.py b/Lib/inspect.py index 8c398bd3534c..d8475c63f901 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -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.""" diff --git a/Lib/test/inspect_fodder.py b/Lib/test/inspect_fodder.py index ff3f0e4b73b9..667507768ccb 100644 --- a/Lib/test/inspect_fodder.py +++ b/Lib/test/inspect_fodder.py @@ -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 diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index bc675aa5df21..7d74746b48b4 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -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 index 000000000000..1ffb9ddccbb0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-04-11-11-41-52.bpo-33291.-xLGf8.rst @@ -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.