Skip to content

Commit aa3716f

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

7 files changed

Lines changed: 140 additions & 1 deletion

File tree

‎Doc/c-api/gcsupport.rst‎

Lines changed: 13 additions & 0 deletions
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‎

Lines changed: 4 additions & 0 deletions
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
#definePyObject_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‎

Lines changed: 14 additions & 0 deletions
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+
deftest_unstable_gc_new_with_extra_data(self):
1047+
classData(_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+
deld.extra
1058+
self.assertIsNone(d.extra)
1059+
10461060

10471061
classTestPendingCalls(unittest.TestCase):
10481062

Lines changed: 2 additions & 0 deletions
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‎

Lines changed: 94 additions & 1 deletion
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+
typedefstruct {
3384+
PyObject_HEAD
3385+
} ObjExtraData;
3386+
3387+
staticPyObject*
3388+
obj_extra_data_new(PyTypeObject*type, PyObject*args, PyObject*kwds)
3389+
{
3390+
size_textra_size=sizeof(PyObject*);
3391+
PyObject*obj=PyUnstable_Object_GC_NewWithExtraData(type, extra_size);
3392+
if (obj==NULL)
3393+
returnPyErr_NoMemory();
3394+
memset(obj, '\0', type->tp_basicsize+extra_size);
3395+
PyObject_Init(obj, type);
3396+
PyObject_GC_Track(obj);
3397+
returnobj;
3398+
}
3399+
3400+
staticPyObject**
3401+
obj_extra_data_get_extra_storage(PyObject*self)
3402+
{
3403+
return (PyObject**)((char*)self+Py_TYPE(self)->tp_basicsize);
3404+
}
3405+
3406+
staticPyObject*
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+
returnvalue;
3415+
}
3416+
3417+
staticint
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+
return0;
3427+
}
3428+
3429+
staticPyGetSetDefobj_extra_data_getset[] = {
3430+
{"extra", (getter)obj_extra_data_get, (setter)obj_extra_data_set, NULL},
3431+
};
3432+
3433+
staticint
3434+
obj_extra_data_traverse(PyObject*self, visitprocvisit, void*arg)
3435+
{
3436+
PyObject**extra_storage=obj_extra_data_get_extra_storage(self);
3437+
PyObject*value=*extra_storage;
3438+
Py_VISIT(value);
3439+
return0;
3440+
}
3441+
3442+
staticint
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+
return0;
3448+
}
3449+
3450+
staticvoid
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+
staticPyTypeObjectObjExtraData_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
structatexit_data {
33853473
intcalled;
@@ -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+
returnNULL;
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‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2357,6 +2357,18 @@ _PyObject_GC_NewVar(PyTypeObject *tp, Py_ssize_t nitems)
23572357
returnop;
23582358
}
23592359

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

‎Tools/c-analyzer/cpython/ignored.tsv‎

Lines changed: 1 addition & 0 deletions
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)