Skip to content

Commit 148f329

Browse files
authored
bpo-39960: Allow heap types in the "Carlo Verre" hack check that override "tp_setattro()" (GH-21092)
Automerge-Triggered-By: @gvanrossum
1 parent 67673b0 commit 148f329

File tree

4 files changed

+120
-11
lines changed

4 files changed

+120
-11
lines changed

Lib/test/test_capi.py

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

519+
def test_heaptype_with_setattro(self):
520+
obj = _testcapi.HeapCTypeSetattr()
521+
self.assertEqual(obj.pvalue, 10)
522+
obj.value = 12
523+
self.assertEqual(obj.pvalue, 12)
524+
del obj.value
525+
self.assertEqual(obj.pvalue, 0)
526+
519527
def test_pynumber_tobase(self):
520528
from _testcapi import pynumber_tobase
521529
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: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6528,6 +6528,80 @@ static PyType_Spec HeapCTypeWithWeakref_spec = {
65286528
HeapCTypeWithWeakref_slots
65296529
};
65306530

6531+
PyDoc_STRVAR(heapctypesetattr__doc__,
6532+
"A heap type without GC, but with overridden __setattr__.\n\n"
6533+
"The 'value' attribute is set to 10 in __init__ and updated via attribute setting.");
6534+
6535+
typedef struct {
6536+
PyObject_HEAD
6537+
long value;
6538+
} HeapCTypeSetattrObject;
6539+
6540+
static struct PyMemberDef heapctypesetattr_members[] = {
6541+
{"pvalue", T_LONG, offsetof(HeapCTypeSetattrObject, value)},
6542+
{NULL} /* Sentinel */
6543+
};
6544+
6545+
static int
6546+
heapctypesetattr_init(PyObject *self, PyObject *args, PyObject *kwargs)
6547+
{
6548+
((HeapCTypeSetattrObject *)self)->value = 10;
6549+
return 0;
6550+
}
6551+
6552+
static void
6553+
heapctypesetattr_dealloc(HeapCTypeSetattrObject *self)
6554+
{
6555+
PyTypeObject *tp = Py_TYPE(self);
6556+
PyObject_Del(self);
6557+
Py_DECREF(tp);
6558+
}
6559+
6560+
static int
6561+
heapctypesetattr_setattro(HeapCTypeSetattrObject *self, PyObject *attr, PyObject *value)
6562+
{
6563+
PyObject *svalue = PyUnicode_FromString("value");
6564+
if (svalue == NULL)
6565+
return -1;
6566+
int eq = PyObject_RichCompareBool(svalue, attr, Py_EQ);
6567+
Py_DECREF(svalue);
6568+
if (eq < 0)
6569+
return -1;
6570+
if (!eq) {
6571+
return PyObject_GenericSetAttr((PyObject*) self, attr, value);
6572+
}
6573+
if (value == NULL) {
6574+
self->value = 0;
6575+
return 0;
6576+
}
6577+
PyObject *ivalue = PyNumber_Long(value);
6578+
if (ivalue == NULL)
6579+
return -1;
6580+
long v = PyLong_AsLong(ivalue);
6581+
Py_DECREF(ivalue);
6582+
if (v == -1 && PyErr_Occurred())
6583+
return -1;
6584+
self->value = v;
6585+
return 0;
6586+
}
6587+
6588+
static PyType_Slot HeapCTypeSetattr_slots[] = {
6589+
{Py_tp_init, heapctypesetattr_init},
6590+
{Py_tp_members, heapctypesetattr_members},
6591+
{Py_tp_setattro, heapctypesetattr_setattro},
6592+
{Py_tp_dealloc, heapctypesetattr_dealloc},
6593+
{Py_tp_doc, (char*)heapctypesetattr__doc__},
6594+
{0, 0},
6595+
};
6596+
6597+
static PyType_Spec HeapCTypeSetattr_spec = {
6598+
"_testcapi.HeapCTypeSetattr",
6599+
sizeof(HeapCTypeSetattrObject),
6600+
0,
6601+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
6602+
HeapCTypeSetattr_slots
6603+
};
6604+
65316605
static PyMethodDef meth_instance_methods[] = {
65326606
{"meth_varargs", meth_varargs, METH_VARARGS},
65336607
{"meth_varargs_keywords", (PyCFunction)(void(*)(void))meth_varargs_keywords, METH_VARARGS|METH_KEYWORDS},
@@ -6834,6 +6908,12 @@ PyInit__testcapi(void)
68346908
}
68356909
PyModule_AddObject(m, "HeapCTypeWithBuffer", HeapCTypeWithBuffer);
68366910

6911+
PyObject *HeapCTypeSetattr = PyType_FromSpec(&HeapCTypeSetattr_spec);
6912+
if (HeapCTypeSetattr == NULL) {
6913+
return NULL;
6914+
}
6915+
PyModule_AddObject(m, "HeapCTypeSetattr", HeapCTypeSetattr);
6916+
68376917
PyObject *subclass_with_finalizer_bases = PyTuple_Pack(1, HeapCTypeSubclass);
68386918
if (subclass_with_finalizer_bases == NULL) {
68396919
return NULL;

Objects/typeobject.c

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

100+
static int
101+
slot_tp_setattro(PyObject *self, PyObject *name, PyObject *value);
102+
100103
/*
101104
* finds the beginning of the docstring's introspection signature.
102105
* if present, returns a pointer pointing to the first '('.
@@ -5945,22 +5948,38 @@ wrap_delitem(PyObject *self, PyObject *args, void *wrapped)
59455948
}
59465949

59475950
/* Helper to check for object.__setattr__ or __delattr__ applied to a type.
5948-
This is called the Carlo Verre hack after its discoverer. */
5951+
This is called the Carlo Verre hack after its discoverer. See
5952+
https://mail.python.org/pipermail/python-dev/2003-April/034535.html
5953+
*/
59495954
static int
59505955
hackcheck(PyObject *self, setattrofunc func, const char *what)
59515956
{
59525957
PyTypeObject *type = Py_TYPE(self);
5953-
while (type && type->tp_flags & Py_TPFLAGS_HEAPTYPE)
5954-
type = type->tp_base;
5955-
/* If type is NULL now, this is a really weird type.
5956-
In the spirit of backwards compatibility (?), just shut up. */
5957-
if (type && type->tp_setattro != func) {
5958-
PyErr_Format(PyExc_TypeError,
5959-
"can't apply this %s to %s object",
5960-
what,
5961-
type->tp_name);
5962-
return 0;
5958+
PyObject *mro = type->tp_mro;
5959+
if (!mro) {
5960+
/* Probably ok not to check the call in this case. */
5961+
return 1;
5962+
}
5963+
assert(PyTuple_Check(mro));
5964+
Py_ssize_t i, n;
5965+
n = PyTuple_GET_SIZE(mro);
5966+
for (i = 0; i < n; i++) {
5967+
PyTypeObject *base = (PyTypeObject*) PyTuple_GET_ITEM(mro, i);
5968+
if (base->tp_setattro == func) {
5969+
/* 'func' is the earliest non-Python implementation in the MRO. */
5970+
break;
5971+
} else if (base->tp_setattro != slot_tp_setattro) {
5972+
/* 'base' is not a Python class and overrides 'func'.
5973+
Its tp_setattro should be called instead. */
5974+
PyErr_Format(PyExc_TypeError,
5975+
"can't apply this %s to %s object",
5976+
what,
5977+
type->tp_name);
5978+
return 0;
5979+
}
59635980
}
5981+
/* Either 'func' is not in the mro (which should fail when checking 'self'),
5982+
or it's the right slot function to call. */
59645983
return 1;
59655984
}
59665985

0 commit comments

Comments
 (0)