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 killed
Longer cycle:
def a(): pass
def b(): pass
a.__wrapped__ = b
b.__wrapped__ = a # mutual cycle
typing.get_type_hints(a) # also hangs forever
Root 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 detection
The 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 func
inspect.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
Linked PRs
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 raisingValueErroron cycle detection.get_type_hints()does not use that helper and has no equivalent guard.Minimal Reproducer
Longer cycle:
Root Cause
File:
Lib/typing.py,get_type_hints(), lines 2488-2489 (CPythonmain):The loop walks the
__wrapped__chain to reach the innermost function and obtainits
__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:inspect.unwrap()raisesValueErrorimmediately on cycle detection.Fix
Replace the bare
whileloop withinspect.unwrap():Or inline the visited-set guard:
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Linked PRs