Bug report
Bug description:
annotationlib.get_annotations() hangs indefinitely when called with eval_str=True on a callable that has a circular __wrapped__ reference chain.
Reproducer
importannotationlibdeff(x: 'int') ->'str': passf.__wrapped__=f# self-referential cycle# Hangs forever, never returnsannotationlib.get_annotations(f, eval_str=True)
Two-node cycle also triggers it:
defg(): passf.__wrapped__=gg.__wrapped__=fannotationlib.get_annotations(f, eval_str=True) # hangs
Root Cause
In Lib/annotationlib.py, get_annotations() has an early-return guard at line 1010:
ifnoteval_str:
returndict(ann) # fast path, skips unwrap entirely for default eval_str=False
When eval_str=True the code falls through to a while True: loop (lines 1039–1048)
that unwraps __wrapped__ chains with no cycle detection:
ifunwrapisnotNone:
whileTrue:
ifhasattr(unwrap, "__wrapped__"):
unwrap=unwrap.__wrapped__# no cycle detectioncontinueiffunctools:=sys.modules.get("functools"):
ifisinstance(unwrap, functools.partial):
unwrap=unwrap.func# also no cycle detectioncontinuebreakFix
Apply the same cycle-detection pattern used by inspect.unwrap() (visited id-set):
ifunwrapisnotNone:
seen= {id(unwrap)}
whileTrue:
ifhasattr(unwrap, "__wrapped__"):
candidate=unwrap.__wrapped__ifid(candidate) inseen:
breakseen.add(id(candidate))
unwrap=candidatecontinueiffunctools:=sys.modules.get("functools"):
ifisinstance(unwrap, functools.partial):
candidate=unwrap.funcifid(candidate) inseen:
breakseen.add(id(candidate))
unwrap=candidatecontinuebreakifhasattr(unwrap, "__globals__"):
obj_globals=unwrap.__globals__CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Linked PRs
Bug report
Bug description:
annotationlib.get_annotations()hangs indefinitely when called witheval_str=Trueon a callable that has a circular__wrapped__reference chain.Reproducer
Two-node cycle also triggers it:
Root Cause
In
Lib/annotationlib.py,get_annotations()has an early-return guard at line 1010:When
eval_str=Truethe code falls through to awhile True:loop (lines 1039–1048)that unwraps
__wrapped__chains with no cycle detection:Fix
Apply the same cycle-detection pattern used by
inspect.unwrap()(visitedid-set):CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Linked PRs