]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-146553: Fix infinite loop in typing.get_type_hints() on circular __wrapped_...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Thu, 23 Apr 2026 02:57:38 +0000 (04:57 +0200)
committerGitHub <noreply@github.com>
Thu, 23 Apr 2026 02:57:38 +0000 (02:57 +0000)
(cherry picked from commit be833e658aaf6703b0dd0c0dadb893d72cbe4c77)

Co-authored-by: Shamil <ashm.tech@proton.me>
Lib/test/test_typing.py
Lib/typing.py
Misc/NEWS.d/next/Library/2026-04-15-11-00-39.gh-issue-146553.VGOsoP.rst [new file with mode: 0644]

index b660368144b1cd3a23577b60c5dd392289a26943..b30e30d1751ed7d638a5666af560c960787fe368 100644 (file)
@@ -6778,6 +6778,24 @@ class GetTypeHintsTests(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)]
index 380211183a41335123ed1a277d62d349e5863786..5da5cb0d22f1484d8142ea5581f24dce711ec8b3 100644 (file)
@@ -2416,8 +2416,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 (file)
index 0000000..4421631
--- /dev/null
@@ -0,0 +1,2 @@
+Fix infinite loop in :func:`typing.get_type_hints` when ``__wrapped__``
+forms a cycle. Patch by Shamil Abdulaev.