summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorscoder <stefan_ml@behnel.de>2020-07-18 21:19:50 (GMT)
committerGitHub <noreply@github.com>2020-07-18 21:19:50 (GMT)
commitc53b310e5926266ce267c44a168165cacd786d6e (patch)
tree26741e4be5b2718fde92877507c111e9a8d00958
parent8ca8a2e8fb068863c1138f07e3098478ef8be12e (diff)
downloadcpython-c53b310e5926266ce267c44a168165cacd786d6e.zip
cpython-c53b310e5926266ce267c44a168165cacd786d6e.tar.gz
cpython-c53b310e5926266ce267c44a168165cacd786d6e.tar.bz2
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
-rw-r--r--Lib/test/test_descr.py36
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2020-07-18-08-15-32.bpo-41295.pu8Ezo.rst3
-rw-r--r--Objects/typeobject.c27
3 files changed, 59 insertions, 7 deletions
diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py
index 7bb6f2b..9738fb5 100644
--- a/Lib/test/test_descr.py
+++ b/Lib/test/test_descr.py
@@ -4308,6 +4308,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
diff --git a/Misc/NEWS.d/next/Core and Builtins/2020-07-18-08-15-32.bpo-41295.pu8Ezo.rst b/Misc/NEWS.d/next/Core and Builtins/2020-07-18-08-15-32.bpo-41295.pu8Ezo.rst
new file mode 100644
index 0000000..d61fd8f
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2020-07-18-08-15-32.bpo-41295.pu8Ezo.rst
@@ -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.
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 16f95f0..49b3c85 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -5961,14 +5961,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 == 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;
+ }
+ }
+
+ /* 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 earliest non-Python implementation in the MRO. */
+ /* 'func' is the right slot function to call. */
break;
- } else if (base->tp_setattro != slot_tp_setattro) {
+ }
+ 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,
@@ -5978,8 +5993,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;
}