Skip to content

Commit c53b310

Browse files
authored
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
1 parent 8ca8a2e commit c53b310

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
@@ -4308,6 +4308,42 @@ def test_carloverre(self):
43084308
else:
43094309
self.fail("Carlo Verre __delattr__ succeeded!")
43104310

4311+
def test_carloverre_multi_inherit_valid(self):
4312+
class A(type):
4313+
def __setattr__(cls, key, value):
4314+
type.__setattr__(cls, key, value)
4315+
4316+
class B:
4317+
pass
4318+
4319+
class C(B, A):
4320+
pass
4321+
4322+
obj = C('D', (object,), {})
4323+
try:
4324+
obj.test = True
4325+
except TypeError:
4326+
self.fail("setattr through direct base types should be legal")
4327+
4328+
def test_carloverre_multi_inherit_invalid(self):
4329+
class A(type):
4330+
def __setattr__(cls, key, value):
4331+
object.__setattr__(cls, key, value) # this should fail!
4332+
4333+
class B:
4334+
pass
4335+
4336+
class C(B, A):
4337+
pass
4338+
4339+
obj = C('D', (object,), {})
4340+
try:
4341+
obj.test = True
4342+
except TypeError:
4343+
pass
4344+
else:
4345+
self.fail("setattr through indirect base types should be rejected")
4346+
43114347
def test_weakref_segfault(self):
43124348
# Testing weakref segfault...
43134349
# 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
@@ -5961,14 +5961,29 @@ hackcheck(PyObject *self, setattrofunc func, const char *what)
59615961
return 1;
59625962
}
59635963
assert(PyTuple_Check(mro));
5964-
Py_ssize_t i, n;
5965-
n = PyTuple_GET_SIZE(mro);
5966-
for (i = 0; i < n; i++) {
5964+
5965+
/* Find the (base) type that defined the type's slot function. */
5966+
PyTypeObject *defining_type = type;
5967+
Py_ssize_t i;
5968+
for (i = PyTuple_GET_SIZE(mro) - 1; i >= 0; i--) {
59675969
PyTypeObject *base = (PyTypeObject*) PyTuple_GET_ITEM(mro, i);
5970+
if (base->tp_setattro == slot_tp_setattro) {
5971+
/* Ignore Python classes:
5972+
they never define their own C-level setattro. */
5973+
}
5974+
else if (base->tp_setattro == type->tp_setattro) {
5975+
defining_type = base;
5976+
break;
5977+
}
5978+
}
5979+
5980+
/* Reject calls that jump over intermediate C-level overrides. */
5981+
for (PyTypeObject *base = defining_type; base; base = base->tp_base) {
59685982
if (base->tp_setattro == func) {
5969-
/* 'func' is the earliest non-Python implementation in the MRO. */
5983+
/* 'func' is the right slot function to call. */
59705984
break;
5971-
} else if (base->tp_setattro != slot_tp_setattro) {
5985+
}
5986+
else if (base->tp_setattro != slot_tp_setattro) {
59725987
/* 'base' is not a Python class and overrides 'func'.
59735988
Its tp_setattro should be called instead. */
59745989
PyErr_Format(PyExc_TypeError,
@@ -5978,8 +5993,6 @@ hackcheck(PyObject *self, setattrofunc func, const char *what)
59785993
return 0;
59795994
}
59805995
}
5981-
/* Either 'func' is not in the mro (which should fail when checking 'self'),
5982-
or it's the right slot function to call. */
59835996
return 1;
59845997
}
59855998

0 commit comments

Comments
 (0)