bpo-33672: Fix Task.__repr__ crash with Cython's bogus coroutines (GH-7161)

This commit is contained in:
Yury Selivanov 2018-05-28 16:27:34 -04:00 committed by GitHub
parent 8267ea2e84
commit 989b9e0e6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 60 additions and 39 deletions

View File

@ -189,56 +189,63 @@ def iscoroutine(obj):
def _format_coroutine(coro): def _format_coroutine(coro):
assert iscoroutine(coro) assert iscoroutine(coro)
if not hasattr(coro, 'cr_code') and not hasattr(coro, 'gi_code'): is_corowrapper = isinstance(coro, CoroWrapper)
# Most likely a built-in type or a Cython coroutine.
# Built-in types might not have __qualname__ or __name__. def get_name(coro):
coro_name = getattr( # Coroutines compiled with Cython sometimes don't have
coro, '__qualname__', # proper __qualname__ or __name__. While that is a bug
getattr(coro, '__name__', type(coro).__name__)) # in Cython, asyncio shouldn't crash with an AttributeError
coro_name = f'{coro_name}()' # in its __repr__ functions.
if is_corowrapper:
return format_helpers._format_callback(coro.func, (), {})
running = False if hasattr(coro, '__qualname__') and coro.__qualname__:
coro_name = coro.__qualname__
elif hasattr(coro, '__name__') and coro.__name__:
coro_name = coro.__name__
else:
# Stop masking Cython bugs, expose them in a friendly way.
coro_name = f'<{type(coro).__name__} without __name__>'
return f'{coro_name}()'
def is_running(coro):
try: try:
running = coro.cr_running return coro.cr_running
except AttributeError: except AttributeError:
try: try:
running = coro.gi_running return coro.gi_running
except AttributeError: except AttributeError:
pass return False
if running: coro_code = None
if hasattr(coro, 'cr_code') and coro.cr_code:
coro_code = coro.cr_code
elif hasattr(coro, 'gi_code') and coro.gi_code:
coro_code = coro.gi_code
coro_name = get_name(coro)
if not coro_code:
# Built-in types might not have __qualname__ or __name__.
if is_running(coro):
return f'{coro_name} running' return f'{coro_name} running'
else: else:
return coro_name return coro_name
coro_name = None coro_frame = None
if isinstance(coro, CoroWrapper): if hasattr(coro, 'gi_frame') and coro.gi_frame:
func = coro.func
coro_name = coro.__qualname__
if coro_name is not None:
coro_name = f'{coro_name}()'
else:
func = coro
if coro_name is None:
coro_name = format_helpers._format_callback(func, (), {})
try:
coro_code = coro.gi_code
except AttributeError:
coro_code = coro.cr_code
try:
coro_frame = coro.gi_frame coro_frame = coro.gi_frame
except AttributeError: elif hasattr(coro, 'cr_frame') and coro.cr_frame:
coro_frame = coro.cr_frame coro_frame = coro.cr_frame
filename = coro_code.co_filename # If Cython's coroutine has a fake code object without proper
# co_filename -- expose that.
filename = coro_code.co_filename or '<empty co_filename>'
lineno = 0 lineno = 0
if (isinstance(coro, CoroWrapper) and if (is_corowrapper and
not inspect.isgeneratorfunction(coro.func) and coro.func is not None and
coro.func is not None): not inspect.isgeneratorfunction(coro.func)):
source = format_helpers._get_function_source(coro.func) source = format_helpers._get_function_source(coro.func)
if source is not None: if source is not None:
filename, lineno = source filename, lineno = source
@ -246,9 +253,11 @@ def _format_coroutine(coro):
coro_repr = f'{coro_name} done, defined at {filename}:{lineno}' coro_repr = f'{coro_name} done, defined at {filename}:{lineno}'
else: else:
coro_repr = f'{coro_name} running, defined at {filename}:{lineno}' coro_repr = f'{coro_name} running, defined at {filename}:{lineno}'
elif coro_frame is not None: elif coro_frame is not None:
lineno = coro_frame.f_lineno lineno = coro_frame.f_lineno
coro_repr = f'{coro_name} running at {filename}:{lineno}' coro_repr = f'{coro_name} running at {filename}:{lineno}'
else: else:
lineno = coro_code.co_firstlineno lineno = coro_code.co_firstlineno
coro_repr = f'{coro_name} done, defined at {filename}:{lineno}' coro_repr = f'{coro_name} done, defined at {filename}:{lineno}'

View File

@ -1,6 +1,7 @@
import functools import functools
import inspect import inspect
import reprlib import reprlib
import sys
import traceback import traceback
from . import constants from . import constants
@ -45,10 +46,10 @@ def _format_callback(func, args, kwargs, suffix=''):
suffix = _format_args_and_kwargs(args, kwargs) + suffix suffix = _format_args_and_kwargs(args, kwargs) + suffix
return _format_callback(func.func, func.args, func.keywords, suffix) return _format_callback(func.func, func.args, func.keywords, suffix)
if hasattr(func, '__qualname__'): if hasattr(func, '__qualname__') and func.__qualname__:
func_repr = getattr(func, '__qualname__') func_repr = func.__qualname__
elif hasattr(func, '__name__'): elif hasattr(func, '__name__') and func.__name__:
func_repr = getattr(func, '__name__') func_repr = func.__name__
else: else:
func_repr = repr(func) func_repr = repr(func)

View File

@ -2784,11 +2784,21 @@ class HandleTests(test_utils.TestCase):
coro.cr_running = True coro.cr_running = True
self.assertEqual(coroutines._format_coroutine(coro), 'BBB() running') self.assertEqual(coroutines._format_coroutine(coro), 'BBB() running')
coro.__name__ = coro.__qualname__ = None
self.assertEqual(coroutines._format_coroutine(coro),
'<CoroLike without __name__>() running')
coro = CoroLike() coro = CoroLike()
coro.__qualname__ = 'CoroLike'
# Some coroutines might not have '__name__', such as # Some coroutines might not have '__name__', such as
# built-in async_gen.asend(). # built-in async_gen.asend().
self.assertEqual(coroutines._format_coroutine(coro), 'CoroLike()') self.assertEqual(coroutines._format_coroutine(coro), 'CoroLike()')
coro = CoroLike()
coro.__qualname__ = 'AAA'
coro.cr_code = None
self.assertEqual(coroutines._format_coroutine(coro), 'AAA()')
class TimerTests(unittest.TestCase): class TimerTests(unittest.TestCase):

View File

@ -0,0 +1 @@
Fix Task.__repr__ crash with Cython's bogus coroutines