summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2020-07-03 00:28:41 (GMT)
committerGitHub <noreply@github.com>2020-07-03 00:28:41 (GMT)
commitbfec674254ea22ef9c0c335587eb65683f4145c7 (patch)
treee555e9cd5702dea1896e8940d43caecb61cc472b
parent32e9e17c426e814ef94e30337050417ce1f631d3 (diff)
downloadcpython-bfec674254ea22ef9c0c335587eb65683f4145c7.zip
cpython-bfec674254ea22ef9c0c335587eb65683f4145c7.tar.gz
cpython-bfec674254ea22ef9c0c335587eb65683f4145c7.tar.bz2
bpo-39960: Allow heap types in the "Carlo Verre" hack check that override "tp_setattro()" (GH-21092)
Automerge-Triggered-By: @gvanrossum (cherry picked from commit 148f32913573c29250dfb3f0d079eb8847633621) Co-authored-by: scoder <stefan_ml@behnel.de>
-rw-r--r--Lib/test/test_capi.py8
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2020-06-23-18-32-41.bpo-39960.Kez3fP.rst2
-rw-r--r--Modules/_testcapimodule.c80
-rw-r--r--Objects/typeobject.c41
4 files changed, 120 insertions, 11 deletions
diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py
index 4efecfd..5150d57 100644
--- a/Lib/test/test_capi.py
+++ b/Lib/test/test_capi.py
@@ -515,6 +515,14 @@ class CAPITest(unittest.TestCase):
# Test that subtype_dealloc decref the newly assigned __class__ only once
self.assertEqual(new_type_refcnt, sys.getrefcount(_testcapi.HeapCTypeSubclass))
+ def test_heaptype_with_setattro(self):
+ obj = _testcapi.HeapCTypeSetattr()
+ self.assertEqual(obj.pvalue, 10)
+ obj.value = 12
+ self.assertEqual(obj.pvalue, 12)
+ del obj.value
+ self.assertEqual(obj.pvalue, 0)
+
def test_pynumber_tobase(self):
from _testcapi import pynumber_tobase
self.assertEqual(pynumber_tobase(123, 2), '0b1111011')
diff --git a/Misc/NEWS.d/next/Core and Builtins/2020-06-23-18-32-41.bpo-39960.Kez3fP.rst b/Misc/NEWS.d/next/Core and Builtins/2020-06-23-18-32-41.bpo-39960.Kez3fP.rst
new file mode 100644
index 0000000..f69fccf
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2020-06-23-18-32-41.bpo-39960.Kez3fP.rst
@@ -0,0 +1,2 @@
+The "hackcheck" that prevents sneaking around a type's __setattr__() by calling the
+superclass method was rewritten to allow C implemented heap types.
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index de14408..c92a71a 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -6528,6 +6528,80 @@ static PyType_Spec HeapCTypeWithWeakref_spec = {
HeapCTypeWithWeakref_slots
};
+PyDoc_STRVAR(heapctypesetattr__doc__,
+"A heap type without GC, but with overridden __setattr__.\n\n"
+"The 'value' attribute is set to 10 in __init__ and updated via attribute setting.");
+
+typedef struct {
+ PyObject_HEAD
+ long value;
+} HeapCTypeSetattrObject;
+
+static struct PyMemberDef heapctypesetattr_members[] = {
+ {"pvalue", T_LONG, offsetof(HeapCTypeSetattrObject, value)},
+ {NULL} /* Sentinel */
+};
+
+static int
+heapctypesetattr_init(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ ((HeapCTypeSetattrObject *)self)->value = 10;
+ return 0;
+}
+
+static void
+heapctypesetattr_dealloc(HeapCTypeSetattrObject *self)
+{
+ PyTypeObject *tp = Py_TYPE(self);
+ PyObject_Del(self);
+ Py_DECREF(tp);
+}
+
+static int
+heapctypesetattr_setattro(HeapCTypeSetattrObject *self, PyObject *attr, PyObject *value)
+{
+ PyObject *svalue = PyUnicode_FromString("value");
+ if (svalue == NULL)
+ return -1;
+ int eq = PyObject_RichCompareBool(svalue, attr, Py_EQ);
+ Py_DECREF(svalue);
+ if (eq < 0)
+ return -1;
+ if (!eq) {
+ return PyObject_GenericSetAttr((PyObject*) self, attr, value);
+ }
+ if (value == NULL) {
+ self->value = 0;
+ return 0;
+ }
+ PyObject *ivalue = PyNumber_Long(value);
+ if (ivalue == NULL)
+ return -1;
+ long v = PyLong_AsLong(ivalue);
+ Py_DECREF(ivalue);
+ if (v == -1 && PyErr_Occurred())
+ return -1;
+ self->value = v;
+ return 0;
+}
+
+static PyType_Slot HeapCTypeSetattr_slots[] = {
+ {Py_tp_init, heapctypesetattr_init},
+ {Py_tp_members, heapctypesetattr_members},
+ {Py_tp_setattro, heapctypesetattr_setattro},
+ {Py_tp_dealloc, heapctypesetattr_dealloc},
+ {Py_tp_doc, (char*)heapctypesetattr__doc__},
+ {0, 0},
+};
+
+static PyType_Spec HeapCTypeSetattr_spec = {
+ "_testcapi.HeapCTypeSetattr",
+ sizeof(HeapCTypeSetattrObject),
+ 0,
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ HeapCTypeSetattr_slots
+};
+
static PyMethodDef meth_instance_methods[] = {
{"meth_varargs", meth_varargs, METH_VARARGS},
{"meth_varargs_keywords", (PyCFunction)(void(*)(void))meth_varargs_keywords, METH_VARARGS|METH_KEYWORDS},
@@ -6834,6 +6908,12 @@ PyInit__testcapi(void)
}
PyModule_AddObject(m, "HeapCTypeWithBuffer", HeapCTypeWithBuffer);
+ PyObject *HeapCTypeSetattr = PyType_FromSpec(&HeapCTypeSetattr_spec);
+ if (HeapCTypeSetattr == NULL) {
+ return NULL;
+ }
+ PyModule_AddObject(m, "HeapCTypeSetattr", HeapCTypeSetattr);
+
PyObject *subclass_with_finalizer_bases = PyTuple_Pack(1, HeapCTypeSubclass);
if (subclass_with_finalizer_bases == NULL) {
return NULL;
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index e52e21d..6c428a7 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -91,6 +91,9 @@ clear_slotdefs(void);
static PyObject *
lookup_maybe_method(PyObject *self, _Py_Identifier *attrid, int *unbound);
+static int
+slot_tp_setattro(PyObject *self, PyObject *name, PyObject *value);
+
/*
* finds the beginning of the docstring's introspection signature.
* if present, returns a pointer pointing to the first '('.
@@ -5939,22 +5942,38 @@ wrap_delitem(PyObject *self, PyObject *args, void *wrapped)
}
/* Helper to check for object.__setattr__ or __delattr__ applied to a type.
- This is called the Carlo Verre hack after its discoverer. */
+ This is called the Carlo Verre hack after its discoverer. See
+ https://mail.python.org/pipermail/python-dev/2003-April/034535.html
+ */
static int
hackcheck(PyObject *self, setattrofunc func, const char *what)
{
PyTypeObject *type = Py_TYPE(self);
- while (type && type->tp_flags & Py_TPFLAGS_HEAPTYPE)
- type = type->tp_base;
- /* If type is NULL now, this is a really weird type.
- In the spirit of backwards compatibility (?), just shut up. */
- if (type && type->tp_setattro != func) {
- PyErr_Format(PyExc_TypeError,
- "can't apply this %s to %s object",
- what,
- type->tp_name);
- return 0;
+ PyObject *mro = type->tp_mro;
+ if (!mro) {
+ /* Probably ok not to check the call in this case. */
+ return 1;
+ }
+ assert(PyTuple_Check(mro));
+ Py_ssize_t i, n;
+ n = PyTuple_GET_SIZE(mro);
+ for (i = 0; i < n; i++) {
+ PyTypeObject *base = (PyTypeObject*) PyTuple_GET_ITEM(mro, i);
+ if (base->tp_setattro == func) {
+ /* 'func' is the earliest non-Python implementation in the MRO. */
+ 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,
+ "can't apply this %s to %s object",
+ what,
+ type->tp_name);
+ 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;
}