From: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> Date: Thu, 23 Apr 2026 02:55:40 +0000 (+0200) Subject: [3.13] gh-146553: Fix infinite loop in typing.get_type_hints() on circular __wrapped_... X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=f540bc9d4a2a59bc976d429506818ff5e9ae117e;p=thirdparty%2FPython%2Fcpython.git [3.13] gh-146553: Fix infinite loop in typing.get_type_hints() on circular __wrapped__ (GH-148595) (#148896) (cherry picked from commit be833e658aaf6703b0dd0c0dadb893d72cbe4c77) Co-authored-by: Shamil --- diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index abe78c38439e..c3930252948f 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -6979,6 +6979,24 @@ class GetTypeHintTests(BaseTestCase): self.assertEqual(gth(ForRefExample.func), expects) self.assertEqual(gth(ForRefExample.nested), expects) + def test_get_type_hints_wrapped_cycle_self(self): + # gh-146553: __wrapped__ self-reference must raise ValueError, + # not loop forever. + def f(x: int) -> str: ... + f.__wrapped__ = f + with self.assertRaisesRegex(ValueError, 'wrapper loop'): + get_type_hints(f) + + def test_get_type_hints_wrapped_cycle_mutual(self): + # gh-146553: mutual __wrapped__ cycle (a -> b -> a) must raise + # ValueError, not loop forever. + def a(): ... + def b(): ... + a.__wrapped__ = b + b.__wrapped__ = a + with self.assertRaisesRegex(ValueError, 'wrapper loop'): + get_type_hints(a) + def test_get_type_hints_annotated(self): def foobar(x: List['X']): ... X = Annotated[int, (1, 10)] diff --git a/Lib/typing.py b/Lib/typing.py index cbc6d90e13a8..629204cf63ad 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2483,8 +2483,12 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False): else: nsobj = obj # Find globalns for the unwrapped object. + seen = {id(nsobj)} while hasattr(nsobj, '__wrapped__'): nsobj = nsobj.__wrapped__ + if id(nsobj) in seen: + raise ValueError(f'wrapper loop when unwrapping {obj!r}') + seen.add(id(nsobj)) globalns = getattr(nsobj, '__globals__', {}) if localns is None: localns = globalns diff --git a/Misc/NEWS.d/next/Library/2026-04-15-11-00-39.gh-issue-146553.VGOsoP.rst b/Misc/NEWS.d/next/Library/2026-04-15-11-00-39.gh-issue-146553.VGOsoP.rst new file mode 100644 index 000000000000..44216318d474 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-15-11-00-39.gh-issue-146553.VGOsoP.rst @@ -0,0 +1,2 @@ +Fix infinite loop in :func:`typing.get_type_hints` when ``__wrapped__`` +forms a cycle. Patch by Shamil Abdulaev.