Skip to content

Commit a2650b6

Browse files
committed
gh-92810: Add recursive issubclass check to _abc.c
1 parent 8d695fd commit a2650b6

File tree

2 files changed

+79
-15
lines changed

2 files changed

+79
-15
lines changed

Lib/_py_abc.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ class ABCMeta(type):
3232
# Note: this counter is private. Use `abc.get_cache_token()` for
3333
# external code.
3434
_abc_invalidation_counter = 0
35-
_abc_issubclass_context = WeakKeyDictionary()
3635

3736
def __new__(mcls, name, bases, namespace, /, **kwargs):
3837
cls = super().__new__(mcls, name, bases, namespace, **kwargs)
@@ -51,6 +50,7 @@ def __new__(mcls, name, bases, namespace, /, **kwargs):
5150
cls._abc_cache = WeakSet()
5251
cls._abc_negative_cache = WeakSet()
5352
cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter
53+
cls._abc_issubclasscheck_recursive = False
5454
return cls
5555

5656
def register(cls, subclass):
@@ -148,18 +148,21 @@ def __subclasscheck__(cls, subclass):
148148
# Unfortunately, issubclass/__subclasscheck__ don't accept third argument with context,
149149
# so using global context within ABCMeta.
150150
# This is done only on first method call, others will use cached result.
151-
scls_context = ABCMeta._abc_issubclass_context.setdefault(scls, WeakSet())
151+
scls_is_abc = hasattr(scls, "_abc_issubclasscheck_recursive")
152+
if scls_is_abc:
153+
scls._abc_issubclasscheck_recursive = True
154+
152155
try:
153-
scls_context.add(cls)
154156
result = issubclass(subclass, scls)
155157
finally:
156-
scls_context.remove(cls)
158+
if scls_is_abc:
159+
scls._abc_issubclasscheck_recursive = False
157160

158161
if result:
159-
if not ABCMeta._abc_issubclass_context.get(cls, None):
162+
if not cls._abc_issubclasscheck_recursive:
160163
cls._abc_cache.add(subclass)
161164
return True
162165

163-
if not ABCMeta._abc_issubclass_context.get(cls, None):
166+
if not cls._abc_issubclasscheck_recursive:
164167
cls._abc_negative_cache.add(subclass)
165168
return False

Modules/_abc.c

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
92111
static int
93112
abc_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+
180217
static 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

Comments
 (0)