Skip to content

Commit 01ab963

Browse files
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 c53b310) Co-authored-by: scoder <[email protected]>
1 parent 27b8110 commit 01ab963

File tree

3 files changed

+59
-7
lines changed

3 files changed

+59
-7
lines changed

Lib/test/test_descr.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4315,6 +4315,42 @@ def test_carloverre(self):
43154315
else:
43164316
self.fail("Carlo Verre __delattr__ succeeded!")
43174317

4318+
def test_carloverre_multi_inherit_valid(self):
4319+
class A(type):
4320+
def __setattr__(cls, key, value):
4321+
type.__setattr__(cls, key, value)
4322+
4323+
class B:
4324+
pass
4325+
4326+
class C(B, A):
4327+
pass
4328+
4329+
obj = C('D', (object,), {})
4330+
try:
4331+
obj.test = True
4332+
except TypeError:
4333+
self.fail("setattr through direct base types should be legal")
4334+
4335+
def test_carloverre_multi_inherit_invalid(self):
4336+
class A(type):
4337+
def __setattr__(cls, key, value):
4338+
object.__setattr__(cls, key, value) # this should fail!
4339+
4340+
class B:
4341+
pass
4342+
4343+
class C(B, A):
4344+
pass
4345+
4346+
obj = C('D', (object,), {})
4347+
try:
4348+
obj.test = True
4349+
except TypeError:
4350+
pass
4351+
else:
4352+
self.fail("setattr through indirect base types should be rejected")
4353+
43184354
def test_weakref_segfault(self):
43194355
# Testing weakref segfault...
43204356
# SF 742911
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Resolve a regression in CPython 3.8.4 where defining "__setattr__" in a
2+
multi-inheritance setup and calling up the hierarchy chain could fail
3+
if builtins/extension types were involved in the base types.

Objects/typeobject.c

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5955,14 +5955,29 @@ hackcheck(PyObject *self, setattrofunc func, const char *what)
59555955
return 1;
59565956
}
59575957
assert(PyTuple_Check(mro));
5958-
Py_ssize_t i, n;
5959-
n = PyTuple_GET_SIZE(mro);
5960-
for (i = 0; i < n; i++) {
5958+
5959+
/* Find the (base) type that defined the type's slot function. */
5960+
PyTypeObject *defining_type = type;
5961+
Py_ssize_t i;
5962+
for (i = PyTuple_GET_SIZE(mro) - 1; i >= 0; i--) {
59615963
PyTypeObject *base = (PyTypeObject*) PyTuple_GET_ITEM(mro, i);
5964+
if (base->tp_setattro == slot_tp_setattro) {
5965+
/* Ignore Python classes:
5966+
they never define their own C-level setattro. */
5967+
}
5968+
else if (base->tp_setattro == type->tp_setattro) {
5969+
defining_type = base;
5970+
break;
5971+
}
5972+
}
5973+
5974+
/* Reject calls that jump over intermediate C-level overrides. */
5975+
for (PyTypeObject *base = defining_type; base; base = base->tp_base) {
59625976
if (base->tp_setattro == func) {
5963-
/* 'func' is the earliest non-Python implementation in the MRO. */
5977+
/* 'func' is the right slot function to call. */
59645978
break;
5965-
} else if (base->tp_setattro != slot_tp_setattro) {
5979+
}
5980+
else if (base->tp_setattro != slot_tp_setattro) {
59665981
/* 'base' is not a Python class and overrides 'func'.
59675982
Its tp_setattro should be called instead. */
59685983
PyErr_Format(PyExc_TypeError,
@@ -5972,8 +5987,6 @@ hackcheck(PyObject *self, setattrofunc func, const char *what)
59725987
return 0;
59735988
}
59745989
}
5975-
/* Either 'func' is not in the mro (which should fail when checking 'self'),
5976-
or it's the right slot function to call. */
59775990
return 1;
59785991
}
59795992

0 commit comments

Comments
 (0)