bpo-30570: Use Py_EnterRecursiveCall() in issubclass() (GH-29048)


* Use Py_EnterRecursiveCall() in issubclass()

Reviewed-by: Gregory P. Smith <greg@krypto.org> [Google]
(cherry picked from commit 423fa1c1817abfa8c3d1bc308ddbbd8f28b69d68)

Co-authored-by: Dennis Sweeney <36520290+sweeneyde@users.noreply.github.com>
diff --git a/Lib/test/test_isinstance.py b/Lib/test/test_isinstance.py
index 109c3f8..6ab44be 100644
--- a/Lib/test/test_isinstance.py
+++ b/Lib/test/test_isinstance.py
@@ -313,6 +313,36 @@ def __bases__(self):
         self.assertRaises(RecursionError, issubclass, int, X())
         self.assertRaises(RecursionError, isinstance, 1, X())
 
+    def test_infinite_recursion_via_bases_tuple(self):
+        """Regression test for bpo-30570."""
+        class Failure(object):
+            def __getattr__(self, attr):
+                return (self, None)
+
+        with self.assertRaises(RecursionError):
+            issubclass(Failure(), int)
+
+    def test_infinite_cycle_in_bases(self):
+        """Regression test for bpo-30570."""
+        class X:
+            @property
+            def __bases__(self):
+                return (self, self, self)
+        self.assertRaises(RecursionError, issubclass, X(), int)
+
+    def test_infinitely_many_bases(self):
+        """Regression test for bpo-30570."""
+        class X:
+            def __getattr__(self, attr):
+                self.assertEqual(attr, "__bases__")
+                class A:
+                    pass
+                class B:
+                    pass
+                A.__getattr__ = B.__getattr__ = X.__getattr__
+                return (A(), B())
+        self.assertRaises(RecursionError, issubclass, X(), int)
+
 
 def blowstack(fxn, arg, compare_to):
     # Make sure that calling isinstance with a deeply nested tuple for its