Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-45315: PyType_FromSpec: Copy spec->name and have the type own the memory for its name #29103

Merged
merged 3 commits into from
Oct 21, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Include/cpython/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ typedef struct _heaptypeobject {
PyObject *ht_name, *ht_slots, *ht_qualname;
struct _dictkeysobject *ht_cached_keys;
PyObject *ht_module;
char *_ht_tpname; // Storage for "tp_name"; see PyType_FromModuleAndSpec
/* here are optional user slots, followed by the members. */
} PyHeapTypeObject;

Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -1419,7 +1419,7 @@ def delx(self): del self.__x
'3P' # PyMappingMethods
'10P' # PySequenceMethods
'2P' # PyBufferProcs
'5P')
'6P')
class newstyleclass(object): pass
# Separate block for PyDictKeysObject with 8 keys and 5 entries
check(newstyleclass, s + calcsize(DICT_KEY_STRUCT_FORMAT) + 32 + 21*calcsize("n2P"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
PyType_FromSpec now copies the class name from the spec to a buffer owned by
the class, so the original can be safely deallocated. Patch by Petr
Viktorin.
encukou marked this conversation as resolved.
Show resolved Hide resolved
121 changes: 121 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1163,6 +1163,126 @@ test_get_type_name(PyObject *self, PyObject *Py_UNUSED(ignored))
}


static PyObject *
simple_str(PyObject *self) {
return PyUnicode_FromString("<test>");
}


static PyObject *
test_type_from_ephemeral_spec(PyObject *self, PyObject *Py_UNUSED(ignored))
{
// Test that a heap type can be created from a spec that's later deleted
// (along with all its contents).
// All necessary data must be copied and held by the class
PyType_Spec *spec = NULL;
char *name = NULL;
char *doc = NULL;
PyType_Slot *slots = NULL;
PyObject *class = NULL;
PyObject *instance = NULL;
PyObject *obj = NULL;
PyObject *result = NULL;

/* create a spec (and all its contents) on the heap */

const char NAME[] = "testcapi._Test";
const char DOC[] = "a test class";

spec = PyMem_New(PyType_Spec, 1);
if (spec == NULL) {
PyErr_NoMemory();
goto finally;
}
name = PyMem_New(char, sizeof(NAME));
if (name == NULL) {
PyErr_NoMemory();
goto finally;
}
memcpy(name, NAME, sizeof(NAME));

doc = PyMem_New(char, sizeof(DOC));
if (name == NULL) {
PyErr_NoMemory();
goto finally;
}
memcpy(doc, DOC, sizeof(DOC));

spec->name = name;
spec->basicsize = sizeof(PyObject);
spec->itemsize = 0;
spec->flags = Py_TPFLAGS_DEFAULT;
slots = PyMem_New(PyType_Slot, 3);
if (slots == NULL) {
PyErr_NoMemory();
goto finally;
}
slots[0].slot = Py_tp_str;
slots[0].pfunc = simple_str;
slots[1].slot = Py_tp_doc;
slots[1].pfunc = doc;
slots[2].slot = 0;
slots[2].pfunc = NULL;
spec->slots = slots;

/* create the class */

class = PyType_FromSpec(spec);
if (class == NULL) {
goto finally;
}

/* deallocate the spec (and all contents) */

// (Explicitly ovewrite memory before freeing,
// so bugs show themselves even without the debug allocator's help.)
memset(spec, 0xdd, sizeof(PyType_Spec));
PyMem_Del(spec);
spec = NULL;
memset(name, 0xdd, sizeof(NAME));
PyMem_Del(name);
name = NULL;
memset(doc, 0xdd, sizeof(DOC));
PyMem_Del(doc);
doc = NULL;
memset(slots, 0xdd, 3 * sizeof(PyType_Slot));
PyMem_Del(slots);
slots = NULL;

/* check that everything works */

PyTypeObject *class_tp = (PyTypeObject *)class;
PyHeapTypeObject *class_ht = (PyHeapTypeObject *)class;
assert(strcmp(class_tp->tp_name, "testcapi._Test") == 0);
assert(strcmp(PyUnicode_AsUTF8(class_ht->ht_name), "_Test") == 0);
assert(strcmp(PyUnicode_AsUTF8(class_ht->ht_qualname), "_Test") == 0);
assert(strcmp(class_tp->tp_doc, "a test class") == 0);

// call and check __str__
instance = PyObject_CallNoArgs(class);
if (instance == NULL) {
goto finally;
}
obj = PyObject_Str(instance);
if (obj == NULL) {
goto finally;
}
assert (strcmp(PyUnicode_AsUTF8(obj), "<test>") == 0);
encukou marked this conversation as resolved.
Show resolved Hide resolved
Py_CLEAR(obj);

result = Py_NewRef(Py_None);
finally:
PyMem_Del(spec);
PyMem_Del(name);
PyMem_Del(doc);
PyMem_Del(slots);
Py_XDECREF(class);
Py_XDECREF(instance);
Py_XDECREF(obj);
return result;
}


static PyObject *
test_get_type_qualname(PyObject *self, PyObject *Py_UNUSED(ignored))
{
Expand Down Expand Up @@ -5804,6 +5924,7 @@ static PyMethodDef TestMethods[] = {
{"test_get_statictype_slots", test_get_statictype_slots, METH_NOARGS},
{"test_get_type_name", test_get_type_name, METH_NOARGS},
{"test_get_type_qualname", test_get_type_qualname, METH_NOARGS},
{"test_type_from_ephemeral_spec", test_type_from_ephemeral_spec, METH_NOARGS},
{"get_kwargs", (PyCFunction)(void(*)(void))get_kwargs,
METH_VARARGS|METH_KEYWORDS},
{"getargs_tuple", getargs_tuple, METH_VARARGS},
Expand Down
52 changes: 37 additions & 15 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2778,6 +2778,7 @@ type_new_alloc(type_new_ctx *ctx)

et->ht_name = Py_NewRef(ctx->name);
et->ht_module = NULL;
et->_ht_tpname = NULL;

return type;
}
Expand Down Expand Up @@ -3435,25 +3436,43 @@ PyType_FromModuleAndSpec(PyObject *module, PyType_Spec *spec, PyObject *bases)
goto fail;
}

type = &res->ht_type;
/* The flags must be initialized early, before the GC traverses us */
type->tp_flags = spec->flags | Py_TPFLAGS_HEAPTYPE;

/* Set the type name and qualname */
const char *s = strrchr(spec->name, '.');
if (s == NULL)
if (s == NULL) {
s = spec->name;
else
}
else {
s++;
}

type = &res->ht_type;
/* The flags must be initialized early, before the GC traverses us */
type->tp_flags = spec->flags | Py_TPFLAGS_HEAPTYPE;
res->ht_name = PyUnicode_FromString(s);
if (!res->ht_name)
if (!res->ht_name) {
goto fail;
}
res->ht_qualname = Py_NewRef(res->ht_name);

/* Copy spec->name to a buffer we own.
*
* Unfortunately, we can't use tp_name directly (with some
* flag saying that it should be deallocated with the type),
* because tp_name is public API and may be set independently
* of any such flag.
* So, we use a separate buffer, _ht_tpname, that's always
* deallocated withthe type (if it's non-NULL).
encukou marked this conversation as resolved.
Show resolved Hide resolved
*/
Py_ssize_t name_buf_len = strlen(spec->name) + 1;
res->_ht_tpname = PyMem_Malloc(name_buf_len);
if (res->_ht_tpname == NULL) {
goto fail;
res->ht_qualname = res->ht_name;
Py_INCREF(res->ht_qualname);
type->tp_name = spec->name;
}
memcpy(res->_ht_tpname, spec->name, name_buf_len);
type->tp_name = res->_ht_tpname;
encukou marked this conversation as resolved.
Show resolved Hide resolved

Py_XINCREF(module);
res->ht_module = module;
res->ht_module = Py_XNewRef(module);

/* Adjust for empty tuple bases */
if (!bases) {
Expand Down Expand Up @@ -4082,6 +4101,7 @@ type_dealloc(PyTypeObject *type)
_PyDictKeys_DecRef(et->ht_cached_keys);
}
Py_XDECREF(et->ht_module);
PyMem_Free(et->_ht_tpname);
Py_TYPE(type)->tp_free((PyObject *)type);
}

Expand Down Expand Up @@ -4273,10 +4293,12 @@ type_traverse(PyTypeObject *type, visitproc visit, void *arg)
Py_VISIT(type->tp_base);
Py_VISIT(((PyHeapTypeObject *)type)->ht_module);

/* There's no need to visit type->tp_subclasses or
((PyHeapTypeObject *)type)->ht_slots, because they can't be involved
in cycles; tp_subclasses is a list of weak references,
and slots is a tuple of strings. */
/* There's no need to visit others because they can't be involved
in cycles:
type->tp_subclasses is a list of weak references,
((PyHeapTypeObject *)type)->ht_slots is a tuple of strings,
((PyHeapTypeObject *)type)->ht_*name are strings.
*/

return 0;
}
Expand Down