Skip to content

Commit

Permalink
bpo-38140: Make dict and weakref offsets opaque for C heap types (pyt…
Browse files Browse the repository at this point in the history
…hon#16076)

* Make dict and weakref offsets opaque for C heap types

* Add news
  • Loading branch information
eduardo-elizondo authored and DinoV committed Sep 19, 2019
1 parent 079931d commit 3368f3c
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 8 deletions.
3 changes: 1 addition & 2 deletions Doc/c-api/type.rst
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,8 @@ The following functions and structs are used to create
* :c:member:`~PyTypeObject.tp_cache`
* :c:member:`~PyTypeObject.tp_subclasses`
* :c:member:`~PyTypeObject.tp_weaklist`
* :c:member:`~PyTypeObject.tp_vectorcall`
* :c:member:`~PyTypeObject.tp_print`
* :c:member:`~PyTypeObject.tp_weaklistoffset`
* :c:member:`~PyTypeObject.tp_dictoffset`
* :c:member:`~PyBufferProcs.bf_getbuffer`
* :c:member:`~PyBufferProcs.bf_releasebuffer`
Expand Down
27 changes: 27 additions & 0 deletions Lib/test/test_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import threading
import time
import unittest
import weakref
from test import support
from test.support import MISSING_C_DOCSTRINGS
from test.support.script_helper import assert_python_failure, assert_python_ok
Expand Down Expand Up @@ -437,6 +438,32 @@ def __del__(self):
# Test that subtype_dealloc decref the newly assigned __class__ only once
self.assertEqual(new_type_refcnt, sys.getrefcount(A))

def test_heaptype_with_dict(self):
inst = _testcapi.HeapCTypeWithDict()
inst.foo = 42
self.assertEqual(inst.foo, 42)
self.assertEqual(inst.dictobj, inst.__dict__)
self.assertEqual(inst.dictobj, {"foo": 42})

inst = _testcapi.HeapCTypeWithDict()
self.assertEqual({}, inst.__dict__)

def test_heaptype_with_negative_dict(self):
inst = _testcapi.HeapCTypeWithNegativeDict()
inst.foo = 42
self.assertEqual(inst.foo, 42)
self.assertEqual(inst.dictobj, inst.__dict__)
self.assertEqual(inst.dictobj, {"foo": 42})

inst = _testcapi.HeapCTypeWithNegativeDict()
self.assertEqual({}, inst.__dict__)

def test_heaptype_with_weakref(self):
inst = _testcapi.HeapCTypeWithWeakref()
ref = weakref.ref(inst)
self.assertEqual(ref(), inst)
self.assertEqual(inst.weakreflist, ref)

def test_c_subclass_of_heap_ctype_with_tpdealloc_decrefs_once(self):
subclass_instance = _testcapi.HeapCTypeSubclass()
type_refcnt = sys.getrefcount(_testcapi.HeapCTypeSubclass)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Make dict and weakref offsets opaque for C heap types by passing the offsets
through PyMemberDef
2 changes: 1 addition & 1 deletion Modules/_struct.c
Original file line number Diff line number Diff line change
Expand Up @@ -2024,7 +2024,7 @@ static struct PyMethodDef s_methods[] = {
};

static PyMemberDef s_members[] = {
{"__weaklistoffset__", T_NONE, offsetof(PyStructObject, weakreflist), READONLY},
{"__weaklistoffset__", T_PYSSIZET, offsetof(PyStructObject, weakreflist), READONLY},
{NULL} /* sentinel */
};

Expand Down
118 changes: 118 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -6363,6 +6363,106 @@ static PyType_Spec HeapCTypeSubclassWithFinalizer_spec = {
HeapCTypeSubclassWithFinalizer_slots
};

typedef struct {
PyObject_HEAD
PyObject *dict;
} HeapCTypeWithDictObject;

static void
heapctypewithdict_dealloc(HeapCTypeWithDictObject* self)
{

PyTypeObject *tp = Py_TYPE(self);
Py_XDECREF(self->dict);
PyObject_DEL(self);
Py_DECREF(tp);
}

static PyGetSetDef heapctypewithdict_getsetlist[] = {
{"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict},
{NULL} /* Sentinel */
};

static struct PyMemberDef heapctypewithdict_members[] = {
{"dictobj", T_OBJECT, offsetof(HeapCTypeWithDictObject, dict)},
{"__dictoffset__", T_PYSSIZET, offsetof(HeapCTypeWithDictObject, dict), READONLY},
{NULL} /* Sentinel */
};

static PyType_Slot HeapCTypeWithDict_slots[] = {
{Py_tp_members, heapctypewithdict_members},
{Py_tp_getset, heapctypewithdict_getsetlist},
{Py_tp_dealloc, heapctypewithdict_dealloc},
{0, 0},
};

static PyType_Spec HeapCTypeWithDict_spec = {
"_testcapi.HeapCTypeWithDict",
sizeof(HeapCTypeWithDictObject),
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
HeapCTypeWithDict_slots
};

static struct PyMemberDef heapctypewithnegativedict_members[] = {
{"dictobj", T_OBJECT, offsetof(HeapCTypeWithDictObject, dict)},
{"__dictoffset__", T_PYSSIZET, -sizeof(void*), READONLY},
{NULL} /* Sentinel */
};

static PyType_Slot HeapCTypeWithNegativeDict_slots[] = {
{Py_tp_members, heapctypewithnegativedict_members},
{Py_tp_getset, heapctypewithdict_getsetlist},
{Py_tp_dealloc, heapctypewithdict_dealloc},
{0, 0},
};

static PyType_Spec HeapCTypeWithNegativeDict_spec = {
"_testcapi.HeapCTypeWithNegativeDict",
sizeof(HeapCTypeWithDictObject),
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
HeapCTypeWithNegativeDict_slots
};

typedef struct {
PyObject_HEAD
PyObject *weakreflist;
} HeapCTypeWithWeakrefObject;

static struct PyMemberDef heapctypewithweakref_members[] = {
{"weakreflist", T_OBJECT, offsetof(HeapCTypeWithWeakrefObject, weakreflist)},
{"__weaklistoffset__", T_PYSSIZET,
offsetof(HeapCTypeWithWeakrefObject, weakreflist), READONLY},
{NULL} /* Sentinel */
};

static void
heapctypewithweakref_dealloc(HeapCTypeWithWeakrefObject* self)
{

PyTypeObject *tp = Py_TYPE(self);
if (self->weakreflist != NULL)
PyObject_ClearWeakRefs((PyObject *) self);
Py_XDECREF(self->weakreflist);
PyObject_DEL(self);
Py_DECREF(tp);
}

static PyType_Slot HeapCTypeWithWeakref_slots[] = {
{Py_tp_members, heapctypewithweakref_members},
{Py_tp_dealloc, heapctypewithweakref_dealloc},
{0, 0},
};

static PyType_Spec HeapCTypeWithWeakref_spec = {
"_testcapi.HeapCTypeWithWeakref",
sizeof(HeapCTypeWithWeakrefObject),
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
HeapCTypeWithWeakref_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 @@ -6596,6 +6696,24 @@ PyInit__testcapi(void)
Py_DECREF(subclass_bases);
PyModule_AddObject(m, "HeapCTypeSubclass", HeapCTypeSubclass);

PyObject *HeapCTypeWithDict = PyType_FromSpec(&HeapCTypeWithDict_spec);
if (HeapCTypeWithDict == NULL) {
return NULL;
}
PyModule_AddObject(m, "HeapCTypeWithDict", HeapCTypeWithDict);

PyObject *HeapCTypeWithNegativeDict = PyType_FromSpec(&HeapCTypeWithNegativeDict_spec);
if (HeapCTypeWithNegativeDict == NULL) {
return NULL;
}
PyModule_AddObject(m, "HeapCTypeWithNegativeDict", HeapCTypeWithNegativeDict);

PyObject *HeapCTypeWithWeakref = PyType_FromSpec(&HeapCTypeWithWeakref_spec);
if (HeapCTypeWithWeakref == NULL) {
return NULL;
}
PyModule_AddObject(m, "HeapCTypeWithWeakref", HeapCTypeWithWeakref);

PyObject *subclass_with_finalizer_bases = PyTuple_Pack(1, HeapCTypeSubclass);
if (subclass_with_finalizer_bases == NULL) {
return NULL;
Expand Down
27 changes: 25 additions & 2 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2853,15 +2853,27 @@ PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
PyTypeObject *type, *base;

PyType_Slot *slot;
Py_ssize_t nmembers;
Py_ssize_t nmembers, weaklistoffset, dictoffset;
char *s, *res_start;

nmembers = 0;
nmembers = weaklistoffset = dictoffset = 0;
for (slot = spec->slots; slot->slot; slot++) {
if (slot->slot == Py_tp_members) {
nmembers = 0;
for (memb = slot->pfunc; memb->name != NULL; memb++) {
nmembers++;
if (strcmp(memb->name, "__weaklistoffset__") == 0) {
// The PyMemberDef must be a Py_ssize_t and readonly
assert(memb->type == T_PYSSIZET);
assert(memb->flags == READONLY);
weaklistoffset = memb->offset;
}
if (strcmp(memb->name, "__dictoffset__") == 0) {
// The PyMemberDef must be a Py_ssize_t and readonly
assert(memb->type == T_PYSSIZET);
assert(memb->flags == READONLY);
dictoffset = memb->offset;
}
}
}
}
Expand Down Expand Up @@ -2990,6 +3002,17 @@ PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
res->ht_cached_keys = _PyDict_NewKeysForClass();
}

if (weaklistoffset) {
type->tp_weaklistoffset = weaklistoffset;
if (PyDict_DelItemString((PyObject *)type->tp_dict, "__weaklistoffset__") < 0)
goto fail;
}
if (dictoffset) {
type->tp_dictoffset = dictoffset;
if (PyDict_DelItemString((PyObject *)type->tp_dict, "__dictoffset__") < 0)
goto fail;
}

/* Set type.__module__ */
s = strrchr(spec->name, '.');
if (s != NULL) {
Expand Down
8 changes: 7 additions & 1 deletion Parser/asdl_c.py
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,11 @@ def visitModule(self, mod):
return Py_BuildValue("O()", Py_TYPE(self));
}
static PyMemberDef ast_type_members[] = {
{"__dictoffset__", T_PYSSIZET, offsetof(AST_object, dict), READONLY},
{NULL} /* Sentinel */
};
static PyMethodDef ast_type_methods[] = {
{"__reduce__", ast_type_reduce, METH_NOARGS, NULL},
{NULL}
Expand All @@ -740,6 +745,7 @@ def visitModule(self, mod):
{Py_tp_setattro, PyObject_GenericSetAttr},
{Py_tp_traverse, ast_traverse},
{Py_tp_clear, ast_clear},
{Py_tp_members, ast_type_members},
{Py_tp_methods, ast_type_methods},
{Py_tp_getset, ast_type_getsets},
{Py_tp_init, ast_type_init},
Expand Down Expand Up @@ -930,7 +936,6 @@ def visitModule(self, mod):
self.emit("if (init_identifiers() < 0) return 0;", 1)
self.emit("state->AST_type = PyType_FromSpec(&AST_type_spec);", 1)
self.emit("if (!state->AST_type) return 0;", 1)
self.emit("((PyTypeObject*)state->AST_type)->tp_dictoffset = offsetof(AST_object, dict);", 1)
self.emit("if (add_ast_fields() < 0) return 0;", 1)
for dfn in mod.dfns:
self.visit(dfn)
Expand Down Expand Up @@ -1372,6 +1377,7 @@ def main(srcfile, dump_module=False):
f.write('\n')
f.write('#include "Python.h"\n')
f.write('#include "%s-ast.h"\n' % mod.name)
f.write('#include "structmember.h"\n')
f.write('\n')

generate_module_def(f, mod)
Expand Down
9 changes: 7 additions & 2 deletions Python/Python-ast.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 3368f3c

Please sign in to comment.