-
-
Notifications
You must be signed in to change notification settings - Fork 34.3k
Description
Bug report
Bug description:
typing.get_type_hints() traverses the __wrapped__ attribute chain with no cycle detection. A callable whose __wrapped__ refers back to itself (or forms any cycle) causes an infinite loop, hanging the Python process indefinitely.
inspect.unwrap() solves the identical problem correctly by raising ValueError on cycle detection. get_type_hints() does not use that helper and has no equivalent guard.
Minimal Reproducer
import typing
def f(): pass
f.__wrapped__ = f # direct self-reference
typing.get_type_hints(f) # hangs forever - process must be killedLonger cycle:
def a(): pass
def b(): pass
a.__wrapped__ = b
b.__wrapped__ = a # mutual cycle
typing.get_type_hints(a) # also hangs foreverRoot Cause
File: Lib/typing.py, get_type_hints(), lines 2488-2489 (CPython main):
# Find globalns for the unwrapped object.
while hasattr(nsobj, '__wrapped__'):
nsobj = nsobj.__wrapped__ # NO cycle detectionThe loop walks the __wrapped__ chain to reach the innermost function and obtain
its __globals__. There is no visited-set, no counter, and no recursion limit.
Comparison with inspect.unwrap()
Lib/inspect.py, unwrap() (lines 679-692) solves the identical problem:
memo = {id(f): f}
recursion_limit = sys.getrecursionlimit()
while not isinstance(func, type) and hasattr(func, '__wrapped__'):
func = func.__wrapped__
id_func = id(func)
if (id_func in memo) or (len(memo) >= recursion_limit):
raise ValueError('wrapper loop when unwrapping {!r}'.format(f))
memo[id_func] = func
return funcinspect.unwrap() raises ValueError immediately on cycle detection.
Fix
Replace the bare while loop with inspect.unwrap():
import inspect as _inspect
nsobj = obj
try:
nsobj = _inspect.unwrap(nsobj)
except (TypeError, ValueError):
pass
globalns = getattr(nsobj, '__globals__', {})Or inline the visited-set guard:
nsobj = obj
_seen = {id(nsobj)}
while hasattr(nsobj, '__wrapped__'):
nsobj = nsobj.__wrapped__
_id = id(nsobj)
if _id in _seen:
raise ValueError(f"wrapper loop when unwrapping {obj!r}")
_seen.add(_id)
globalns = getattr(nsobj, '__globals__', {})CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux