fix many custom mro() edge cases and improve code quality (#22735)
Patch by Eldar Abusalimov.
This commit is contained in:
parent
9125fe2e50
commit
104b9e0cca
|
@ -4996,11 +4996,236 @@ class SharedKeyTests(unittest.TestCase):
|
|||
self.assertLess(sys.getsizeof(vars(b)), sys.getsizeof({}))
|
||||
|
||||
|
||||
class DebugHelperMeta(type):
|
||||
"""
|
||||
Sets default __doc__ and simplifies repr() output.
|
||||
"""
|
||||
def __new__(mcls, name, bases, attrs):
|
||||
if attrs.get('__doc__') is None:
|
||||
attrs['__doc__'] = name # helps when debugging with gdb
|
||||
return type.__new__(mcls, name, bases, attrs)
|
||||
def __repr__(cls):
|
||||
return repr(cls.__name__)
|
||||
|
||||
|
||||
class MroTest(unittest.TestCase):
|
||||
"""
|
||||
Regressions for some bugs revealed through
|
||||
mcsl.mro() customization (typeobject.c: mro_internal()) and
|
||||
cls.__bases__ assignment (typeobject.c: type_set_bases()).
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.step = 0
|
||||
self.ready = False
|
||||
|
||||
def step_until(self, limit):
|
||||
ret = (self.step < limit)
|
||||
if ret:
|
||||
self.step += 1
|
||||
return ret
|
||||
|
||||
def test_incomplete_set_bases_on_self(self):
|
||||
"""
|
||||
type_set_bases must be aware that type->tp_mro can be NULL.
|
||||
"""
|
||||
class M(DebugHelperMeta):
|
||||
def mro(cls):
|
||||
if self.step_until(1):
|
||||
assert cls.__mro__ is None
|
||||
cls.__bases__ += ()
|
||||
|
||||
return type.mro(cls)
|
||||
|
||||
class A(metaclass=M):
|
||||
pass
|
||||
|
||||
def test_reent_set_bases_on_base(self):
|
||||
"""
|
||||
Deep reentrancy must not over-decref old_mro.
|
||||
"""
|
||||
class M(DebugHelperMeta):
|
||||
def mro(cls):
|
||||
if cls.__mro__ is not None and cls.__name__ == 'B':
|
||||
# 4-5 steps are usually enough to make it crash somewhere
|
||||
if self.step_until(10):
|
||||
A.__bases__ += ()
|
||||
|
||||
return type.mro(cls)
|
||||
|
||||
class A(metaclass=M):
|
||||
pass
|
||||
class B(A):
|
||||
pass
|
||||
B.__bases__ += ()
|
||||
|
||||
def test_reent_set_bases_on_direct_base(self):
|
||||
"""
|
||||
Similar to test_reent_set_bases_on_base, but may crash differently.
|
||||
"""
|
||||
class M(DebugHelperMeta):
|
||||
def mro(cls):
|
||||
base = cls.__bases__[0]
|
||||
if base is not object:
|
||||
if self.step_until(5):
|
||||
base.__bases__ += ()
|
||||
|
||||
return type.mro(cls)
|
||||
|
||||
class A(metaclass=M):
|
||||
pass
|
||||
class B(A):
|
||||
pass
|
||||
class C(B):
|
||||
pass
|
||||
|
||||
def test_reent_set_bases_tp_base_cycle(self):
|
||||
"""
|
||||
type_set_bases must check for an inheritance cycle not only through
|
||||
MRO of the type, which may be not yet updated in case of reentrance,
|
||||
but also through tp_base chain, which is assigned before diving into
|
||||
inner calls to mro().
|
||||
|
||||
Otherwise, the following snippet can loop forever:
|
||||
do {
|
||||
// ...
|
||||
type = type->tp_base;
|
||||
} while (type != NULL);
|
||||
|
||||
Functions that rely on tp_base (like solid_base and PyType_IsSubtype)
|
||||
would not be happy in that case, causing a stack overflow.
|
||||
"""
|
||||
class M(DebugHelperMeta):
|
||||
def mro(cls):
|
||||
if self.ready:
|
||||
if cls.__name__ == 'B1':
|
||||
B2.__bases__ = (B1,)
|
||||
if cls.__name__ == 'B2':
|
||||
B1.__bases__ = (B2,)
|
||||
return type.mro(cls)
|
||||
|
||||
class A(metaclass=M):
|
||||
pass
|
||||
class B1(A):
|
||||
pass
|
||||
class B2(A):
|
||||
pass
|
||||
|
||||
self.ready = True
|
||||
with self.assertRaises(TypeError):
|
||||
B1.__bases__ += ()
|
||||
|
||||
def test_tp_subclasses_cycle_in_update_slots(self):
|
||||
"""
|
||||
type_set_bases must check for reentrancy upon finishing its job
|
||||
by updating tp_subclasses of old/new bases of the type.
|
||||
Otherwise, an implicit inheritance cycle through tp_subclasses
|
||||
can break functions that recurse on elements of that field
|
||||
(like recurse_down_subclasses and mro_hierarchy) eventually
|
||||
leading to a stack overflow.
|
||||
"""
|
||||
class M(DebugHelperMeta):
|
||||
def mro(cls):
|
||||
if self.ready and cls.__name__ == 'C':
|
||||
self.ready = False
|
||||
C.__bases__ = (B2,)
|
||||
return type.mro(cls)
|
||||
|
||||
class A(metaclass=M):
|
||||
pass
|
||||
class B1(A):
|
||||
pass
|
||||
class B2(A):
|
||||
pass
|
||||
class C(A):
|
||||
pass
|
||||
|
||||
self.ready = True
|
||||
C.__bases__ = (B1,)
|
||||
B1.__bases__ = (C,)
|
||||
|
||||
self.assertEqual(C.__bases__, (B2,))
|
||||
self.assertEqual(B2.__subclasses__(), [C])
|
||||
self.assertEqual(B1.__subclasses__(), [])
|
||||
|
||||
self.assertEqual(B1.__bases__, (C,))
|
||||
self.assertEqual(C.__subclasses__(), [B1])
|
||||
|
||||
def test_tp_subclasses_cycle_error_return_path(self):
|
||||
"""
|
||||
The same as test_tp_subclasses_cycle_in_update_slots, but tests
|
||||
a code path executed on error (goto bail).
|
||||
"""
|
||||
class E(Exception):
|
||||
pass
|
||||
class M(DebugHelperMeta):
|
||||
def mro(cls):
|
||||
if self.ready and cls.__name__ == 'C':
|
||||
if C.__bases__ == (B2,):
|
||||
self.ready = False
|
||||
else:
|
||||
C.__bases__ = (B2,)
|
||||
raise E
|
||||
return type.mro(cls)
|
||||
|
||||
class A(metaclass=M):
|
||||
pass
|
||||
class B1(A):
|
||||
pass
|
||||
class B2(A):
|
||||
pass
|
||||
class C(A):
|
||||
pass
|
||||
|
||||
self.ready = True
|
||||
with self.assertRaises(E):
|
||||
C.__bases__ = (B1,)
|
||||
B1.__bases__ = (C,)
|
||||
|
||||
self.assertEqual(C.__bases__, (B2,))
|
||||
self.assertEqual(C.__mro__, tuple(type.mro(C)))
|
||||
|
||||
def test_incomplete_extend(self):
|
||||
"""
|
||||
Extending an unitialized type with type->tp_mro == NULL must
|
||||
throw a reasonable TypeError exception, instead of failing
|
||||
with PyErr_BadInternalCall.
|
||||
"""
|
||||
class M(DebugHelperMeta):
|
||||
def mro(cls):
|
||||
if cls.__mro__ is None and cls.__name__ != 'X':
|
||||
with self.assertRaises(TypeError):
|
||||
class X(cls):
|
||||
pass
|
||||
|
||||
return type.mro(cls)
|
||||
|
||||
class A(metaclass=M):
|
||||
pass
|
||||
|
||||
def test_incomplete_super(self):
|
||||
"""
|
||||
Attrubute lookup on a super object must be aware that
|
||||
its target type can be uninitialized (type->tp_mro == NULL).
|
||||
"""
|
||||
class M(DebugHelperMeta):
|
||||
def mro(cls):
|
||||
if cls.__mro__ is None:
|
||||
with self.assertRaises(AttributeError):
|
||||
super(cls, cls).xxx
|
||||
|
||||
return type.mro(cls)
|
||||
|
||||
class A(metaclass=M):
|
||||
pass
|
||||
|
||||
|
||||
def test_main():
|
||||
# Run all local test cases, with PTypesLongInitTest first.
|
||||
support.run_unittest(PTypesLongInitTest, OperatorsTest,
|
||||
ClassPropertiesAndMethods, DictProxyTests,
|
||||
MiscTests, PicklingTests, SharedKeyTests)
|
||||
MiscTests, PicklingTests, SharedKeyTests,
|
||||
MroTest)
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_main()
|
||||
|
|
|
@ -16,6 +16,7 @@ Michael Abbott
|
|||
Rajiv Abraham
|
||||
David Abrahams
|
||||
Marc Abramowitz
|
||||
Eldar Abusalimov
|
||||
Ron Adam
|
||||
Anton Afanasyev
|
||||
Ali Afshar
|
||||
|
|
|
@ -11,6 +11,9 @@ Release date: TBA
|
|||
Core and Builtins
|
||||
-----------------
|
||||
|
||||
- Issue #22735: Fix many edge cases (including crashes) involving custom mro()
|
||||
implementations.
|
||||
|
||||
- Issue #22896: Avoid using PyObject_AsCharBuffer(), PyObject_AsReadBuffer()
|
||||
and PyObject_AsWriteBuffer().
|
||||
|
||||
|
|
|
@ -539,9 +539,11 @@ type_get_bases(PyTypeObject *type, void *context)
|
|||
}
|
||||
|
||||
static PyTypeObject *best_base(PyObject *);
|
||||
static int mro_internal(PyTypeObject *);
|
||||
static int mro_internal(PyTypeObject *, PyObject **);
|
||||
static int type_is_subtype_base_chain(PyTypeObject *, PyTypeObject *);
|
||||
static int compatible_for_assignment(PyTypeObject *, PyTypeObject *, char *);
|
||||
static int add_subclass(PyTypeObject*, PyTypeObject*);
|
||||
static int add_all_subclasses(PyTypeObject *type, PyObject *bases);
|
||||
static void remove_subclass(PyTypeObject *, PyTypeObject *);
|
||||
static void remove_all_subclasses(PyTypeObject *type, PyObject *bases);
|
||||
static void update_all_slots(PyTypeObject *);
|
||||
|
@ -551,167 +553,194 @@ static int update_subclasses(PyTypeObject *type, PyObject *name,
|
|||
update_callback callback, void *data);
|
||||
static int recurse_down_subclasses(PyTypeObject *type, PyObject *name,
|
||||
update_callback callback, void *data);
|
||||
static PyObject *type_subclasses(PyTypeObject *type, PyObject *ignored);
|
||||
|
||||
static int
|
||||
mro_subclasses(PyTypeObject *type, PyObject* temp)
|
||||
mro_hierarchy(PyTypeObject *type, PyObject *temp)
|
||||
{
|
||||
PyTypeObject *subclass;
|
||||
PyObject *ref, *subclasses, *old_mro;
|
||||
Py_ssize_t i;
|
||||
int res;
|
||||
PyObject *new_mro, *old_mro;
|
||||
PyObject *tuple;
|
||||
PyObject *subclasses;
|
||||
Py_ssize_t i, n;
|
||||
|
||||
subclasses = type->tp_subclasses;
|
||||
if (subclasses == NULL)
|
||||
return 0;
|
||||
assert(PyDict_CheckExact(subclasses));
|
||||
i = 0;
|
||||
res = mro_internal(type, &old_mro);
|
||||
if (res <= 0)
|
||||
/* error / reentrance */
|
||||
return res;
|
||||
new_mro = type->tp_mro;
|
||||
|
||||
while (PyDict_Next(subclasses, &i, NULL, &ref)) {
|
||||
assert(PyWeakref_CheckRef(ref));
|
||||
subclass = (PyTypeObject *)PyWeakref_GET_OBJECT(ref);
|
||||
assert(subclass != NULL);
|
||||
if ((PyObject *)subclass == Py_None)
|
||||
continue;
|
||||
assert(PyType_Check(subclass));
|
||||
old_mro = subclass->tp_mro;
|
||||
if (mro_internal(subclass) < 0) {
|
||||
subclass->tp_mro = old_mro;
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
PyObject* tuple;
|
||||
tuple = PyTuple_Pack(2, subclass, old_mro);
|
||||
Py_DECREF(old_mro);
|
||||
if (!tuple)
|
||||
return -1;
|
||||
if (PyList_Append(temp, tuple) < 0)
|
||||
return -1;
|
||||
Py_DECREF(tuple);
|
||||
}
|
||||
if (mro_subclasses(subclass, temp) < 0)
|
||||
return -1;
|
||||
if (old_mro != NULL)
|
||||
tuple = PyTuple_Pack(3, type, new_mro, old_mro);
|
||||
else
|
||||
tuple = PyTuple_Pack(2, type, new_mro);
|
||||
|
||||
if (tuple != NULL)
|
||||
res = PyList_Append(temp, tuple);
|
||||
else
|
||||
res = -1;
|
||||
Py_XDECREF(tuple);
|
||||
|
||||
if (res < 0) {
|
||||
type->tp_mro = old_mro;
|
||||
Py_DECREF(new_mro);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
Py_XDECREF(old_mro);
|
||||
|
||||
/* Obtain a copy of subclasses list to iterate over.
|
||||
|
||||
Otherwise type->tp_subclasses might be altered
|
||||
in the middle of the loop, for example, through a custom mro(),
|
||||
by invoking type_set_bases on some subclass of the type
|
||||
which in turn calls remove_subclass/add_subclass on this type.
|
||||
|
||||
Finally, this makes things simple avoiding the need to deal
|
||||
with dictionary iterators and weak references.
|
||||
*/
|
||||
subclasses = type_subclasses(type, NULL);
|
||||
if (subclasses == NULL)
|
||||
return -1;
|
||||
n = PyList_GET_SIZE(subclasses);
|
||||
for (i = 0; i < n; i++) {
|
||||
PyTypeObject *subclass;
|
||||
subclass = (PyTypeObject *)PyList_GET_ITEM(subclasses, i);
|
||||
res = mro_hierarchy(subclass, temp);
|
||||
if (res < 0)
|
||||
break;
|
||||
}
|
||||
Py_DECREF(subclasses);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int
|
||||
type_set_bases(PyTypeObject *type, PyObject *value, void *context)
|
||||
type_set_bases(PyTypeObject *type, PyObject *new_bases, void *context)
|
||||
{
|
||||
Py_ssize_t i;
|
||||
int r = 0;
|
||||
PyObject *ob, *temp;
|
||||
int res = 0;
|
||||
PyObject *temp;
|
||||
PyObject *old_bases;
|
||||
PyTypeObject *new_base, *old_base;
|
||||
PyObject *old_bases, *old_mro;
|
||||
Py_ssize_t i;
|
||||
|
||||
if (!check_set_special_type_attr(type, value, "__bases__"))
|
||||
if (!check_set_special_type_attr(type, new_bases, "__bases__"))
|
||||
return -1;
|
||||
if (!PyTuple_Check(value)) {
|
||||
if (!PyTuple_Check(new_bases)) {
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"can only assign tuple to %s.__bases__, not %s",
|
||||
type->tp_name, Py_TYPE(value)->tp_name);
|
||||
type->tp_name, Py_TYPE(new_bases)->tp_name);
|
||||
return -1;
|
||||
}
|
||||
if (PyTuple_GET_SIZE(value) == 0) {
|
||||
if (PyTuple_GET_SIZE(new_bases) == 0) {
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"can only assign non-empty tuple to %s.__bases__, not ()",
|
||||
type->tp_name);
|
||||
return -1;
|
||||
}
|
||||
for (i = 0; i < PyTuple_GET_SIZE(value); i++) {
|
||||
ob = PyTuple_GET_ITEM(value, i);
|
||||
for (i = 0; i < PyTuple_GET_SIZE(new_bases); i++) {
|
||||
PyObject *ob;
|
||||
PyTypeObject *base;
|
||||
|
||||
ob = PyTuple_GET_ITEM(new_bases, i);
|
||||
if (!PyType_Check(ob)) {
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"%s.__bases__ must be tuple of classes, not '%s'",
|
||||
type->tp_name, Py_TYPE(ob)->tp_name);
|
||||
return -1;
|
||||
}
|
||||
if (PyType_IsSubtype((PyTypeObject*)ob, type)) {
|
||||
|
||||
base = (PyTypeObject*)ob;
|
||||
if (PyType_IsSubtype(base, type) ||
|
||||
/* In case of reentering here again through a custom mro()
|
||||
the above check is not enough since it relies on
|
||||
base->tp_mro which would gonna be updated inside
|
||||
mro_internal only upon returning from the mro().
|
||||
|
||||
However, base->tp_base has already been assigned (see
|
||||
below), which in turn may cause an inheritance cycle
|
||||
through tp_base chain. And this is definitely
|
||||
not what you want to ever happen. */
|
||||
(base->tp_mro != NULL && type_is_subtype_base_chain(base, type))) {
|
||||
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"a __bases__ item causes an inheritance cycle");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
new_base = best_base(value);
|
||||
|
||||
if (!new_base)
|
||||
new_base = best_base(new_bases);
|
||||
if (new_base == NULL)
|
||||
return -1;
|
||||
|
||||
if (!compatible_for_assignment(type->tp_base, new_base, "__bases__"))
|
||||
return -1;
|
||||
|
||||
Py_INCREF(new_bases);
|
||||
Py_INCREF(new_base);
|
||||
Py_INCREF(value);
|
||||
|
||||
old_bases = type->tp_bases;
|
||||
old_base = type->tp_base;
|
||||
old_mro = type->tp_mro;
|
||||
|
||||
type->tp_bases = value;
|
||||
type->tp_bases = new_bases;
|
||||
type->tp_base = new_base;
|
||||
|
||||
if (mro_internal(type) < 0) {
|
||||
goto bail;
|
||||
}
|
||||
|
||||
temp = PyList_New(0);
|
||||
if (!temp)
|
||||
if (temp == NULL)
|
||||
goto bail;
|
||||
|
||||
r = mro_subclasses(type, temp);
|
||||
|
||||
if (r < 0) {
|
||||
for (i = 0; i < PyList_Size(temp); i++) {
|
||||
PyTypeObject* cls;
|
||||
PyObject* mro;
|
||||
PyArg_UnpackTuple(PyList_GET_ITEM(temp, i),
|
||||
"", 2, 2, &cls, &mro);
|
||||
Py_INCREF(mro);
|
||||
ob = cls->tp_mro;
|
||||
cls->tp_mro = mro;
|
||||
Py_DECREF(ob);
|
||||
}
|
||||
Py_DECREF(temp);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
if (mro_hierarchy(type, temp) < 0)
|
||||
goto undo;
|
||||
Py_DECREF(temp);
|
||||
|
||||
/* any base that was in __bases__ but now isn't, we
|
||||
need to remove |type| from its tp_subclasses.
|
||||
conversely, any class now in __bases__ that wasn't
|
||||
needs to have |type| added to its subclasses. */
|
||||
/* Take no action in case if type->tp_bases has been replaced
|
||||
through reentrance. */
|
||||
if (type->tp_bases == new_bases) {
|
||||
/* any base that was in __bases__ but now isn't, we
|
||||
need to remove |type| from its tp_subclasses.
|
||||
conversely, any class now in __bases__ that wasn't
|
||||
needs to have |type| added to its subclasses. */
|
||||
|
||||
/* for now, sod that: just remove from all old_bases,
|
||||
add to all new_bases */
|
||||
|
||||
remove_all_subclasses(type, old_bases);
|
||||
|
||||
for (i = PyTuple_GET_SIZE(value) - 1; i >= 0; i--) {
|
||||
ob = PyTuple_GET_ITEM(value, i);
|
||||
if (PyType_Check(ob)) {
|
||||
if (add_subclass((PyTypeObject*)ob, type) < 0)
|
||||
r = -1;
|
||||
}
|
||||
/* for now, sod that: just remove from all old_bases,
|
||||
add to all new_bases */
|
||||
remove_all_subclasses(type, old_bases);
|
||||
res = add_all_subclasses(type, new_bases);
|
||||
update_all_slots(type);
|
||||
}
|
||||
|
||||
update_all_slots(type);
|
||||
|
||||
Py_DECREF(old_bases);
|
||||
Py_DECREF(old_base);
|
||||
Py_DECREF(old_mro);
|
||||
|
||||
return r;
|
||||
return res;
|
||||
|
||||
undo:
|
||||
for (i = PyList_GET_SIZE(temp) - 1; i >= 0; i--) {
|
||||
PyTypeObject *cls;
|
||||
PyObject *new_mro, *old_mro = NULL;
|
||||
|
||||
PyArg_UnpackTuple(PyList_GET_ITEM(temp, i),
|
||||
"", 2, 3, &cls, &new_mro, &old_mro);
|
||||
/* Do not rollback if cls has a newer version of MRO. */
|
||||
if (cls->tp_mro == new_mro) {
|
||||
Py_XINCREF(old_mro);
|
||||
cls->tp_mro = old_mro;
|
||||
Py_DECREF(new_mro);
|
||||
}
|
||||
}
|
||||
Py_DECREF(temp);
|
||||
|
||||
bail:
|
||||
Py_DECREF(type->tp_bases);
|
||||
Py_DECREF(type->tp_base);
|
||||
if (type->tp_mro != old_mro) {
|
||||
Py_DECREF(type->tp_mro);
|
||||
}
|
||||
if (type->tp_bases == new_bases) {
|
||||
assert(type->tp_base == new_base);
|
||||
|
||||
type->tp_bases = old_bases;
|
||||
type->tp_base = old_base;
|
||||
type->tp_mro = old_mro;
|
||||
type->tp_bases = old_bases;
|
||||
type->tp_base = old_base;
|
||||
|
||||
Py_DECREF(new_bases);
|
||||
Py_DECREF(new_base);
|
||||
}
|
||||
else {
|
||||
Py_DECREF(old_bases);
|
||||
Py_DECREF(old_base);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
@ -1284,6 +1313,18 @@ static PyTypeObject *solid_base(PyTypeObject *type);
|
|||
|
||||
/* type test with subclassing support */
|
||||
|
||||
Py_LOCAL_INLINE(int)
|
||||
type_is_subtype_base_chain(PyTypeObject *a, PyTypeObject *b)
|
||||
{
|
||||
do {
|
||||
if (a == b)
|
||||
return 1;
|
||||
a = a->tp_base;
|
||||
} while (a != NULL);
|
||||
|
||||
return (b == &PyBaseObject_Type);
|
||||
}
|
||||
|
||||
int
|
||||
PyType_IsSubtype(PyTypeObject *a, PyTypeObject *b)
|
||||
{
|
||||
|
@ -1302,15 +1343,9 @@ PyType_IsSubtype(PyTypeObject *a, PyTypeObject *b)
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
else
|
||||
/* a is not completely initilized yet; follow tp_base */
|
||||
do {
|
||||
if (a == b)
|
||||
return 1;
|
||||
a = a->tp_base;
|
||||
} while (a != NULL);
|
||||
return b == &PyBaseObject_Type;
|
||||
}
|
||||
return type_is_subtype_base_chain(a, b);
|
||||
}
|
||||
|
||||
/* Internal routines to do a method lookup in the type
|
||||
|
@ -1577,10 +1612,11 @@ consistent method resolution\norder (MRO) for bases");
|
|||
}
|
||||
|
||||
static int
|
||||
pmerge(PyObject *acc, PyObject* to_merge) {
|
||||
pmerge(PyObject *acc, PyObject* to_merge)
|
||||
{
|
||||
int res = 0;
|
||||
Py_ssize_t i, j, to_merge_size, empty_cnt;
|
||||
int *remain;
|
||||
int ok;
|
||||
|
||||
to_merge_size = PyList_GET_SIZE(to_merge);
|
||||
|
||||
|
@ -1618,15 +1654,13 @@ pmerge(PyObject *acc, PyObject* to_merge) {
|
|||
candidate = PyList_GET_ITEM(cur_list, remain[i]);
|
||||
for (j = 0; j < to_merge_size; j++) {
|
||||
PyObject *j_lst = PyList_GET_ITEM(to_merge, j);
|
||||
if (tail_contains(j_lst, remain[j], candidate)) {
|
||||
if (tail_contains(j_lst, remain[j], candidate))
|
||||
goto skip; /* continue outer loop */
|
||||
}
|
||||
}
|
||||
ok = PyList_Append(acc, candidate);
|
||||
if (ok < 0) {
|
||||
PyMem_FREE(remain);
|
||||
return -1;
|
||||
}
|
||||
res = PyList_Append(acc, candidate);
|
||||
if (res < 0)
|
||||
goto out;
|
||||
|
||||
for (j = 0; j < to_merge_size; j++) {
|
||||
PyObject *j_lst = PyList_GET_ITEM(to_merge, j);
|
||||
if (remain[j] < PyList_GET_SIZE(j_lst) &&
|
||||
|
@ -1638,22 +1672,25 @@ pmerge(PyObject *acc, PyObject* to_merge) {
|
|||
skip: ;
|
||||
}
|
||||
|
||||
if (empty_cnt == to_merge_size) {
|
||||
PyMem_FREE(remain);
|
||||
return 0;
|
||||
if (empty_cnt != to_merge_size) {
|
||||
set_mro_error(to_merge, remain);
|
||||
res = -1;
|
||||
}
|
||||
set_mro_error(to_merge, remain);
|
||||
|
||||
out:
|
||||
PyMem_FREE(remain);
|
||||
return -1;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
mro_implementation(PyTypeObject *type)
|
||||
{
|
||||
Py_ssize_t i, n;
|
||||
int ok;
|
||||
PyObject *bases, *result;
|
||||
PyObject *result = NULL;
|
||||
PyObject *bases;
|
||||
PyObject *to_merge, *bases_aslist;
|
||||
int res;
|
||||
Py_ssize_t i, n;
|
||||
|
||||
if (type->tp_dict == NULL) {
|
||||
if (PyType_Ready(type) < 0)
|
||||
|
@ -1677,42 +1714,44 @@ mro_implementation(PyTypeObject *type)
|
|||
return NULL;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
PyObject *base = PyTuple_GET_ITEM(bases, i);
|
||||
PyObject *parentMRO;
|
||||
parentMRO = PySequence_List(((PyTypeObject*)base)->tp_mro);
|
||||
if (parentMRO == NULL) {
|
||||
Py_DECREF(to_merge);
|
||||
return NULL;
|
||||
PyTypeObject *base;
|
||||
PyObject *base_mro_aslist;
|
||||
|
||||
base = (PyTypeObject *)PyTuple_GET_ITEM(bases, i);
|
||||
if (base->tp_mro == NULL) {
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"Cannot extend an incomplete type '%.100s'",
|
||||
base->tp_name);
|
||||
goto out;
|
||||
}
|
||||
|
||||
PyList_SET_ITEM(to_merge, i, parentMRO);
|
||||
base_mro_aslist = PySequence_List(base->tp_mro);
|
||||
if (base_mro_aslist == NULL)
|
||||
goto out;
|
||||
|
||||
PyList_SET_ITEM(to_merge, i, base_mro_aslist);
|
||||
}
|
||||
|
||||
bases_aslist = PySequence_List(bases);
|
||||
if (bases_aslist == NULL) {
|
||||
Py_DECREF(to_merge);
|
||||
return NULL;
|
||||
}
|
||||
if (bases_aslist == NULL)
|
||||
goto out;
|
||||
/* This is just a basic sanity check. */
|
||||
if (check_duplicates(bases_aslist) < 0) {
|
||||
Py_DECREF(to_merge);
|
||||
Py_DECREF(bases_aslist);
|
||||
return NULL;
|
||||
goto out;
|
||||
}
|
||||
PyList_SET_ITEM(to_merge, n, bases_aslist);
|
||||
|
||||
result = Py_BuildValue("[O]", (PyObject *)type);
|
||||
if (result == NULL) {
|
||||
Py_DECREF(to_merge);
|
||||
return NULL;
|
||||
}
|
||||
if (result == NULL)
|
||||
goto out;
|
||||
|
||||
ok = pmerge(result, to_merge);
|
||||
res = pmerge(result, to_merge);
|
||||
if (res < 0)
|
||||
Py_CLEAR(result);
|
||||
|
||||
out:
|
||||
Py_DECREF(to_merge);
|
||||
if (ok < 0) {
|
||||
Py_DECREF(result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -1726,59 +1765,133 @@ mro_external(PyObject *self)
|
|||
}
|
||||
|
||||
static int
|
||||
mro_internal(PyTypeObject *type)
|
||||
mro_check(PyTypeObject *type, PyObject *mro)
|
||||
{
|
||||
PyObject *mro, *result, *tuple;
|
||||
int checkit = 0;
|
||||
PyTypeObject *solid;
|
||||
Py_ssize_t i, n;
|
||||
|
||||
if (Py_TYPE(type) == &PyType_Type) {
|
||||
result = mro_implementation(type);
|
||||
}
|
||||
else {
|
||||
_Py_IDENTIFIER(mro);
|
||||
checkit = 1;
|
||||
mro = lookup_method((PyObject *)type, &PyId_mro);
|
||||
if (mro == NULL)
|
||||
solid = solid_base(type);
|
||||
|
||||
n = PyTuple_GET_SIZE(mro);
|
||||
for (i = 0; i < n; i++) {
|
||||
PyTypeObject *base;
|
||||
PyObject *tmp;
|
||||
|
||||
tmp = PyTuple_GET_ITEM(mro, i);
|
||||
if (!PyType_Check(tmp)) {
|
||||
PyErr_Format(
|
||||
PyExc_TypeError,
|
||||
"mro() returned a non-class ('%.500s')",
|
||||
Py_TYPE(tmp)->tp_name);
|
||||
return -1;
|
||||
result = PyObject_CallObject(mro, NULL);
|
||||
Py_DECREF(mro);
|
||||
}
|
||||
if (result == NULL)
|
||||
return -1;
|
||||
tuple = PySequence_Tuple(result);
|
||||
Py_DECREF(result);
|
||||
if (tuple == NULL)
|
||||
return -1;
|
||||
if (checkit) {
|
||||
Py_ssize_t i, len;
|
||||
PyObject *cls;
|
||||
PyTypeObject *solid;
|
||||
}
|
||||
|
||||
solid = solid_base(type);
|
||||
|
||||
len = PyTuple_GET_SIZE(tuple);
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
PyTypeObject *t;
|
||||
cls = PyTuple_GET_ITEM(tuple, i);
|
||||
if (!PyType_Check(cls)) {
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"mro() returned a non-class ('%.500s')",
|
||||
Py_TYPE(cls)->tp_name);
|
||||
Py_DECREF(tuple);
|
||||
return -1;
|
||||
}
|
||||
t = (PyTypeObject*)cls;
|
||||
if (!PyType_IsSubtype(solid, solid_base(t))) {
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"mro() returned base with unsuitable layout ('%.500s')",
|
||||
t->tp_name);
|
||||
Py_DECREF(tuple);
|
||||
return -1;
|
||||
}
|
||||
base = (PyTypeObject*)tmp;
|
||||
if (!PyType_IsSubtype(solid, solid_base(base))) {
|
||||
PyErr_Format(
|
||||
PyExc_TypeError,
|
||||
"mro() returned base with unsuitable layout ('%.500s')",
|
||||
base->tp_name);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
type->tp_mro = tuple;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Lookups an mcls.mro method, invokes it and checks the result (if needed,
|
||||
in case of a custom mro() implementation).
|
||||
|
||||
Keep in mind that during execution of this function type->tp_mro
|
||||
can be replaced due to possible reentrance (for example,
|
||||
through type_set_bases):
|
||||
|
||||
- when looking up the mcls.mro attribute (it could be
|
||||
a user-provided descriptor);
|
||||
|
||||
- from inside a custom mro() itself;
|
||||
|
||||
- through a finalizer of the return value of mro().
|
||||
*/
|
||||
static PyObject *
|
||||
mro_invoke(PyTypeObject *type)
|
||||
{
|
||||
PyObject *mro_result;
|
||||
PyObject *new_mro;
|
||||
int custom = (Py_TYPE(type) != &PyType_Type);
|
||||
|
||||
if (custom) {
|
||||
_Py_IDENTIFIER(mro);
|
||||
PyObject *mro_meth = lookup_method((PyObject *)type, &PyId_mro);
|
||||
if (mro_meth == NULL)
|
||||
return NULL;
|
||||
mro_result = PyObject_CallObject(mro_meth, NULL);
|
||||
Py_DECREF(mro_meth);
|
||||
}
|
||||
else {
|
||||
mro_result = mro_implementation(type);
|
||||
}
|
||||
if (mro_result == NULL)
|
||||
return NULL;
|
||||
|
||||
new_mro = PySequence_Tuple(mro_result);
|
||||
Py_DECREF(mro_result);
|
||||
if (new_mro == NULL)
|
||||
return NULL;
|
||||
|
||||
if (custom && mro_check(type, new_mro) < 0) {
|
||||
Py_DECREF(new_mro);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return new_mro;
|
||||
}
|
||||
|
||||
/* Calculates and assigns a new MRO to type->tp_mro.
|
||||
Return values and invariants:
|
||||
|
||||
- Returns 1 if a new MRO value has been set to type->tp_mro due to
|
||||
this call of mro_internal (no tricky reentrancy and no errors).
|
||||
|
||||
In case if p_old_mro argument is not NULL, a previous value
|
||||
of type->tp_mro is put there, and the ownership of this
|
||||
reference is transferred to a caller.
|
||||
Otherwise, the previous value (if any) is decref'ed.
|
||||
|
||||
- Returns 0 in case when type->tp_mro gets changed because of
|
||||
reentering here through a custom mro() (see a comment to mro_invoke).
|
||||
|
||||
In this case, a refcount of an old type->tp_mro is adjusted
|
||||
somewhere deeper in the call stack (by the innermost mro_internal
|
||||
or its caller) and may become zero upon returning from here.
|
||||
This also implies that the whole hierarchy of subclasses of the type
|
||||
has seen the new value and updated their MRO accordingly.
|
||||
|
||||
- Returns -1 in case of an error.
|
||||
*/
|
||||
static int
|
||||
mro_internal(PyTypeObject *type, PyObject **p_old_mro)
|
||||
{
|
||||
PyObject *new_mro, *old_mro;
|
||||
int reent;
|
||||
|
||||
/* Keep a reference to be able to do a reentrancy check below.
|
||||
Don't let old_mro be GC'ed and its address be reused for
|
||||
another object, like (suddenly!) a new tp_mro. */
|
||||
old_mro = type->tp_mro;
|
||||
Py_XINCREF(old_mro);
|
||||
new_mro = mro_invoke(type); /* might cause reentrance */
|
||||
reent = (type->tp_mro != old_mro);
|
||||
Py_XDECREF(old_mro);
|
||||
if (new_mro == NULL)
|
||||
return -1;
|
||||
|
||||
if (reent) {
|
||||
Py_DECREF(new_mro);
|
||||
return 0;
|
||||
}
|
||||
|
||||
type->tp_mro = new_mro;
|
||||
|
||||
type_mro_modified(type, type->tp_mro);
|
||||
/* corner case: the super class might have been hidden
|
||||
|
@ -1787,7 +1900,12 @@ mro_internal(PyTypeObject *type)
|
|||
|
||||
PyType_Modified(type);
|
||||
|
||||
return 0;
|
||||
if (p_old_mro != NULL)
|
||||
*p_old_mro = old_mro; /* transfer the ownership */
|
||||
else
|
||||
Py_XDECREF(old_mro);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
@ -4661,9 +4779,8 @@ PyType_Ready(PyTypeObject *type)
|
|||
}
|
||||
|
||||
/* Calculate method resolution order */
|
||||
if (mro_internal(type) < 0) {
|
||||
if (mro_internal(type, NULL) < 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Inherit special flags from dominant base */
|
||||
if (type->tp_base != NULL)
|
||||
|
@ -4812,6 +4929,24 @@ add_subclass(PyTypeObject *base, PyTypeObject *type)
|
|||
return result;
|
||||
}
|
||||
|
||||
static int
|
||||
add_all_subclasses(PyTypeObject *type, PyObject *bases)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
if (bases) {
|
||||
Py_ssize_t i;
|
||||
for (i = 0; i < PyTuple_GET_SIZE(bases); i++) {
|
||||
PyObject *base = PyTuple_GET_ITEM(bases, i);
|
||||
if (PyType_Check(base) &&
|
||||
add_subclass((PyTypeObject*)base, type) < 0)
|
||||
res = -1;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void
|
||||
remove_subclass(PyTypeObject *base, PyTypeObject *type)
|
||||
{
|
||||
|
@ -6765,70 +6900,74 @@ static PyObject *
|
|||
super_getattro(PyObject *self, PyObject *name)
|
||||
{
|
||||
superobject *su = (superobject *)self;
|
||||
int skip = su->obj_type == NULL;
|
||||
PyTypeObject *starttype;
|
||||
PyObject *mro;
|
||||
Py_ssize_t i, n;
|
||||
|
||||
if (!skip) {
|
||||
/* We want __class__ to return the class of the super object
|
||||
(i.e. super, or a subclass), not the class of su->obj. */
|
||||
skip = (PyUnicode_Check(name) &&
|
||||
PyUnicode_GET_LENGTH(name) == 9 &&
|
||||
_PyUnicode_CompareWithId(name, &PyId___class__) == 0);
|
||||
starttype = su->obj_type;
|
||||
if (starttype == NULL)
|
||||
goto skip;
|
||||
|
||||
/* We want __class__ to return the class of the super object
|
||||
(i.e. super, or a subclass), not the class of su->obj. */
|
||||
if (PyUnicode_Check(name) &&
|
||||
PyUnicode_GET_LENGTH(name) == 9 &&
|
||||
_PyUnicode_CompareWithId(name, &PyId___class__) == 0)
|
||||
goto skip;
|
||||
|
||||
mro = starttype->tp_mro;
|
||||
if (mro == NULL)
|
||||
goto skip;
|
||||
|
||||
assert(PyTuple_Check(mro));
|
||||
n = PyTuple_GET_SIZE(mro);
|
||||
|
||||
/* No need to check the last one: it's gonna be skipped anyway. */
|
||||
for (i = 0; i+1 < n; i++) {
|
||||
if ((PyObject *)(su->type) == PyTuple_GET_ITEM(mro, i))
|
||||
break;
|
||||
}
|
||||
i++; /* skip su->type (if any) */
|
||||
if (i >= n)
|
||||
goto skip;
|
||||
|
||||
if (!skip) {
|
||||
PyObject *mro, *res, *tmp, *dict;
|
||||
PyTypeObject *starttype;
|
||||
/* keep a strong reference to mro because starttype->tp_mro can be
|
||||
replaced during PyDict_GetItem(dict, name) */
|
||||
Py_INCREF(mro);
|
||||
do {
|
||||
PyObject *res, *tmp, *dict;
|
||||
descrgetfunc f;
|
||||
Py_ssize_t i, n;
|
||||
|
||||
starttype = su->obj_type;
|
||||
mro = starttype->tp_mro;
|
||||
tmp = PyTuple_GET_ITEM(mro, i);
|
||||
assert(PyType_Check(tmp));
|
||||
|
||||
if (mro == NULL)
|
||||
n = 0;
|
||||
else {
|
||||
assert(PyTuple_Check(mro));
|
||||
n = PyTuple_GET_SIZE(mro);
|
||||
}
|
||||
for (i = 0; i < n; i++) {
|
||||
if ((PyObject *)(su->type) == PyTuple_GET_ITEM(mro, i))
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
res = NULL;
|
||||
/* keep a strong reference to mro because starttype->tp_mro can be
|
||||
replaced during PyDict_GetItem(dict, name) */
|
||||
Py_INCREF(mro);
|
||||
for (; i < n; i++) {
|
||||
tmp = PyTuple_GET_ITEM(mro, i);
|
||||
if (PyType_Check(tmp))
|
||||
dict = ((PyTypeObject *)tmp)->tp_dict;
|
||||
else
|
||||
continue;
|
||||
res = PyDict_GetItem(dict, name);
|
||||
if (res != NULL) {
|
||||
Py_INCREF(res);
|
||||
f = Py_TYPE(res)->tp_descr_get;
|
||||
if (f != NULL) {
|
||||
tmp = f(res,
|
||||
/* Only pass 'obj' param if
|
||||
this is instance-mode super
|
||||
(See SF ID #743627)
|
||||
*/
|
||||
(su->obj == (PyObject *)
|
||||
su->obj_type
|
||||
? (PyObject *)NULL
|
||||
: su->obj),
|
||||
(PyObject *)starttype);
|
||||
Py_DECREF(res);
|
||||
res = tmp;
|
||||
}
|
||||
Py_DECREF(mro);
|
||||
return res;
|
||||
dict = ((PyTypeObject *)tmp)->tp_dict;
|
||||
assert(dict != NULL && PyDict_Check(dict));
|
||||
|
||||
res = PyDict_GetItem(dict, name);
|
||||
if (res != NULL) {
|
||||
Py_INCREF(res);
|
||||
|
||||
f = Py_TYPE(res)->tp_descr_get;
|
||||
if (f != NULL) {
|
||||
tmp = f(res,
|
||||
/* Only pass 'obj' param if this is instance-mode super
|
||||
(See SF ID #743627) */
|
||||
(su->obj == (PyObject *)starttype) ? NULL : su->obj,
|
||||
(PyObject *)starttype);
|
||||
Py_DECREF(res);
|
||||
res = tmp;
|
||||
}
|
||||
|
||||
Py_DECREF(mro);
|
||||
return res;
|
||||
}
|
||||
Py_DECREF(mro);
|
||||
}
|
||||
|
||||
i++;
|
||||
} while (i < n);
|
||||
Py_DECREF(mro);
|
||||
|
||||
skip:
|
||||
return PyObject_GenericGetAttr(self, name);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue