Skip to content

Infinite Loop in typing.get_type_hints() via Circular __wrapped__ #146553

@raminfp

Description

@raminfp

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directorytopic-typingtype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions