Skip to content

Commit aa3716f

Browse files
committed
gh-103743: Add PyUnstable_Object_GC_NewWithExtraData (#103743)
1 parent af53046 commit aa3716f

File tree

7 files changed

+140
-1
lines changed

7 files changed

+140
-1
lines changed

Doc/c-api/gcsupport.rst

+13
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,19 @@ rules:
6565
Analogous to :c:func:`PyObject_NewVar` but for container objects with the
6666
:const:`Py_TPFLAGS_HAVE_GC` flag set.
6767
68+
.. c:function:: TYPE* PyUnstable_Object_GC_NewWithExtraData(PyTypeObject *type, size_t extra_size)
69+
70+
Analogous to :c:func:`PyObject_GC_New` but for container objects that
71+
have additional data at the end of the object not managed by Python.
72+
73+
.. warning::
74+
The function is marked as unstable because the final mechanism
75+
for reserving extra data after an instance is not yet decided.
76+
Once :pep:`697` is implemented, the mechanism described there can
77+
be used to reserve the extra data.
78+
79+
.. versionadded:: 3.12
80+
6881
6982
.. c:function:: TYPE* PyObject_GC_Resize(TYPE, PyVarObject *op, Py_ssize_t newsize)
7083

Include/objimpl.h

+4
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ PyAPI_FUNC(PyVarObject *) PyObject_InitVar(PyVarObject *,
131131
PyAPI_FUNC(PyObject *) _PyObject_New(PyTypeObject *);
132132
PyAPI_FUNC(PyVarObject *) _PyObject_NewVar(PyTypeObject *, Py_ssize_t);
133133

134+
#if !defined(Py_LIMITED_API)
135+
PyAPI_FUNC(PyObject *) PyUnstable_Object_GC_NewWithExtraData(PyTypeObject *, size_t);
136+
#endif
137+
134138
#define PyObject_New(type, typeobj) ((type *)_PyObject_New(typeobj))
135139

136140
// Alias to PyObject_New(). In Python 3.8, PyObject_NEW() called directly

Lib/test/test_capi/test_misc.py

+14
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,20 @@ class dictsub(dict): ... # dict subclasses must work
10431043
self.assertEqual(_testcapi.function_get_kw_defaults(some), None)
10441044
self.assertEqual(some.__kwdefaults__, None)
10451045

1046+
def test_unstable_gc_new_with_extra_data(self):
1047+
class Data(_testcapi.ObjExtraData):
1048+
__slots__ = ('x', 'y')
1049+
1050+
d = Data()
1051+
d.x = 10
1052+
d.y = 20
1053+
d.extra = 30
1054+
self.assertEqual(d.x, 10)
1055+
self.assertEqual(d.y, 20)
1056+
self.assertEqual(d.extra, 30)
1057+
del d.extra
1058+
self.assertIsNone(d.extra)
1059+
10461060

10471061
class TestPendingCalls(unittest.TestCase):
10481062

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add ``PyUnstable_Object_GC_NewWithExtraData`` function that can be used to
2+
allocate additional memory after an object for data not managed by Python.

Modules/_testcapimodule.c

+94-1
Original file line numberDiff line numberDiff line change
@@ -3343,7 +3343,7 @@ test_gc_visit_objects_basic(PyObject *Py_UNUSED(self),
33433343
}
33443344
state.target = obj;
33453345
state.found = 0;
3346-
3346+
33473347
PyUnstable_GC_VisitObjects(gc_visit_callback_basic, &state);
33483348
Py_DECREF(obj);
33493349
if (!state.found) {
@@ -3380,6 +3380,94 @@ test_gc_visit_objects_exit_early(PyObject *Py_UNUSED(self),
33803380
Py_RETURN_NONE;
33813381
}
33823382

3383+
typedef struct {
3384+
PyObject_HEAD
3385+
} ObjExtraData;
3386+
3387+
static PyObject *
3388+
obj_extra_data_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
3389+
{
3390+
size_t extra_size = sizeof(PyObject *);
3391+
PyObject *obj = PyUnstable_Object_GC_NewWithExtraData(type, extra_size);
3392+
if (obj == NULL)
3393+
return PyErr_NoMemory();
3394+
memset(obj, '\0', type->tp_basicsize + extra_size);
3395+
PyObject_Init(obj, type);
3396+
PyObject_GC_Track(obj);
3397+
return obj;
3398+
}
3399+
3400+
static PyObject **
3401+
obj_extra_data_get_extra_storage(PyObject *self)
3402+
{
3403+
return (PyObject **)((char *)self + Py_TYPE(self)->tp_basicsize);
3404+
}
3405+
3406+
static PyObject *
3407+
obj_extra_data_get(PyObject *self, void *Py_UNUSED(ignored))
3408+
{
3409+
PyObject **extra_storage = obj_extra_data_get_extra_storage(self);
3410+
PyObject *value = *extra_storage;
3411+
if (!value)
3412+
Py_RETURN_NONE;
3413+
Py_INCREF(value);
3414+
return value;
3415+
}
3416+
3417+
static int
3418+
obj_extra_data_set(PyObject *self, PyObject *newval, void *Py_UNUSED(ignored))
3419+
{
3420+
PyObject **extra_storage = obj_extra_data_get_extra_storage(self);
3421+
Py_CLEAR(*extra_storage);
3422+
if (newval) {
3423+
Py_INCREF(newval);
3424+
*extra_storage = newval;
3425+
}
3426+
return 0;
3427+
}
3428+
3429+
static PyGetSetDef obj_extra_data_getset[] = {
3430+
{"extra", (getter)obj_extra_data_get, (setter)obj_extra_data_set, NULL},
3431+
};
3432+
3433+
static int
3434+
obj_extra_data_traverse(PyObject *self, visitproc visit, void *arg)
3435+
{
3436+
PyObject **extra_storage = obj_extra_data_get_extra_storage(self);
3437+
PyObject *value = *extra_storage;
3438+
Py_VISIT(value);
3439+
return 0;
3440+
}
3441+
3442+
static int
3443+
obj_extra_data_clear(PyObject *self)
3444+
{
3445+
PyObject **extra_storage = obj_extra_data_get_extra_storage(self);
3446+
Py_CLEAR(*extra_storage);
3447+
return 0;
3448+
}
3449+
3450+
static void
3451+
obj_extra_data_dealloc(PyObject *self)
3452+
{
3453+
PyObject_GC_UnTrack(self);
3454+
obj_extra_data_clear(self);
3455+
Py_TYPE(self)->tp_free(self);
3456+
}
3457+
3458+
static PyTypeObject ObjExtraData_Type = {
3459+
PyVarObject_HEAD_INIT(NULL, 0)
3460+
"obj_with_extra_data",
3461+
sizeof(ObjExtraData),
3462+
0,
3463+
.tp_getset = obj_extra_data_getset,
3464+
.tp_dealloc = obj_extra_data_dealloc,
3465+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
3466+
.tp_traverse = (traverseproc)obj_extra_data_traverse,
3467+
.tp_clear = (inquiry)obj_extra_data_clear,
3468+
.tp_new = obj_extra_data_new,
3469+
.tp_free = PyObject_GC_Del,
3470+
};
33833471

33843472
struct atexit_data {
33853473
int called;
@@ -4103,6 +4191,11 @@ PyInit__testcapi(void)
41034191
Py_INCREF(&MethStatic_Type);
41044192
PyModule_AddObject(m, "MethStatic", (PyObject *)&MethStatic_Type);
41054193

4194+
if (PyType_Ready(&ObjExtraData_Type) < 0)
4195+
return NULL;
4196+
Py_INCREF(&ObjExtraData_Type);
4197+
PyModule_AddObject(m, "ObjExtraData", (PyObject *)&ObjExtraData_Type);
4198+
41064199
PyModule_AddObject(m, "CHAR_MAX", PyLong_FromLong(CHAR_MAX));
41074200
PyModule_AddObject(m, "CHAR_MIN", PyLong_FromLong(CHAR_MIN));
41084201
PyModule_AddObject(m, "UCHAR_MAX", PyLong_FromLong(UCHAR_MAX));

Modules/gcmodule.c

+12
Original file line numberDiff line numberDiff line change
@@ -2357,6 +2357,18 @@ _PyObject_GC_NewVar(PyTypeObject *tp, Py_ssize_t nitems)
23572357
return op;
23582358
}
23592359

2360+
PyObject *
2361+
PyUnstable_Object_GC_NewWithExtraData(PyTypeObject *tp, size_t extra_size)
2362+
{
2363+
size_t presize = _PyType_PreHeaderSize(tp);
2364+
PyObject *op = gc_alloc(_PyObject_SIZE(tp) + extra_size, presize);
2365+
if (op == NULL) {
2366+
return NULL;
2367+
}
2368+
_PyObject_Init(op, tp);
2369+
return op;
2370+
}
2371+
23602372
PyVarObject *
23612373
_PyObject_GC_Resize(PyVarObject *op, Py_ssize_t nitems)
23622374
{

Tools/c-analyzer/cpython/ignored.tsv

+1
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,7 @@ Modules/_testcapimodule.c - g_dict_watch_events -
508508
Modules/_testcapimodule.c - g_dict_watchers_installed -
509509
Modules/_testcapimodule.c - g_type_modified_events -
510510
Modules/_testcapimodule.c - g_type_watchers_installed -
511+
Modules/_testcapimodule.c - ObjExtraData_Type -
511512
Modules/_testimportmultiple.c - _barmodule -
512513
Modules/_testimportmultiple.c - _foomodule -
513514
Modules/_testimportmultiple.c - _testimportmultiple -

0 commit comments

Comments
 (0)