@@ -65,6 +65,7 @@ typedef struct {
6565 PyObject * _abc_cache ;
6666 PyObject * _abc_negative_cache ;
6767 uint64_t _abc_negative_cache_version ;
68+ bool _abc_issubclasscheck_recursive ;
6869} _abc_data ;
6970
7071#define _abc_data_CAST (op ) ((_abc_data *)(op))
@@ -89,6 +90,24 @@ set_cache_version(_abc_data *impl, uint64_t version)
8990#endif
9091}
9192
93+ static inline bool
94+ is_issubclasscheck_recursive (_abc_data * impl )
95+ {
96+ return impl -> _abc_issubclasscheck_recursive ;
97+ }
98+
99+ static inline void
100+ set_issubclasscheck_recursive (_abc_data * impl )
101+ {
102+ impl -> _abc_issubclasscheck_recursive = true;
103+ }
104+
105+ static inline void
106+ unset_issubclasscheck_recursive (_abc_data * impl )
107+ {
108+ impl -> _abc_issubclasscheck_recursive = false;
109+ }
110+
92111static int
93112abc_data_traverse (PyObject * op , visitproc visit , void * arg )
94113{
@@ -139,6 +158,7 @@ abc_data_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
139158 self -> _abc_cache = NULL ;
140159 self -> _abc_negative_cache = NULL ;
141160 self -> _abc_negative_cache_version = get_invalidation_counter (state );
161+ self -> _abc_issubclasscheck_recursive = false;
142162 return (PyObject * ) self ;
143163}
144164
@@ -177,6 +197,23 @@ _get_impl(PyObject *module, PyObject *self)
177197 return (_abc_data * )impl ;
178198}
179199
200+ static _abc_data *
201+ _get_impl_optional (PyObject * module , PyObject * self )
202+ {
203+ _abcmodule_state * state = get_abc_state (module );
204+ PyObject * impl = NULL ;
205+ int res = PyObject_GetOptionalAttr (self , & _Py_ID (_abc_impl ), & impl );
206+ if (res <= 0 ) {
207+ return NULL ;
208+ }
209+ if (!Py_IS_TYPE (impl , state -> _abc_data_type )) {
210+ PyErr_SetString (PyExc_TypeError , "_abc_impl is set to a wrong type" );
211+ Py_DECREF (impl );
212+ return NULL ;
213+ }
214+ return (_abc_data * )impl ;
215+ }
216+
180217static int
181218_in_weak_set (_abc_data * impl , PyObject * * pset , PyObject * obj )
182219{
@@ -347,11 +384,12 @@ _abc__get_dump(PyObject *module, PyObject *self)
347384 }
348385 PyObject * res ;
349386 Py_BEGIN_CRITICAL_SECTION (impl );
350- res = Py_BuildValue ("NNNK " ,
387+ res = Py_BuildValue ("NNNKK " ,
351388 PySet_New (impl -> _abc_registry ),
352389 PySet_New (impl -> _abc_cache ),
353390 PySet_New (impl -> _abc_negative_cache ),
354- get_cache_version (impl ));
391+ get_cache_version (impl ),
392+ is_issubclasscheck_recursive (impl ));
355393 Py_END_CRITICAL_SECTION ();
356394 Py_DECREF (impl );
357395 return res ;
@@ -814,23 +852,46 @@ _abc__abc_subclasscheck_impl(PyObject *module, PyObject *self,
814852 if (scls == NULL ) {
815853 goto end ;
816854 }
855+
856+ _abc_data * scls_impl = _get_impl_optional (module , scls );
857+ if (scls_impl != NULL ) {
858+ /*
859+ If inside recursive issubclass check, avoid adding classes to any cache because this
860+ may drastically increase memory usage.
861+ Unfortunately, issubclass/__subclasscheck__ don't accept third argument with context,
862+ so using global context within ABCMeta.
863+ This is done only on first method call, others will use cached result.
864+ */
865+ set_issubclasscheck_recursive (scls_impl );
866+ }
867+
817868 int r = PyObject_IsSubclass (subclass , scls );
818869 Py_DECREF (scls );
870+
871+ if (scls_impl != NULL ) {
872+ unset_issubclasscheck_recursive (scls_impl );
873+ Py_DECREF (scls_impl );
874+ }
875+
876+ if (r < 0 ) {
877+ goto end ;
878+ }
819879 if (r > 0 ) {
820- if (_add_to_weak_set (impl , & impl -> _abc_cache , subclass ) < 0 ) {
821- goto end ;
880+ if (!is_issubclasscheck_recursive (impl )) {
881+ if (_add_to_weak_set (impl , & impl -> _abc_cache , subclass ) < 0 ) {
882+ goto end ;
883+ }
822884 }
823885 result = Py_True ;
824886 goto end ;
825887 }
826- if (r < 0 ) {
827- goto end ;
828- }
829888 }
830889
831890 /* No dice; update negative cache. */
832- if (_add_to_weak_set (impl , & impl -> _abc_negative_cache , subclass ) < 0 ) {
833- goto end ;
891+ if (!is_issubclasscheck_recursive (impl )) {
892+ if (_add_to_weak_set (impl , & impl -> _abc_negative_cache , subclass ) < 0 ) {
893+ goto end ;
894+ }
834895 }
835896 result = Py_False ;
836897
0 commit comments