bpo-41295: Reimplement the Carlo Verre "hackcheck" (GH-21528)

Walk down the MRO backwards to find the type that originally defined the final `tp_setattro`, then make sure we are not jumping over intermediate C-level bases with the Python-level call.

Automerge-Triggered-By: @gvanrossum
(cherry picked from commit c53b310e59)

Co-authored-by: scoder <stefan_ml@behnel.de>
This commit is contained in:
Miss Islington (bot) 2020-07-18 14:37:43 -07:00 committed by GitHub
parent 668d321476
commit 38d930f2cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 60 additions and 8 deletions

View File

@ -4315,6 +4315,42 @@ order (MRO) for bases """
else:
self.fail("Carlo Verre __delattr__ succeeded!")
def test_carloverre_multi_inherit_valid(self):
class A(type):
def __setattr__(cls, key, value):
type.__setattr__(cls, key, value)
class B:
pass
class C(B, A):
pass
obj = C('D', (object,), {})
try:
obj.test = True
except TypeError:
self.fail("setattr through direct base types should be legal")
def test_carloverre_multi_inherit_invalid(self):
class A(type):
def __setattr__(cls, key, value):
object.__setattr__(cls, key, value) # this should fail!
class B:
pass
class C(B, A):
pass
obj = C('D', (object,), {})
try:
obj.test = True
except TypeError:
pass
else:
self.fail("setattr through indirect base types should be rejected")
def test_weakref_segfault(self):
# Testing weakref segfault...
# SF 742911

View File

@ -0,0 +1,3 @@
Resolve a regression in CPython 3.8.4 where defining "__setattr__" in a
multi-inheritance setup and calling up the hierarchy chain could fail
if builtins/extension types were involved in the base types.

View File

@ -5822,14 +5822,29 @@ hackcheck(PyObject *self, setattrofunc func, const char *what)
return 1;
}
assert(PyTuple_Check(mro));
Py_ssize_t i, n;
n = PyTuple_GET_SIZE(mro);
for (i = 0; i < n; i++) {
/* Find the (base) type that defined the type's slot function. */
PyTypeObject *defining_type = type;
Py_ssize_t i;
for (i = PyTuple_GET_SIZE(mro) - 1; i >= 0; i--) {
PyTypeObject *base = (PyTypeObject*) PyTuple_GET_ITEM(mro, i);
if (base->tp_setattro == func) {
/* 'func' is the earliest non-Python implementation in the MRO. */
if (base->tp_setattro == slot_tp_setattro) {
/* Ignore Python classes:
they never define their own C-level setattro. */
}
else if (base->tp_setattro == type->tp_setattro) {
defining_type = base;
break;
} else if (base->tp_setattro != slot_tp_setattro) {
}
}
/* Reject calls that jump over intermediate C-level overrides. */
for (PyTypeObject *base = defining_type; base; base = base->tp_base) {
if (base->tp_setattro == func) {
/* 'func' is the right slot function to call. */
break;
}
else if (base->tp_setattro != slot_tp_setattro) {
/* 'base' is not a Python class and overrides 'func'.
Its tp_setattro should be called instead. */
PyErr_Format(PyExc_TypeError,
@ -5839,8 +5854,6 @@ hackcheck(PyObject *self, setattrofunc func, const char *what)
return 0;
}
}
/* Either 'func' is not in the mro (which should fail when checking 'self'),
or it's the right slot function to call. */
return 1;
}