Skip to content

Commit 47ffc90

Browse files
committed
bpo-40217: Ensure Py_VISIT(Py_TYPE(self)) is always called for PyType_FromSpec types
1 parent 48b069a commit 47ffc90

File tree

1 file changed

+83
-1
lines changed

1 file changed

+83
-1
lines changed

Objects/typeobject.c

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,38 @@ type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
10211021
return obj;
10221022
}
10231023

1024+
PyObject *
1025+
PyType_FromSpec_Alloc(PyTypeObject *type, Py_ssize_t nitems)
1026+
{
1027+
PyObject *obj;
1028+
const size_t size = _Py_SIZE_ROUND_UP(
1029+
_PyObject_VAR_SIZE(type, nitems+1) + sizeof(traverseproc),
1030+
SIZEOF_VOID_P);
1031+
/* note that we need to add one, for the sentinel and space for the
1032+
provided tp-traverse: See bpo-40217 for more details */
1033+
1034+
if (PyType_IS_GC(type))
1035+
obj = _PyObject_GC_Malloc(size);
1036+
else
1037+
obj = (PyObject *)PyObject_MALLOC(size);
1038+
1039+
if (obj == NULL)
1040+
return PyErr_NoMemory();
1041+
1042+
obj = obj;
1043+
1044+
memset(obj, '\0', size);
1045+
1046+
if (type->tp_itemsize == 0)
1047+
(void)PyObject_INIT(obj, type);
1048+
else
1049+
(void) PyObject_INIT_VAR((PyVarObject *)obj, type, nitems);
1050+
1051+
if (PyType_IS_GC(type))
1052+
_PyObject_GC_TRACK(obj);
1053+
return obj;
1054+
}
1055+
10241056
PyObject *
10251057
PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems)
10261058
{
@@ -2846,6 +2878,36 @@ static const short slotoffsets[] = {
28462878
#include "typeslots.inc"
28472879
};
28482880

2881+
static int
2882+
PyType_FromSpec_tp_traverse(PyObject *self, visitproc visit, void *arg)
2883+
{
2884+
PyTypeObject *parent = Py_TYPE(self);
2885+
2886+
// Only a instance of a type that is directly created by
2887+
// PyType_FromSpec (not subclasses) must visit its parent.
2888+
if (parent->tp_traverse == PyType_FromSpec_tp_traverse) {
2889+
Py_VISIT(parent);
2890+
}
2891+
2892+
// Search for the original type that was created using PyType_FromSpec
2893+
PyTypeObject *base;
2894+
base = parent;
2895+
while (base->tp_traverse != PyType_FromSpec_tp_traverse) {
2896+
base = base->tp_base;
2897+
assert(base);
2898+
}
2899+
2900+
// Extract the user defined traverse function that we placed at the end
2901+
// of the type and call it.
2902+
size_t size = Py_SIZE(base);
2903+
size_t _offset = _PyObject_VAR_SIZE(&PyType_Type, size+1);
2904+
traverseproc fun = *(traverseproc*)((char*)base + _offset);
2905+
if (fun == NULL) {
2906+
return 0;
2907+
}
2908+
return fun(self, visit, arg);
2909+
}
2910+
28492911
PyObject *
28502912
PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
28512913
{
@@ -2880,7 +2942,7 @@ PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
28802942
}
28812943
}
28822944

2883-
res = (PyHeapTypeObject*)PyType_GenericAlloc(&PyType_Type, nmembers);
2945+
res = (PyHeapTypeObject*)PyType_FromSpec_Alloc(&PyType_Type, nmembers);
28842946
if (res == NULL)
28852947
return NULL;
28862948
res_start = (char*)res;
@@ -2985,6 +3047,26 @@ PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
29853047
memcpy(PyHeapType_GET_MEMBERS(res), slot->pfunc, len);
29863048
type->tp_members = PyHeapType_GET_MEMBERS(res);
29873049
}
3050+
else if (slot->slot == Py_tp_traverse) {
3051+
3052+
/* Types created by PyType_FromSpec own a strong reference to their
3053+
* type, but this was added in Python 3.8. The tp_traverse function
3054+
* needs to call Py_VISIT on the type but all existing traverse
3055+
* functions cannot be updated (especially the ones from existing user
3056+
* functions) so we need to provide a tp_traverse that manually calls
3057+
* Py_VISIT(Py_TYPE(self)) and then call the provided tp_traverse. In
3058+
* this way, user functions do not need to be updated, preserve
3059+
* backwards compatibility.
3060+
*
3061+
* We store the user-provided traverse function at the end of the type
3062+
* (we have allocated space for it) so we can call it from our
3063+
* PyType_FromSpec_tp_traverse wrapper. */
3064+
3065+
type->tp_traverse = PyType_FromSpec_tp_traverse;
3066+
size_t _offset = _PyObject_VAR_SIZE(&PyType_Type, nmembers+1);
3067+
traverseproc *user_traverse = (traverseproc*)((char*)type + _offset);
3068+
*user_traverse = slot->pfunc;
3069+
}
29883070
else {
29893071
/* Copy other slots directly */
29903072
*(void**)(res_start + slotoffsets[slot->slot]) = slot->pfunc;

0 commit comments

Comments
 (0)