bpo-33672: Fix Task.__repr__ crash with Cython's bogus coroutines (GH-7161)
This commit is contained in:
parent
8267ea2e84
commit
989b9e0e6d
|
@ -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.
|
|
||||||
|
|
||||||
|
def get_name(coro):
|
||||||
|
# Coroutines compiled with Cython sometimes don't have
|
||||||
|
# proper __qualname__ or __name__. While that is a bug
|
||||||
|
# in Cython, asyncio shouldn't crash with an AttributeError
|
||||||
|
# in its __repr__ functions.
|
||||||
|
if is_corowrapper:
|
||||||
|
return format_helpers._format_callback(coro.func, (), {})
|
||||||
|
|
||||||
|
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:
|
||||||
|
return coro.cr_running
|
||||||
|
except AttributeError:
|
||||||
|
try:
|
||||||
|
return coro.gi_running
|
||||||
|
except AttributeError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
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__.
|
# Built-in types might not have __qualname__ or __name__.
|
||||||
coro_name = getattr(
|
if is_running(coro):
|
||||||
coro, '__qualname__',
|
|
||||||
getattr(coro, '__name__', type(coro).__name__))
|
|
||||||
coro_name = f'{coro_name}()'
|
|
||||||
|
|
||||||
running = False
|
|
||||||
try:
|
|
||||||
running = coro.cr_running
|
|
||||||
except AttributeError:
|
|
||||||
try:
|
|
||||||
running = coro.gi_running
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if running:
|
|
||||||
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}'
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Fix Task.__repr__ crash with Cython's bogus coroutines
|
Loading…
Reference in New Issue