gh-103509: PEP 697 -- Limited C API for Extending Opaque Types (GH-103511)

Co-authored-by: Oleg Iarygin <oleg@arhadthedev.net>
Co-authored-by: Erlend E. Aasland <erlend.aasland@protonmail.com>
This commit is contained in:
Petr Viktorin 2023-05-04 09:56:53 +02:00 committed by GitHub
parent 35d273825a
commit cd9a56c2b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 970 additions and 19 deletions

View file

@ -18,6 +18,7 @@
#include "structmember.h" // PyMemberDef
#include <ctype.h>
#include <stddef.h> // ptrdiff_t
/*[clinic input]
class type "PyTypeObject *" "&PyType_Type"
@ -1686,6 +1687,12 @@ PyType_GenericNew(PyTypeObject *type, PyObject *args, PyObject *kwds)
/* Helpers for subtyping */
static inline PyMemberDef *
_PyHeapType_GET_MEMBERS(PyHeapTypeObject* type)
{
return PyObject_GetItemData((PyObject *)type);
}
static int
traverse_slots(PyTypeObject *type, PyObject *self, visitproc visit, void *arg)
{
@ -3873,6 +3880,15 @@ static const PySlot_Offset pyslot_offsets[] = {
#include "typeslots.inc"
};
/* Align up to the nearest multiple of alignof(max_align_t)
* (like _Py_ALIGN_UP, but for a size rather than pointer)
*/
static Py_ssize_t
_align_up(Py_ssize_t size)
{
return (size + ALIGNOF_MAX_ALIGN_T - 1) & ~(ALIGNOF_MAX_ALIGN_T - 1);
}
/* Given a PyType_FromMetaclass `bases` argument (NULL, type, or tuple of
* types), return a tuple of types.
*/
@ -4013,6 +4029,20 @@ _PyType_FromMetaclass_impl(
assert(memb->flags == READONLY);
vectorcalloffset = memb->offset;
}
if (memb->flags & Py_RELATIVE_OFFSET) {
if (spec->basicsize > 0) {
PyErr_SetString(
PyExc_SystemError,
"With Py_RELATIVE_OFFSET, basicsize must be negative.");
goto finally;
}
if (memb->offset < 0 || memb->offset >= -spec->basicsize) {
PyErr_SetString(
PyExc_SystemError,
"Member offset out of range (0..-basicsize)");
goto finally;
}
}
}
break;
case Py_tp_doc:
@ -4154,6 +4184,32 @@ _PyType_FromMetaclass_impl(
// here we just check its work
assert(_PyType_HasFeature(base, Py_TPFLAGS_BASETYPE));
/* Calculate sizes */
Py_ssize_t basicsize = spec->basicsize;
Py_ssize_t type_data_offset = spec->basicsize;
if (basicsize == 0) {
/* Inherit */
basicsize = base->tp_basicsize;
}
else if (basicsize < 0) {
/* Extend */
type_data_offset = _align_up(base->tp_basicsize);
basicsize = type_data_offset + _align_up(-spec->basicsize);
/* Inheriting variable-sized types is limited */
if (base->tp_itemsize
&& !((base->tp_flags | spec->flags) & Py_TPFLAGS_ITEMS_AT_END))
{
PyErr_SetString(
PyExc_SystemError,
"Cannot extend variable-size class without Py_TPFLAGS_ITEMS_AT_END.");
goto finally;
}
}
Py_ssize_t itemsize = spec->itemsize;
/* Allocate the new type
*
* Between here and PyType_Ready, we should limit:
@ -4201,8 +4257,8 @@ _PyType_FromMetaclass_impl(
/* Copy the sizes */
type->tp_basicsize = spec->basicsize;
type->tp_itemsize = spec->itemsize;
type->tp_basicsize = basicsize;
type->tp_itemsize = itemsize;
/* Copy all the ordinary slots */
@ -4219,6 +4275,16 @@ _PyType_FromMetaclass_impl(
size_t len = Py_TYPE(type)->tp_itemsize * nmembers;
memcpy(_PyHeapType_GET_MEMBERS(res), slot->pfunc, len);
type->tp_members = _PyHeapType_GET_MEMBERS(res);
PyMemberDef *memb;
Py_ssize_t i;
for (memb = _PyHeapType_GET_MEMBERS(res), i = nmembers;
i > 0; ++memb, --i)
{
if (memb->flags & Py_RELATIVE_OFFSET) {
memb->flags &= ~Py_RELATIVE_OFFSET;
memb->offset += type_data_offset;
}
}
}
break;
default:
@ -4227,6 +4293,7 @@ _PyType_FromMetaclass_impl(
PySlot_Offset slotoffsets = pyslot_offsets[slot->slot];
short slot_offset = slotoffsets.slot_offset;
if (slotoffsets.subslot_offset == -1) {
/* Set a slot in the main PyTypeObject */
*(void**)((char*)res_start + slot_offset) = slot->pfunc;
}
else {
@ -4461,6 +4528,34 @@ PyType_GetModuleByDef(PyTypeObject *type, PyModuleDef *def)
return NULL;
}
void *
PyObject_GetTypeData(PyObject *obj, PyTypeObject *cls)
{
assert(PyObject_TypeCheck(obj, cls));
return (char *)obj + _align_up(cls->tp_base->tp_basicsize);
}
Py_ssize_t
PyType_GetTypeDataSize(PyTypeObject *cls)
{
ptrdiff_t result = cls->tp_basicsize - _align_up(cls->tp_base->tp_basicsize);
if (result < 0) {
return 0;
}
return result;
}
void *
PyObject_GetItemData(PyObject *obj)
{
if (!PyType_HasFeature(Py_TYPE(obj), Py_TPFLAGS_ITEMS_AT_END)) {
PyErr_Format(PyExc_TypeError,
"type '%s' does not have Py_TPFLAGS_ITEMS_AT_END",
Py_TYPE(obj)->tp_name);
return NULL;
}
return (char *)obj + Py_TYPE(obj)->tp_basicsize;
}
/* Internal API to look for a name through the MRO, bypassing the method cache.
This returns a borrowed reference, and might set an exception.
@ -5158,7 +5253,8 @@ PyTypeObject PyType_Type = {
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_TYPE_SUBCLASS |
Py_TPFLAGS_HAVE_VECTORCALL, /* tp_flags */
Py_TPFLAGS_HAVE_VECTORCALL |
Py_TPFLAGS_ITEMS_AT_END, /* tp_flags */
type_doc, /* tp_doc */
(traverseproc)type_traverse, /* tp_traverse */
(inquiry)type_clear, /* tp_clear */
@ -6572,9 +6668,14 @@ inherit_special(PyTypeObject *type, PyTypeObject *base)
else if (PyType_IsSubtype(base, &PyDict_Type)) {
type->tp_flags |= Py_TPFLAGS_DICT_SUBCLASS;
}
/* Setup some inheritable flags */
if (PyType_HasFeature(base, _Py_TPFLAGS_MATCH_SELF)) {
type->tp_flags |= _Py_TPFLAGS_MATCH_SELF;
}
if (PyType_HasFeature(base, Py_TPFLAGS_ITEMS_AT_END)) {
type->tp_flags |= Py_TPFLAGS_ITEMS_AT_END;
}
}
static int