Skip to content

Commit 5a2a44d

Browse files
1st1miss-islington
authored andcommitted
bpo-33672: Fix Task.__repr__ crash with Cython's bogus coroutines (pythonGH-7161)
(cherry picked from commit 989b9e0) Co-authored-by: Yury Selivanov <yury@magic.io>
1 parent 3757939 commit 5a2a44d

File tree

4 files changed

+61
-40
lines changed

4 files changed

+61
-40
lines changed

Lib/asyncio/coroutines.py

Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -189,66 +189,75 @@ def iscoroutine(obj):
189189
def _format_coroutine(coro):
190190
assert iscoroutine(coro)
191191

192-
if not hasattr(coro, 'cr_code') and not hasattr(coro, 'gi_code'):
193-
# Most likely a built-in type or a Cython coroutine.
194-
195-
# Built-in types might not have __qualname__ or __name__.
196-
coro_name = getattr(
197-
coro, '__qualname__',
198-
getattr(coro, '__name__', type(coro).__name__))
199-
coro_name = f'{coro_name}()'
192+
is_corowrapper = isinstance(coro, CoroWrapper)
193+
194+
def get_name(coro):
195+
# Coroutines compiled with Cython sometimes don't have
196+
# proper __qualname__ or __name__. While that is a bug
197+
# in Cython, asyncio shouldn't crash with an AttributeError
198+
# in its __repr__ functions.
199+
if is_corowrapper:
200+
return format_helpers._format_callback(coro.func, (), {})
201+
202+
if hasattr(coro, '__qualname__') and coro.__qualname__:
203+
coro_name = coro.__qualname__
204+
elif hasattr(coro, '__name__') and coro.__name__:
205+
coro_name = coro.__name__
206+
else:
207+
# Stop masking Cython bugs, expose them in a friendly way.
208+
coro_name = f'<{type(coro).__name__} without __name__>'
209+
return f'{coro_name}()'
200210

201-
running = False
211+
def is_running(coro):
202212
try:
203-
running = coro.cr_running
213+
return coro.cr_running
204214
except AttributeError:
205215
try:
206-
running = coro.gi_running
216+
return coro.gi_running
207217
except AttributeError:
208-
pass
218+
return False
209219

210-
if running:
220+
coro_code = None
221+
if hasattr(coro, 'cr_code') and coro.cr_code:
222+
coro_code = coro.cr_code
223+
elif hasattr(coro, 'gi_code') and coro.gi_code:
224+
coro_code = coro.gi_code
225+
226+
coro_name = get_name(coro)
227+
228+
if not coro_code:
229+
# Built-in types might not have __qualname__ or __name__.
230+
if is_running(coro):
211231
return f'{coro_name} running'
212232
else:
213233
return coro_name
214234

215-
coro_name = None
216-
if isinstance(coro, CoroWrapper):
217-
func = coro.func
218-
coro_name = coro.__qualname__
219-
if coro_name is not None:
220-
coro_name = f'{coro_name}()'
221-
else:
222-
func = coro
223-
224-
if coro_name is None:
225-
coro_name = format_helpers._format_callback(func, (), {})
226-
227-
try:
228-
coro_code = coro.gi_code
229-
except AttributeError:
230-
coro_code = coro.cr_code
231-
232-
try:
235+
coro_frame = None
236+
if hasattr(coro, 'gi_frame') and coro.gi_frame:
233237
coro_frame = coro.gi_frame
234-
except AttributeError:
238+
elif hasattr(coro, 'cr_frame') and coro.cr_frame:
235239
coro_frame = coro.cr_frame
236240

237-
filename = coro_code.co_filename
241+
# If Cython's coroutine has a fake code object without proper
242+
# co_filename -- expose that.
243+
filename = coro_code.co_filename or '<empty co_filename>'
244+
238245
lineno = 0
239-
if (isinstance(coro, CoroWrapper) and
240-
not inspect.isgeneratorfunction(coro.func) and
241-
coro.func is not None):
246+
if (is_corowrapper and
247+
coro.func is not None and
248+
not inspect.isgeneratorfunction(coro.func)):
242249
source = format_helpers._get_function_source(coro.func)
243250
if source is not None:
244251
filename, lineno = source
245252
if coro_frame is None:
246253
coro_repr = f'{coro_name} done, defined at {filename}:{lineno}'
247254
else:
248255
coro_repr = f'{coro_name} running, defined at {filename}:{lineno}'
256+
249257
elif coro_frame is not None:
250258
lineno = coro_frame.f_lineno
251259
coro_repr = f'{coro_name} running at {filename}:{lineno}'
260+
252261
else:
253262
lineno = coro_code.co_firstlineno
254263
coro_repr = f'{coro_name} done, defined at {filename}:{lineno}'

Lib/asyncio/format_helpers.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import functools
22
import inspect
33
import reprlib
4+
import sys
45
import traceback
56

67
from . import constants
@@ -45,10 +46,10 @@ def _format_callback(func, args, kwargs, suffix=''):
4546
suffix = _format_args_and_kwargs(args, kwargs) + suffix
4647
return _format_callback(func.func, func.args, func.keywords, suffix)
4748

48-
if hasattr(func, '__qualname__'):
49-
func_repr = getattr(func, '__qualname__')
50-
elif hasattr(func, '__name__'):
51-
func_repr = getattr(func, '__name__')
49+
if hasattr(func, '__qualname__') and func.__qualname__:
50+
func_repr = func.__qualname__
51+
elif hasattr(func, '__name__') and func.__name__:
52+
func_repr = func.__name__
5253
else:
5354
func_repr = repr(func)
5455

Lib/test/test_asyncio/test_events.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2784,11 +2784,21 @@ def test_coroutine_like_object_debug_formatting(self):
27842784
coro.cr_running = True
27852785
self.assertEqual(coroutines._format_coroutine(coro), 'BBB() running')
27862786

2787+
coro.__name__ = coro.__qualname__ = None
2788+
self.assertEqual(coroutines._format_coroutine(coro),
2789+
'<CoroLike without __name__>() running')
2790+
27872791
coro = CoroLike()
2792+
coro.__qualname__ = 'CoroLike'
27882793
# Some coroutines might not have '__name__', such as
27892794
# built-in async_gen.asend().
27902795
self.assertEqual(coroutines._format_coroutine(coro), 'CoroLike()')
27912796

2797+
coro = CoroLike()
2798+
coro.__qualname__ = 'AAA'
2799+
coro.cr_code = None
2800+
self.assertEqual(coroutines._format_coroutine(coro), 'AAA()')
2801+
27922802

27932803
class TimerTests(unittest.TestCase):
27942804

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix Task.__repr__ crash with Cython's bogus coroutines

0 commit comments

Comments
 (0)