mirror of https://github.com/python/cpython
gh-89546: Clean up PyType_FromMetaclass (GH-93686)
When changing PyType_FromMetaclass recently (GH-93012, GH-93466, GH-28748) I found a bunch of opportunities to improve the code. Here they are. Fixes: #89546 Automerge-Triggered-By: GH:encukou
This commit is contained in:
parent
5bcf33de0b
commit
3597c12941
|
@ -0,0 +1,4 @@
|
||||||
|
:c:func:`PyType_FromMetaclass` (and other ``PyType_From*`` functions) now
|
||||||
|
check that offsets and the base class's
|
||||||
|
:c:member:`~PyTypeObject.tp_basicsize` fit in the new class's
|
||||||
|
``tp_basicsize``.
|
|
@ -1221,7 +1221,7 @@ static PyType_Spec MinimalMetaclass_spec = {
|
||||||
|
|
||||||
static PyType_Spec MinimalType_spec = {
|
static PyType_Spec MinimalType_spec = {
|
||||||
.name = "_testcapi.MinimalSpecType",
|
.name = "_testcapi.MinimalSpecType",
|
||||||
.basicsize = sizeof(PyObject),
|
.basicsize = 0, // Updated later
|
||||||
.flags = Py_TPFLAGS_DEFAULT,
|
.flags = Py_TPFLAGS_DEFAULT,
|
||||||
.slots = empty_type_slots,
|
.slots = empty_type_slots,
|
||||||
};
|
};
|
||||||
|
@ -1245,6 +1245,7 @@ test_from_spec_metatype_inheritance(PyObject *self, PyObject *Py_UNUSED(ignored)
|
||||||
goto finally;
|
goto finally;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MinimalType_spec.basicsize = (int)(((PyTypeObject*)class)->tp_basicsize);
|
||||||
new = PyType_FromSpecWithBases(&MinimalType_spec, class);
|
new = PyType_FromSpecWithBases(&MinimalType_spec, class);
|
||||||
if (new == NULL) {
|
if (new == NULL) {
|
||||||
goto finally;
|
goto finally;
|
||||||
|
|
|
@ -3398,30 +3398,87 @@ get_bases_tuple(PyObject *bases_in, PyType_Spec *spec)
|
||||||
return PyTuple_Pack(1, bases_in);
|
return PyTuple_Pack(1, bases_in);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
check_basicsize_includes_size_and_offsets(PyTypeObject* type)
|
||||||
|
{
|
||||||
|
if (type->tp_alloc != PyType_GenericAlloc) {
|
||||||
|
// Custom allocators can ignore tp_basicsize
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
Py_ssize_t max = (Py_ssize_t)type->tp_basicsize;
|
||||||
|
|
||||||
|
if (type->tp_base && type->tp_base->tp_basicsize > type->tp_basicsize) {
|
||||||
|
PyErr_Format(PyExc_TypeError,
|
||||||
|
"tp_basicsize for type '%s' (%d) is too small for base '%s' (%d)",
|
||||||
|
type->tp_name, type->tp_basicsize,
|
||||||
|
type->tp_base->tp_name, type->tp_base->tp_basicsize);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (type->tp_weaklistoffset + (Py_ssize_t)sizeof(PyObject*) > max) {
|
||||||
|
PyErr_Format(PyExc_TypeError,
|
||||||
|
"weaklist offset %d is out of bounds for type '%s' (tp_basicsize = %d)",
|
||||||
|
type->tp_weaklistoffset,
|
||||||
|
type->tp_name, type->tp_basicsize);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (type->tp_dictoffset + (Py_ssize_t)sizeof(PyObject*) > max) {
|
||||||
|
PyErr_Format(PyExc_TypeError,
|
||||||
|
"dict offset %d is out of bounds for type '%s' (tp_basicsize = %d)",
|
||||||
|
type->tp_dictoffset,
|
||||||
|
type->tp_name, type->tp_basicsize);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (type->tp_vectorcall_offset + (Py_ssize_t)sizeof(vectorcallfunc*) > max) {
|
||||||
|
PyErr_Format(PyExc_TypeError,
|
||||||
|
"vectorcall offset %d is out of bounds for type '%s' (tp_basicsize = %d)",
|
||||||
|
type->tp_vectorcall_offset,
|
||||||
|
type->tp_name, type->tp_basicsize);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
|
PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
|
||||||
PyType_Spec *spec, PyObject *bases_in)
|
PyType_Spec *spec, PyObject *bases_in)
|
||||||
{
|
{
|
||||||
|
/* Invariant: A non-NULL value in one of these means this function holds
|
||||||
|
* a strong reference or owns allocated memory.
|
||||||
|
* These get decrefed/freed/returned at the end, on both success and error.
|
||||||
|
*/
|
||||||
PyHeapTypeObject *res = NULL;
|
PyHeapTypeObject *res = NULL;
|
||||||
PyObject *modname = NULL;
|
|
||||||
PyTypeObject *type;
|
PyTypeObject *type;
|
||||||
PyObject *bases = NULL;
|
PyObject *bases = NULL;
|
||||||
|
char *tp_doc = NULL;
|
||||||
|
PyObject *ht_name = NULL;
|
||||||
|
char *_ht_tpname = NULL;
|
||||||
|
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
|
/* Prepare slots that need special handling.
|
||||||
|
* Keep in mind that a slot can be given multiple times:
|
||||||
|
* if that would cause trouble (leaks, UB, ...), raise an exception.
|
||||||
|
*/
|
||||||
|
|
||||||
const PyType_Slot *slot;
|
const PyType_Slot *slot;
|
||||||
Py_ssize_t nmembers = 0;
|
Py_ssize_t nmembers = 0;
|
||||||
Py_ssize_t weaklistoffset, dictoffset, vectorcalloffset;
|
Py_ssize_t weaklistoffset, dictoffset, vectorcalloffset;
|
||||||
char *res_start;
|
char *res_start;
|
||||||
short slot_offset, subslot_offset;
|
|
||||||
|
|
||||||
nmembers = weaklistoffset = dictoffset = vectorcalloffset = 0;
|
nmembers = weaklistoffset = dictoffset = vectorcalloffset = 0;
|
||||||
for (slot = spec->slots; slot->slot; slot++) {
|
for (slot = spec->slots; slot->slot; slot++) {
|
||||||
if (slot->slot == Py_tp_members) {
|
if (slot->slot < 0
|
||||||
|
|| (size_t)slot->slot >= Py_ARRAY_LENGTH(pyslot_offsets)) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "invalid slot offset");
|
||||||
|
goto finally;
|
||||||
|
}
|
||||||
|
switch (slot->slot) {
|
||||||
|
case Py_tp_members:
|
||||||
if (nmembers != 0) {
|
if (nmembers != 0) {
|
||||||
PyErr_SetString(
|
PyErr_SetString(
|
||||||
PyExc_SystemError,
|
PyExc_SystemError,
|
||||||
"Multiple Py_tp_members slots are not supported.");
|
"Multiple Py_tp_members slots are not supported.");
|
||||||
return NULL;
|
goto finally;
|
||||||
}
|
}
|
||||||
for (const PyMemberDef *memb = slot->pfunc; memb->name != NULL; memb++) {
|
for (const PyMemberDef *memb = slot->pfunc; memb->name != NULL; memb++) {
|
||||||
nmembers++;
|
nmembers++;
|
||||||
|
@ -3444,15 +3501,70 @@ PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
|
||||||
vectorcalloffset = memb->offset;
|
vectorcalloffset = memb->offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case Py_tp_doc:
|
||||||
|
/* For the docstring slot, which usually points to a static string
|
||||||
|
literal, we need to make a copy */
|
||||||
|
if (tp_doc != NULL) {
|
||||||
|
PyErr_SetString(
|
||||||
|
PyExc_SystemError,
|
||||||
|
"Multiple Py_tp_doc slots are not supported.");
|
||||||
|
goto finally;
|
||||||
|
}
|
||||||
|
if (slot->pfunc == NULL) {
|
||||||
|
PyObject_Free(tp_doc);
|
||||||
|
tp_doc = NULL;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
size_t len = strlen(slot->pfunc)+1;
|
||||||
|
tp_doc = PyObject_Malloc(len);
|
||||||
|
if (tp_doc == NULL) {
|
||||||
|
PyErr_NoMemory();
|
||||||
|
goto finally;
|
||||||
|
}
|
||||||
|
memcpy(tp_doc, slot->pfunc, len);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Prepare the type name and qualname */
|
||||||
|
|
||||||
if (spec->name == NULL) {
|
if (spec->name == NULL) {
|
||||||
PyErr_SetString(PyExc_SystemError,
|
PyErr_SetString(PyExc_SystemError,
|
||||||
"Type spec does not define the name field.");
|
"Type spec does not define the name field.");
|
||||||
goto finally;
|
goto finally;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *s = strrchr(spec->name, '.');
|
||||||
|
if (s == NULL) {
|
||||||
|
s = spec->name;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ht_name = PyUnicode_FromString(s);
|
||||||
|
if (!ht_name) {
|
||||||
|
goto finally;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 with the type (if it's non-NULL).
|
||||||
|
*/
|
||||||
|
Py_ssize_t name_buf_len = strlen(spec->name) + 1;
|
||||||
|
_ht_tpname = PyMem_Malloc(name_buf_len);
|
||||||
|
if (_ht_tpname == NULL) {
|
||||||
|
goto finally;
|
||||||
|
}
|
||||||
|
memcpy(_ht_tpname, spec->name, name_buf_len);
|
||||||
|
|
||||||
/* Get a tuple of bases.
|
/* Get a tuple of bases.
|
||||||
* bases is a strong reference (unlike bases_in).
|
* bases is a strong reference (unlike bases_in).
|
||||||
*/
|
*/
|
||||||
|
@ -3491,7 +3603,13 @@ PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
|
||||||
// here we just check its work
|
// here we just check its work
|
||||||
assert(_PyType_HasFeature(base, Py_TPFLAGS_BASETYPE));
|
assert(_PyType_HasFeature(base, Py_TPFLAGS_BASETYPE));
|
||||||
|
|
||||||
/* Allocate the new type */
|
/* Allocate the new type
|
||||||
|
*
|
||||||
|
* Between here and PyType_Ready, we should limit:
|
||||||
|
* - calls to Python code
|
||||||
|
* - raising exceptions
|
||||||
|
* - memory allocations
|
||||||
|
*/
|
||||||
|
|
||||||
res = (PyHeapTypeObject*)metaclass->tp_alloc(metaclass, nmembers);
|
res = (PyHeapTypeObject*)metaclass->tp_alloc(metaclass, nmembers);
|
||||||
if (res == NULL) {
|
if (res == NULL) {
|
||||||
|
@ -3503,105 +3621,70 @@ PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
|
||||||
/* The flags must be initialized early, before the GC traverses us */
|
/* The flags must be initialized early, before the GC traverses us */
|
||||||
type->tp_flags = spec->flags | Py_TPFLAGS_HEAPTYPE;
|
type->tp_flags = spec->flags | Py_TPFLAGS_HEAPTYPE;
|
||||||
|
|
||||||
/* Set the type name and qualname */
|
|
||||||
const char *s = strrchr(spec->name, '.');
|
|
||||||
if (s == NULL) {
|
|
||||||
s = spec->name;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
s++;
|
|
||||||
}
|
|
||||||
|
|
||||||
res->ht_name = PyUnicode_FromString(s);
|
|
||||||
if (!res->ht_name) {
|
|
||||||
goto finally;
|
|
||||||
}
|
|
||||||
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 with the type (if it's non-NULL).
|
|
||||||
*/
|
|
||||||
Py_ssize_t name_buf_len = strlen(spec->name) + 1;
|
|
||||||
res->_ht_tpname = PyMem_Malloc(name_buf_len);
|
|
||||||
if (res->_ht_tpname == NULL) {
|
|
||||||
goto finally;
|
|
||||||
}
|
|
||||||
type->tp_name = memcpy(res->_ht_tpname, spec->name, name_buf_len);
|
|
||||||
|
|
||||||
res->ht_module = Py_XNewRef(module);
|
res->ht_module = Py_XNewRef(module);
|
||||||
|
|
||||||
/* Initialize essential fields */
|
/* Initialize essential fields */
|
||||||
|
|
||||||
type->tp_as_async = &res->as_async;
|
type->tp_as_async = &res->as_async;
|
||||||
type->tp_as_number = &res->as_number;
|
type->tp_as_number = &res->as_number;
|
||||||
type->tp_as_sequence = &res->as_sequence;
|
type->tp_as_sequence = &res->as_sequence;
|
||||||
type->tp_as_mapping = &res->as_mapping;
|
type->tp_as_mapping = &res->as_mapping;
|
||||||
type->tp_as_buffer = &res->as_buffer;
|
type->tp_as_buffer = &res->as_buffer;
|
||||||
|
|
||||||
/* Set tp_base and tp_bases */
|
/* Set slots we have prepared */
|
||||||
|
|
||||||
type->tp_base = (PyTypeObject *)Py_NewRef(base);
|
type->tp_base = (PyTypeObject *)Py_NewRef(base);
|
||||||
type->tp_bases = bases;
|
type->tp_bases = bases;
|
||||||
bases = NULL; // We give our reference to bases to the type
|
bases = NULL; // We give our reference to bases to the type
|
||||||
|
|
||||||
|
type->tp_doc = tp_doc;
|
||||||
|
tp_doc = NULL; // Give ownership of the allocated memory to the type
|
||||||
|
|
||||||
|
res->ht_qualname = Py_NewRef(ht_name);
|
||||||
|
res->ht_name = ht_name;
|
||||||
|
ht_name = NULL; // Give our reference to to the type
|
||||||
|
|
||||||
|
type->tp_name = _ht_tpname;
|
||||||
|
res->_ht_tpname = _ht_tpname;
|
||||||
|
_ht_tpname = NULL; // Give ownership to to the type
|
||||||
|
|
||||||
/* Copy the sizes */
|
/* Copy the sizes */
|
||||||
|
|
||||||
type->tp_basicsize = spec->basicsize;
|
type->tp_basicsize = spec->basicsize;
|
||||||
type->tp_itemsize = spec->itemsize;
|
type->tp_itemsize = spec->itemsize;
|
||||||
|
|
||||||
|
/* Copy all the ordinary slots */
|
||||||
|
|
||||||
for (slot = spec->slots; slot->slot; slot++) {
|
for (slot = spec->slots; slot->slot; slot++) {
|
||||||
if (slot->slot < 0
|
switch (slot->slot) {
|
||||||
|| (size_t)slot->slot >= Py_ARRAY_LENGTH(pyslot_offsets)) {
|
case Py_tp_base:
|
||||||
PyErr_SetString(PyExc_RuntimeError, "invalid slot offset");
|
case Py_tp_bases:
|
||||||
goto finally;
|
case Py_tp_doc:
|
||||||
}
|
|
||||||
else if (slot->slot == Py_tp_base || slot->slot == Py_tp_bases) {
|
|
||||||
/* Processed above */
|
/* Processed above */
|
||||||
continue;
|
break;
|
||||||
}
|
case Py_tp_members:
|
||||||
else if (slot->slot == Py_tp_doc) {
|
{
|
||||||
/* For the docstring slot, which usually points to a static string
|
/* Move the slots to the heap type itself */
|
||||||
literal, we need to make a copy */
|
size_t len = Py_TYPE(type)->tp_itemsize * nmembers;
|
||||||
if (type->tp_doc != NULL) {
|
memcpy(_PyHeapType_GET_MEMBERS(res), slot->pfunc, len);
|
||||||
PyErr_SetString(
|
type->tp_members = _PyHeapType_GET_MEMBERS(res);
|
||||||
PyExc_SystemError,
|
|
||||||
"Multiple Py_tp_doc slots are not supported.");
|
|
||||||
goto finally;
|
|
||||||
}
|
}
|
||||||
if (slot->pfunc == NULL) {
|
break;
|
||||||
type->tp_doc = NULL;
|
default:
|
||||||
continue;
|
{
|
||||||
}
|
/* Copy other slots directly */
|
||||||
size_t len = strlen(slot->pfunc)+1;
|
PySlot_Offset slotoffsets = pyslot_offsets[slot->slot];
|
||||||
char *tp_doc = PyObject_Malloc(len);
|
short slot_offset = slotoffsets.slot_offset;
|
||||||
if (tp_doc == NULL) {
|
if (slotoffsets.subslot_offset == -1) {
|
||||||
type->tp_doc = NULL;
|
*(void**)((char*)res_start + slot_offset) = slot->pfunc;
|
||||||
PyErr_NoMemory();
|
}
|
||||||
goto finally;
|
else {
|
||||||
}
|
void *procs = *(void**)((char*)res_start + slot_offset);
|
||||||
memcpy(tp_doc, slot->pfunc, len);
|
short subslot_offset = slotoffsets.subslot_offset;
|
||||||
type->tp_doc = tp_doc;
|
*(void**)((char*)procs + subslot_offset) = slot->pfunc;
|
||||||
}
|
}
|
||||||
else if (slot->slot == Py_tp_members) {
|
|
||||||
/* Move the slots to the heap type itself */
|
|
||||||
size_t len = Py_TYPE(type)->tp_itemsize * nmembers;
|
|
||||||
memcpy(_PyHeapType_GET_MEMBERS(res), slot->pfunc, len);
|
|
||||||
type->tp_members = _PyHeapType_GET_MEMBERS(res);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
/* Copy other slots directly */
|
|
||||||
PySlot_Offset slotoffsets = pyslot_offsets[slot->slot];
|
|
||||||
slot_offset = slotoffsets.slot_offset;
|
|
||||||
if (slotoffsets.subslot_offset == -1) {
|
|
||||||
*(void**)((char*)res_start + slot_offset) = slot->pfunc;
|
|
||||||
} else {
|
|
||||||
void *parent_slot = *(void**)((char*)res_start + slot_offset);
|
|
||||||
subslot_offset = slotoffsets.subslot_offset;
|
|
||||||
*(void**)((char*)parent_slot + subslot_offset) = slot->pfunc;
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type->tp_dealloc == NULL) {
|
if (type->tp_dealloc == NULL) {
|
||||||
|
@ -3611,14 +3694,26 @@ PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
|
||||||
type->tp_dealloc = subtype_dealloc;
|
type->tp_dealloc = subtype_dealloc;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vectorcalloffset) {
|
/* Set up offsets */
|
||||||
type->tp_vectorcall_offset = vectorcalloffset;
|
|
||||||
}
|
type->tp_vectorcall_offset = vectorcalloffset;
|
||||||
|
type->tp_weaklistoffset = weaklistoffset;
|
||||||
|
type->tp_dictoffset = dictoffset;
|
||||||
|
|
||||||
|
/* Ready the type (which includes inheritance).
|
||||||
|
*
|
||||||
|
* After this call we should generally only touch up what's
|
||||||
|
* accessible to Python code, like __dict__.
|
||||||
|
*/
|
||||||
|
|
||||||
if (PyType_Ready(type) < 0) {
|
if (PyType_Ready(type) < 0) {
|
||||||
goto finally;
|
goto finally;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!check_basicsize_includes_size_and_offsets(type)) {
|
||||||
|
goto finally;
|
||||||
|
}
|
||||||
|
|
||||||
if (type->tp_flags & Py_TPFLAGS_MANAGED_DICT) {
|
if (type->tp_flags & Py_TPFLAGS_MANAGED_DICT) {
|
||||||
res->ht_cached_keys = _PyDict_NewKeysForClass();
|
res->ht_cached_keys = _PyDict_NewKeysForClass();
|
||||||
}
|
}
|
||||||
|
@ -3636,13 +3731,11 @@ PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (weaklistoffset) {
|
if (weaklistoffset) {
|
||||||
type->tp_weaklistoffset = weaklistoffset;
|
|
||||||
if (PyDict_DelItem((PyObject *)type->tp_dict, &_Py_ID(__weaklistoffset__)) < 0) {
|
if (PyDict_DelItem((PyObject *)type->tp_dict, &_Py_ID(__weaklistoffset__)) < 0) {
|
||||||
goto finally;
|
goto finally;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dictoffset) {
|
if (dictoffset) {
|
||||||
type->tp_dictoffset = dictoffset;
|
|
||||||
if (PyDict_DelItem((PyObject *)type->tp_dict, &_Py_ID(__dictoffset__)) < 0) {
|
if (PyDict_DelItem((PyObject *)type->tp_dict, &_Py_ID(__dictoffset__)) < 0) {
|
||||||
goto finally;
|
goto finally;
|
||||||
}
|
}
|
||||||
|
@ -3656,12 +3749,13 @@ PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
|
||||||
if (r == 0) {
|
if (r == 0) {
|
||||||
s = strrchr(spec->name, '.');
|
s = strrchr(spec->name, '.');
|
||||||
if (s != NULL) {
|
if (s != NULL) {
|
||||||
modname = PyUnicode_FromStringAndSize(
|
PyObject *modname = PyUnicode_FromStringAndSize(
|
||||||
spec->name, (Py_ssize_t)(s - spec->name));
|
spec->name, (Py_ssize_t)(s - spec->name));
|
||||||
if (modname == NULL) {
|
if (modname == NULL) {
|
||||||
goto finally;
|
goto finally;
|
||||||
}
|
}
|
||||||
r = PyDict_SetItem(type->tp_dict, &_Py_ID(__module__), modname);
|
r = PyDict_SetItem(type->tp_dict, &_Py_ID(__module__), modname);
|
||||||
|
Py_DECREF(modname);
|
||||||
if (r != 0) {
|
if (r != 0) {
|
||||||
goto finally;
|
goto finally;
|
||||||
}
|
}
|
||||||
|
@ -3681,7 +3775,9 @@ PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
|
||||||
Py_CLEAR(res);
|
Py_CLEAR(res);
|
||||||
}
|
}
|
||||||
Py_XDECREF(bases);
|
Py_XDECREF(bases);
|
||||||
Py_XDECREF(modname);
|
PyObject_Free(tp_doc);
|
||||||
|
Py_XDECREF(ht_name);
|
||||||
|
PyMem_Free(_ht_tpname);
|
||||||
return (PyObject*)res;
|
return (PyObject*)res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue