Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
bpo-39960: Allow heap types in the "Carlo Verre" hack check that over…
…ride "tp_setattro()" (GH-21092)

Automerge-Triggered-By: @gvanrossum
(cherry picked from commit 148f329)

Co-authored-by: scoder <stefan_ml@behnel.de>
  • Loading branch information
miss-islington and scoder committed Jul 3, 2020
1 parent 32e9e17 commit bfec674
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 11 deletions.
8 changes: 8 additions & 0 deletions Lib/test/test_capi.py
Expand Up @@ -515,6 +515,14 @@ def test_c_subclass_of_heap_ctype_with_del_modifying_dunder_class_only_decrefs_o
# 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')
Expand Down
@@ -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.
80 changes: 80 additions & 0 deletions Modules/_testcapimodule.c
Expand Up @@ -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},
Expand Down Expand Up @@ -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;
Expand Down
41 changes: 30 additions & 11 deletions Objects/typeobject.c
Expand Up @@ -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 '('.
Expand Down Expand Up @@ -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;
}

Expand Down

0 comments on commit bfec674

Please sign in to comment.