diff options
author | Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> | 2020-07-03 00:28:41 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-03 00:28:41 (GMT) |
commit | bfec674254ea22ef9c0c335587eb65683f4145c7 (patch) | |
tree | e555e9cd5702dea1896e8940d43caecb61cc472b | |
parent | 32e9e17c426e814ef94e30337050417ce1f631d3 (diff) | |
download | cpython-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.py | 8 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Core and Builtins/2020-06-23-18-32-41.bpo-39960.Kez3fP.rst | 2 | ||||
-rw-r--r-- | Modules/_testcapimodule.c | 80 | ||||
-rw-r--r-- | Objects/typeobject.c | 41 |
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; } |