Skip to content

Commit 8912c18

Browse files
authored
bpo-39960: Allow heap types in the "Carlo Verre" hack check that override "tp_setattro()" (GH-21092) (GH-21339)
Backport to Py3.8.
1 parent 01c0925 commit 8912c18

File tree

4 files changed

+119
-11
lines changed

4 files changed

+119
-11
lines changed

Lib/test/test_capi.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,14 @@ def test_c_subclass_of_heap_ctype_with_del_modifying_dunder_class_only_decrefs_o
473473
# Test that subtype_dealloc decref the newly assigned __class__ only once
474474
self.assertEqual(new_type_refcnt, sys.getrefcount(_testcapi.HeapCTypeSubclass))
475475

476+
def test_heaptype_with_setattro(self):
477+
obj = _testcapi.HeapCTypeSetattr()
478+
self.assertEqual(obj.pvalue, 10)
479+
obj.value = 12
480+
self.assertEqual(obj.pvalue, 12)
481+
del obj.value
482+
self.assertEqual(obj.pvalue, 0)
483+
476484
def test_pynumber_tobase(self):
477485
from _testcapi import pynumber_tobase
478486
self.assertEqual(pynumber_tobase(123, 2), '0b1111011')
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The "hackcheck" that prevents sneaking around a type's __setattr__() by calling the
2+
superclass method was rewritten to allow C implemented heap types.

Modules/_testcapimodule.c

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6177,6 +6177,79 @@ static PyType_Spec HeapCTypeSubclassWithFinalizer_spec = {
61776177
HeapCTypeSubclassWithFinalizer_slots
61786178
};
61796179

6180+
PyDoc_STRVAR(heapctypesetattr__doc__,
6181+
"A heap type without GC, but with overridden __setattr__.\n\n"
6182+
"The 'value' attribute is set to 10 in __init__ and updated via attribute setting.");
6183+
6184+
typedef struct {
6185+
PyObject_HEAD
6186+
long value;
6187+
} HeapCTypeSetattrObject;
6188+
6189+
static struct PyMemberDef heapctypesetattr_members[] = {
6190+
{"pvalue", T_LONG, offsetof(HeapCTypeSetattrObject, value)},
6191+
{NULL} /* Sentinel */
6192+
};
6193+
6194+
static int
6195+
heapctypesetattr_init(PyObject *self, PyObject *args, PyObject *kwargs)
6196+
{
6197+
((HeapCTypeSetattrObject *)self)->value = 10;
6198+
return 0;
6199+
}
6200+
6201+
static void
6202+
heapctypesetattr_dealloc(HeapCTypeSetattrObject *self)
6203+
{
6204+
PyTypeObject *tp = Py_TYPE(self);
6205+
PyObject_Del(self);
6206+
Py_DECREF(tp);
6207+
}
6208+
6209+
static int
6210+
heapctypesetattr_setattro(HeapCTypeSetattrObject *self, PyObject *attr, PyObject *value)
6211+
{
6212+
PyObject *svalue = PyUnicode_FromString("value");
6213+
if (svalue == NULL)
6214+
return -1;
6215+
int eq = PyObject_RichCompareBool(svalue, attr, Py_EQ);
6216+
Py_DECREF(svalue);
6217+
if (eq < 0)
6218+
return -1;
6219+
if (!eq) {
6220+
return PyObject_GenericSetAttr((PyObject*) self, attr, value);
6221+
}
6222+
if (value == NULL) {
6223+
self->value = 0;
6224+
return 0;
6225+
}
6226+
PyObject *ivalue = PyNumber_Long(value);
6227+
if (ivalue == NULL)
6228+
return -1;
6229+
long v = PyLong_AsLong(ivalue);
6230+
Py_DECREF(ivalue);
6231+
if (v == -1 && PyErr_Occurred())
6232+
return -1;
6233+
self->value = v;
6234+
return 0;
6235+
}
6236+
6237+
static PyType_Slot HeapCTypeSetattr_slots[] = {
6238+
{Py_tp_init, heapctypesetattr_init},
6239+
{Py_tp_members, heapctypesetattr_members},
6240+
{Py_tp_setattro, heapctypesetattr_setattro},
6241+
{Py_tp_dealloc, heapctypesetattr_dealloc},
6242+
{Py_tp_doc, (char*)heapctypesetattr__doc__},
6243+
{0, 0},
6244+
};
6245+
6246+
static PyType_Spec HeapCTypeSetattr_spec = {
6247+
"_testcapi.HeapCTypeSetattr",
6248+
sizeof(HeapCTypeSetattrObject),
6249+
0,
6250+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
6251+
HeapCTypeSetattr_slots
6252+
};
61806253

61816254
static struct PyModuleDef _testcapimodule = {
61826255
PyModuleDef_HEAD_INIT,
@@ -6336,6 +6409,12 @@ PyInit__testcapi(void)
63366409
Py_DECREF(subclass_bases);
63376410
PyModule_AddObject(m, "HeapCTypeSubclass", HeapCTypeSubclass);
63386411

6412+
PyObject *HeapCTypeSetattr = PyType_FromSpec(&HeapCTypeSetattr_spec);
6413+
if (HeapCTypeSetattr == NULL) {
6414+
return NULL;
6415+
}
6416+
PyModule_AddObject(m, "HeapCTypeSetattr", HeapCTypeSetattr);
6417+
63396418
PyObject *subclass_with_finalizer_bases = PyTuple_Pack(1, HeapCTypeSubclass);
63406419
if (subclass_with_finalizer_bases == NULL) {
63416420
return NULL;

Objects/typeobject.c

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ clear_slotdefs(void);
8181
static PyObject *
8282
lookup_maybe_method(PyObject *self, _Py_Identifier *attrid, int *unbound);
8383

84+
static int
85+
slot_tp_setattro(PyObject *self, PyObject *name, PyObject *value);
86+
8487
/*
8588
* finds the beginning of the docstring's introspection signature.
8689
* if present, returns a pointer pointing to the first '('.
@@ -5806,22 +5809,38 @@ wrap_delitem(PyObject *self, PyObject *args, void *wrapped)
58065809
}
58075810

58085811
/* Helper to check for object.__setattr__ or __delattr__ applied to a type.
5809-
This is called the Carlo Verre hack after its discoverer. */
5812+
This is called the Carlo Verre hack after its discoverer. See
5813+
https://mail.python.org/pipermail/python-dev/2003-April/034535.html
5814+
*/
58105815
static int
58115816
hackcheck(PyObject *self, setattrofunc func, const char *what)
58125817
{
58135818
PyTypeObject *type = Py_TYPE(self);
5814-
while (type && type->tp_flags & Py_TPFLAGS_HEAPTYPE)
5815-
type = type->tp_base;
5816-
/* If type is NULL now, this is a really weird type.
5817-
In the spirit of backwards compatibility (?), just shut up. */
5818-
if (type && type->tp_setattro != func) {
5819-
PyErr_Format(PyExc_TypeError,
5820-
"can't apply this %s to %s object",
5821-
what,
5822-
type->tp_name);
5823-
return 0;
5819+
PyObject *mro = type->tp_mro;
5820+
if (!mro) {
5821+
/* Probably ok not to check the call in this case. */
5822+
return 1;
5823+
}
5824+
assert(PyTuple_Check(mro));
5825+
Py_ssize_t i, n;
5826+
n = PyTuple_GET_SIZE(mro);
5827+
for (i = 0; i < n; i++) {
5828+
PyTypeObject *base = (PyTypeObject*) PyTuple_GET_ITEM(mro, i);
5829+
if (base->tp_setattro == func) {
5830+
/* 'func' is the earliest non-Python implementation in the MRO. */
5831+
break;
5832+
} else if (base->tp_setattro != slot_tp_setattro) {
5833+
/* 'base' is not a Python class and overrides 'func'.
5834+
Its tp_setattro should be called instead. */
5835+
PyErr_Format(PyExc_TypeError,
5836+
"can't apply this %s to %s object",
5837+
what,
5838+
type->tp_name);
5839+
return 0;
5840+
}
58245841
}
5842+
/* Either 'func' is not in the mro (which should fail when checking 'self'),
5843+
or it's the right slot function to call. */
58255844
return 1;
58265845
}
58275846

0 commit comments

Comments
 (0)