diff options
-rw-r--r-- | Doc/c-api/structures.rst | 245 | ||||
-rw-r--r-- | Doc/data/stable_abi.dat | 2 | ||||
-rw-r--r-- | Doc/extending/newtypes.rst | 33 | ||||
-rw-r--r-- | Doc/extending/newtypes_tutorial.rst | 15 | ||||
-rw-r--r-- | Doc/includes/custom2.c | 8 | ||||
-rw-r--r-- | Doc/includes/custom3.c | 4 | ||||
-rw-r--r-- | Doc/includes/custom4.c | 4 | ||||
-rw-r--r-- | Doc/whatsnew/3.12.rst | 31 | ||||
-rw-r--r-- | Include/descrobject.h | 55 | ||||
-rw-r--r-- | Include/structmember.h | 95 | ||||
-rw-r--r-- | Lib/test/test_capi/test_structmembers.py | 91 | ||||
-rw-r--r-- | Misc/NEWS.d/next/C API/2022-11-02-16-51-24.gh-issue-47146.dsYDtI.rst | 5 | ||||
-rw-r--r-- | Misc/stable_abi.toml | 49 | ||||
-rw-r--r-- | Modules/Setup.stdlib.in | 2 | ||||
-rw-r--r-- | Modules/_testcapi/parts.h | 1 | ||||
-rw-r--r-- | Modules/_testcapi/structmember.c | 217 | ||||
-rw-r--r-- | Modules/_testcapimodule.c | 151 | ||||
-rw-r--r-- | PCbuild/_testcapi.vcxproj | 1 | ||||
-rw-r--r-- | PCbuild/_testcapi.vcxproj.filters | 3 |
19 files changed, 667 insertions, 345 deletions
diff --git a/Doc/c-api/structures.rst b/Doc/c-api/structures.rst index 5a20f07..827d624 100644 --- a/Doc/c-api/structures.rst +++ b/Doc/c-api/structures.rst @@ -385,86 +385,67 @@ Accessing attributes of extension types .. c:type:: PyMemberDef Structure which describes an attribute of a type which corresponds to a C - struct member. Its fields are: + struct member. Its fields are, in order: - .. c:member:: const char* PyMemberDef.name + .. c:member:: const char* name - Name of the member + Name of the member. + A NULL value marks the end of a ``PyMemberDef[]`` array. - .. c:member:: int PyMemberDef.type - - The type of the member in the C struct. + The string should be static, no copy is made of it. .. c:member:: Py_ssize_t PyMemberDef.offset The offset in bytes that the member is located on the type’s object struct. - .. c:member:: int PyMemberDef.flags - - Flag bits indicating if the field should be read-only or writable. - - .. c:member:: const char* PyMemberDef.doc - - Points to the contents of the docstring. - - :c:member:`PyMemberDef.type` can be one of many ``T_`` macros corresponding to various C - types. When the member is accessed in Python, it will be converted to the - equivalent Python type. - - =============== ================== - Macro name C type - =============== ================== - T_SHORT short - T_INT int - T_LONG long - T_FLOAT float - T_DOUBLE double - T_STRING const char \* - T_OBJECT PyObject \* - T_OBJECT_EX PyObject \* - T_CHAR char - T_BYTE char - T_UBYTE unsigned char - T_UINT unsigned int - T_USHORT unsigned short - T_ULONG unsigned long - T_BOOL char - T_LONGLONG long long - T_ULONGLONG unsigned long long - T_PYSSIZET Py_ssize_t - =============== ================== - - :c:macro:`T_OBJECT` and :c:macro:`T_OBJECT_EX` differ in that - :c:macro:`T_OBJECT` returns ``None`` if the member is ``NULL`` and - :c:macro:`T_OBJECT_EX` raises an :exc:`AttributeError`. Try to use - :c:macro:`T_OBJECT_EX` over :c:macro:`T_OBJECT` because :c:macro:`T_OBJECT_EX` - handles use of the :keyword:`del` statement on that attribute more correctly - than :c:macro:`T_OBJECT`. - - :c:member:`PyMemberDef.flags` can be ``0`` for write and read access or :c:macro:`READONLY` for - read-only access. Using :c:macro:`T_STRING` for :attr:`type` implies - :c:macro:`READONLY`. :c:macro:`T_STRING` data is interpreted as UTF-8. - Only :c:macro:`T_OBJECT` and :c:macro:`T_OBJECT_EX` - members can be deleted. (They are set to ``NULL``). + .. c:member:: int type + + The type of the member in the C struct. + See :ref:`PyMemberDef-types` for the possible values. + + .. c:member:: int flags + + Zero or more of the :ref:`PyMemberDef-flags`, combined using bitwise OR. + + .. c:member:: const char* doc + + The docstring, or NULL. + The string should be static, no copy is made of it. + Typically, it is defined using :c:macro:`PyDoc_STR`. + + By default (when :c:member:`flags` is ``0``), members allow + both read and write access. + Use the :c:macro:`Py_READONLY` flag for read-only access. + Certain types, like :c:macro:`Py_T_STRING`, imply :c:macro:`Py_READONLY`. + Only :c:macro:`Py_T_OBJECT_EX` (and legacy :c:macro:`T_OBJECT`) members can + be deleted. .. _pymemberdef-offsets: - Heap allocated types (created using :c:func:`PyType_FromSpec` or similar), - ``PyMemberDef`` may contain definitions for the special member - ``__vectorcalloffset__``, corresponding to + For heap-allocated types (created using :c:func:`PyType_FromSpec` or similar), + ``PyMemberDef`` may contain a definition for the special member + ``"__vectorcalloffset__"``, corresponding to :c:member:`~PyTypeObject.tp_vectorcall_offset` in type objects. - These must be defined with ``T_PYSSIZET`` and ``READONLY``, for example:: + These must be defined with ``Py_T_PYSSIZET`` and ``Py_READONLY``, for example:: static PyMemberDef spam_type_members[] = { - {"__vectorcalloffset__", T_PYSSIZET, offsetof(Spam_object, vectorcall), READONLY}, + {"__vectorcalloffset__", Py_T_PYSSIZET, + offsetof(Spam_object, vectorcall), Py_READONLY}, {NULL} /* Sentinel */ }; + (You may need to ``#include <stddef.h>`` for :c:func:`!offsetof`.) + The legacy offsets :c:member:`~PyTypeObject.tp_dictoffset` and - :c:member:`~PyTypeObject.tp_weaklistoffset` are still supported, but extensions are - strongly encouraged to use ``Py_TPFLAGS_MANAGED_DICT`` and - ``Py_TPFLAGS_MANAGED_WEAKREF`` instead. + :c:member:`~PyTypeObject.tp_weaklistoffset` can be defined similarly using + ``"__dictoffset__"`` and ``"__weaklistoffset__"`` members, but extensions + are strongly encouraged to use :const:`Py_TPFLAGS_MANAGED_DICT` and + :const:`Py_TPFLAGS_MANAGED_WEAKREF` instead. + .. versionchanged:: 3.12 + + ``PyMemberDef`` is always available. + Previously, it required including ``"structmember.h"``. .. c:function:: PyObject* PyMember_GetOne(const char *obj_addr, struct PyMemberDef *m) @@ -472,6 +453,10 @@ Accessing attributes of extension types attribute is described by ``PyMemberDef`` *m*. Returns ``NULL`` on error. + .. versionchanged:: 3.12 + + ``PyMember_GetOne`` is always available. + Previously, it required including ``"structmember.h"``. .. c:function:: int PyMember_SetOne(char *obj_addr, struct PyMemberDef *m, PyObject *o) @@ -479,6 +464,144 @@ Accessing attributes of extension types The attribute to set is described by ``PyMemberDef`` *m*. Returns ``0`` if successful and a negative value on failure. + .. versionchanged:: 3.12 + + ``PyMember_SetOne`` is always available. + Previously, it required including ``"structmember.h"``. + +.. _PyMemberDef-flags: + +Member flags +^^^^^^^^^^^^ + +The following flags can be used with :c:member:`PyMemberDef.flags`: + +.. c:macro:: Py_READONLY + + Not writable. + +.. c:macro:: Py_AUDIT_READ + + Emit an ``object.__getattr__`` :ref:`audit event <audit-events>` + before reading. + +.. index:: + single: READ_RESTRICTED + single: WRITE_RESTRICTED + single: RESTRICTED + +.. versionchanged:: 3.10 + + The :const:`!RESTRICTED`, :const:`!READ_RESTRICTED` and + :const:`!WRITE_RESTRICTED` macros available with + ``#include "structmember.h"`` are deprecated. + :const:`!READ_RESTRICTED` and :const:`!RESTRICTED` are equivalent to + :const:`Py_AUDIT_READ`; :const:`!WRITE_RESTRICTED` does nothing. + +.. index:: + single: READONLY + +.. versionchanged:: 3.12 + + The :const:`!READONLY` macro was renamed to :const:`Py_READONLY`. + The :const:`!PY_AUDIT_READ` macro was renamed with the ``Py_`` prefix. + The new names are now always available. + Previously, these required ``#include "structmember.h"``. + The header is still available and it provides the old names. + +.. _PyMemberDef-types: + +Member types +^^^^^^^^^^^^ + +:c:member:`PyMemberDef.type` can be one of the following macros corresponding +to various C types. +When the member is accessed in Python, it will be converted to the +equivalent Python type. +When it is set from Python, it will be converted back to the C type. +If that is not possible, an exception such as :exc:`TypeError` or +:exc:`ValueError` is raised. + +Unless marked (D), attributes defined this way cannot be deleted +using e.g. :keyword:`del` or :py:func:`delattr`. + +================================ ============================= ====================== +Macro name C type Python type +================================ ============================= ====================== +.. c:macro:: Py_T_BYTE :c:expr:`char` :py:class:`int` +.. c:macro:: Py_T_SHORT :c:expr:`short` :py:class:`int` +.. c:macro:: Py_T_INT :c:expr:`int` :py:class:`int` +.. c:macro:: Py_T_LONG :c:expr:`long` :py:class:`int` +.. c:macro:: Py_T_LONGLONG :c:expr:`long long` :py:class:`int` +.. c:macro:: Py_T_UBYTE :c:expr:`unsigned char` :py:class:`int` +.. c:macro:: Py_T_UINT :c:expr:`unsigned int` :py:class:`int` +.. c:macro:: Py_T_USHORT :c:expr:`unsigned short` :py:class:`int` +.. c:macro:: Py_T_ULONG :c:expr:`unsigned long` :py:class:`int` +.. c:macro:: Py_T_ULONGLONG :c:expr:`unsigned long long` :py:class:`int` +.. c:macro:: Py_T_PYSSIZET :c:expr:`Py_ssize_t` :py:class:`int` +.. c:macro:: Py_T_FLOAT :c:expr:`float` :py:class:`float` +.. c:macro:: Py_T_DOUBLE :c:expr:`double` :py:class:`float` +.. c:macro:: Py_T_BOOL :c:expr:`char` :py:class:`bool` + (written as 0 or 1) +.. c:macro:: Py_T_STRING :c:expr:`const char *` (*) :py:class:`str` (RO) +.. c:macro:: Py_T_STRING_INPLACE :c:expr:`const char[]` (*) :py:class:`str` (RO) +.. c:macro:: Py_T_CHAR :c:expr:`char` (0-127) :py:class:`str` (**) +.. c:macro:: Py_T_OBJECT_EX :c:expr:`PyObject *` :py:class:`object` (D) +================================ ============================= ====================== + + (*): Zero-terminated, UTF8-encoded C string. + With :c:macro:`!Py_T_STRING` the C representation is a pointer; + with :c:macro:`!Py_T_STRING_INLINE` the string is stored directly + in the structure. + + (**): String of length 1. Only ASCII is accepted. + + (RO): Implies :c:macro:`Py_READONLY`. + + (D): Can be deleted, in which case the pointer is set to ``NULL``. + Reading a ``NULL`` pointer raises :py:exc:`AttributeError`. + +.. index:: + single: T_BYTE + single: T_SHORT + single: T_INT + single: T_LONG + single: T_LONGLONG + single: T_UBYTE + single: T_USHORT + single: T_UINT + single: T_ULONG + single: T_ULONGULONG + single: T_PYSSIZET + single: T_FLOAT + single: T_DOUBLE + single: T_BOOL + single: T_CHAR + single: T_STRING + single: T_STRING_INPLACE + single: T_OBJECT_EX + single: structmember.h + +.. versionadded:: 3.12 + + In previous versions, the macros were only available with + ``#include "structmember.h"`` and were named without the ``Py_`` prefix + (e.g. as ``T_INT``). + The header is still available and contains the old names, along with + the following deprecated types: + + .. c:macro:: T_OBJECT + + Like ``Py_T_OBJECT_EX``, but ``NULL`` is converted to ``None``. + This results in surprising behavior in Python: deleting the attribute + effectively sets it to ``None``. + + .. c:macro:: T_NONE + + Always ``None``. Must be used with :c:macro:`Py_READONLY`. + +Defining Getters and Setters +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. c:type:: PyGetSetDef diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index db8fc15..53895bb 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -386,6 +386,8 @@ function,PyMem_Malloc,3.2,, function,PyMem_Realloc,3.2,, type,PyMemberDef,3.2,,full-abi var,PyMemberDescr_Type,3.2,, +function,PyMember_GetOne,3.2,, +function,PyMember_SetOne,3.2,, function,PyMemoryView_FromBuffer,3.11,, function,PyMemoryView_FromMemory,3.7,, function,PyMemoryView_FromObject,3.2,, diff --git a/Doc/extending/newtypes.rst b/Doc/extending/newtypes.rst index 3de849a..80a1387 100644 --- a/Doc/extending/newtypes.rst +++ b/Doc/extending/newtypes.rst @@ -286,36 +286,11 @@ be read-only or read-write. The structures in the table are defined as:: For each entry in the table, a :term:`descriptor` will be constructed and added to the type which will be able to extract a value from the instance structure. The -:attr:`type` field should contain one of the type codes defined in the -:file:`structmember.h` header; the value will be used to determine how to +:attr:`type` field should contain a type code like :c:macro:`Py_T_INT` or +:c:macro:`Py_T_DOUBLE`; the value will be used to determine how to convert Python values to and from C values. The :attr:`flags` field is used to -store flags which control how the attribute can be accessed. - -The following flag constants are defined in :file:`structmember.h`; they may be -combined using bitwise-OR. - -+---------------------------+----------------------------------------------+ -| Constant | Meaning | -+===========================+==============================================+ -| :const:`READONLY` | Never writable. | -+---------------------------+----------------------------------------------+ -| :const:`PY_AUDIT_READ` | Emit an ``object.__getattr__`` | -| | :ref:`audit events <audit-events>` before | -| | reading. | -+---------------------------+----------------------------------------------+ - -.. versionchanged:: 3.10 - :const:`RESTRICTED`, :const:`READ_RESTRICTED` and :const:`WRITE_RESTRICTED` - are deprecated. However, :const:`READ_RESTRICTED` is an alias for - :const:`PY_AUDIT_READ`, so fields that specify either :const:`RESTRICTED` - or :const:`READ_RESTRICTED` will also raise an audit event. - -.. index:: - single: READONLY - single: READ_RESTRICTED - single: WRITE_RESTRICTED - single: RESTRICTED - single: PY_AUDIT_READ +store flags which control how the attribute can be accessed: you can set it to +:c:macro:`Py_READONLY` to prevent Python code from setting it. An interesting advantage of using the :c:member:`~PyTypeObject.tp_members` table to build descriptors that are used at runtime is that any attribute defined this way can diff --git a/Doc/extending/newtypes_tutorial.rst b/Doc/extending/newtypes_tutorial.rst index 5d4a3f0..54de3fd 100644 --- a/Doc/extending/newtypes_tutorial.rst +++ b/Doc/extending/newtypes_tutorial.rst @@ -239,13 +239,6 @@ adds these capabilities: This version of the module has a number of changes. -We've added an extra include:: - - #include <structmember.h> - -This include provides declarations that we use to handle attributes, as -described a bit later. - The :class:`Custom` type now has three data attributes in its C struct, *first*, *last*, and *number*. The *first* and *last* variables are Python strings containing first and last names. The *number* attribute is a C integer. @@ -436,11 +429,11 @@ We want to expose our instance variables as attributes. There are a number of ways to do that. The simplest way is to define member definitions:: static PyMemberDef Custom_members[] = { - {"first", T_OBJECT_EX, offsetof(CustomObject, first), 0, + {"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0, "first name"}, - {"last", T_OBJECT_EX, offsetof(CustomObject, last), 0, + {"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0, "last name"}, - {"number", T_INT, offsetof(CustomObject, number), 0, + {"number", Py_T_INT, offsetof(CustomObject, number), 0, "custom number"}, {NULL} /* Sentinel */ }; @@ -609,7 +602,7 @@ above. In this case, we aren't using a closure, so we just pass ``NULL``. We also remove the member definitions for these attributes:: static PyMemberDef Custom_members[] = { - {"number", T_INT, offsetof(CustomObject, number), 0, + {"number", Py_T_INT, offsetof(CustomObject, number), 0, "custom number"}, {NULL} /* Sentinel */ }; diff --git a/Doc/includes/custom2.c b/Doc/includes/custom2.c index aee9e1b..6638b9f 100644 --- a/Doc/includes/custom2.c +++ b/Doc/includes/custom2.c @@ -1,6 +1,6 @@ #define PY_SSIZE_T_CLEAN #include <Python.h> -#include "structmember.h" +#include <stddef.h> /* for offsetof() */ typedef struct { PyObject_HEAD @@ -63,11 +63,11 @@ Custom_init(CustomObject *self, PyObject *args, PyObject *kwds) } static PyMemberDef Custom_members[] = { - {"first", T_OBJECT_EX, offsetof(CustomObject, first), 0, + {"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0, "first name"}, - {"last", T_OBJECT_EX, offsetof(CustomObject, last), 0, + {"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0, "last name"}, - {"number", T_INT, offsetof(CustomObject, number), 0, + {"number", Py_T_INT, offsetof(CustomObject, number), 0, "custom number"}, {NULL} /* Sentinel */ }; diff --git a/Doc/includes/custom3.c b/Doc/includes/custom3.c index 8d88bc2..0faf2bd 100644 --- a/Doc/includes/custom3.c +++ b/Doc/includes/custom3.c @@ -1,6 +1,6 @@ #define PY_SSIZE_T_CLEAN #include <Python.h> -#include "structmember.h" +#include <stddef.h> /* for offsetof() */ typedef struct { PyObject_HEAD @@ -63,7 +63,7 @@ Custom_init(CustomObject *self, PyObject *args, PyObject *kwds) } static PyMemberDef Custom_members[] = { - {"number", T_INT, offsetof(CustomObject, number), 0, + {"number", Py_T_INT, offsetof(CustomObject, number), 0, "custom number"}, {NULL} /* Sentinel */ }; diff --git a/Doc/includes/custom4.c b/Doc/includes/custom4.c index ad240ae..b725bc0 100644 --- a/Doc/includes/custom4.c +++ b/Doc/includes/custom4.c @@ -1,6 +1,6 @@ #define PY_SSIZE_T_CLEAN #include <Python.h> -#include "structmember.h" +#include <stddef.h> /* for offsetof() */ typedef struct { PyObject_HEAD @@ -79,7 +79,7 @@ Custom_init(CustomObject *self, PyObject *args, PyObject *kwds) } static PyMemberDef Custom_members[] = { - {"number", T_INT, offsetof(CustomObject, number), 0, + {"number", Py_T_INT, offsetof(CustomObject, number), 0, "custom number"}, {NULL} /* Sentinel */ }; diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index f8786c1..8e9a4f0 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -849,6 +849,37 @@ Deprecated * Creating :c:data:`immutable types <Py_TPFLAGS_IMMUTABLETYPE>` with mutable bases is deprecated and will be disabled in Python 3.14. +* The ``structmember.h`` header is deprecated, though it continues to be + available and there are no plans to remove it. + + Its contents are now available just by including ``Python.h``, + with a ``Py`` prefix added if it was missing: + + - :c:struct:`PyMemberDef`, :c:func:`PyMember_GetOne` and + :c:func:`PyMember_SetOne` + - Type macros like :c:macro:`Py_T_INT`, :c:macro:`Py_T_DOUBLE`, etc. + (previously ``T_INT``, ``T_DOUBLE``, etc.) + - The flags :c:macro:`Py_READONLY` (previously ``READONLY``) and + :c:macro:`Py_AUDIT_READ` (previously all uppercase) + + Several items are not exposed from ``Python.h``: + + - :c:macro:`T_OBJECT` (use :c:macro:`Py_T_OBJECT_EX`) + - :c:macro:`T_NONE` (previously undocumented, and pretty quirky) + - The macro ``WRITE_RESTRICTED`` which does nothing. + - The macros ``RESTRICTED`` and ``READ_RESTRICTED``, equivalents of + :c:macro:`Py_AUDIT_READ`. + - In some configurations, ``<stddef.h>`` is not included from ``Python.h``. + It should be included manually when using ``offsetof()``. + + The deprecated header continues to provide its original + contents under the original names. + Your old code can stay unchanged, unless the extra include and non-namespaced + macros bother you greatly. + + (Contributed in :gh:`47146` by Petr Viktorin, based on + earlier work by Alexander Belopolsky and Matthias Braun.) + Removed ------- diff --git a/Include/descrobject.h b/Include/descrobject.h index 77f221d..0a420b8 100644 --- a/Include/descrobject.h +++ b/Include/descrobject.h @@ -32,6 +32,61 @@ PyAPI_FUNC(PyObject *) PyDescr_NewGetSet(PyTypeObject *, PyGetSetDef *); PyAPI_FUNC(PyObject *) PyDictProxy_New(PyObject *); PyAPI_FUNC(PyObject *) PyWrapper_New(PyObject *, PyObject *); + +/* An array of PyMemberDef structures defines the name, type and offset + of selected members of a C structure. These can be read by + PyMember_GetOne() and set by PyMember_SetOne() (except if their READONLY + flag is set). The array must be terminated with an entry whose name + pointer is NULL. */ +struct PyMemberDef { + const char *name; + int type; + Py_ssize_t offset; + int flags; + const char *doc; +}; + +// These constants used to be in structmember.h, not prefixed by Py_. +// (structmember.h now has aliases to the new names.) + +/* Types */ +#define Py_T_SHORT 0 +#define Py_T_INT 1 +#define Py_T_LONG 2 +#define Py_T_FLOAT 3 +#define Py_T_DOUBLE 4 +#define Py_T_STRING 5 +#define _Py_T_OBJECT 6 // Deprecated, use Py_T_OBJECT_EX instead +/* the ordering here is weird for binary compatibility */ +#define Py_T_CHAR 7 /* 1-character string */ +#define Py_T_BYTE 8 /* 8-bit signed int */ +/* unsigned variants: */ +#define Py_T_UBYTE 9 +#define Py_T_USHORT 10 +#define Py_T_UINT 11 +#define Py_T_ULONG 12 + +/* Added by Jack: strings contained in the structure */ +#define Py_T_STRING_INPLACE 13 + +/* Added by Lillo: bools contained in the structure (assumed char) */ +#define Py_T_BOOL 14 + +#define Py_T_OBJECT_EX 16 +#define Py_T_LONGLONG 17 +#define Py_T_ULONGLONG 18 + +#define Py_T_PYSSIZET 19 /* Py_ssize_t */ +#define _Py_T_NONE 20 // Deprecated. Value is always None. + +/* Flags */ +#define Py_READONLY 1 +#define Py_AUDIT_READ 2 // Added in 3.10, harmless no-op before that +#define _Py_WRITE_RESTRICTED 4 // Deprecated, no-op. Do not reuse the value. + +PyAPI_FUNC(PyObject *) PyMember_GetOne(const char *, PyMemberDef *); +PyAPI_FUNC(int) PyMember_SetOne(char *, PyMemberDef *, PyObject *); + #ifndef Py_LIMITED_API # define Py_CPYTHON_DESCROBJECT_H # include "cpython/descrobject.h" diff --git a/Include/structmember.h b/Include/structmember.h index 65a777d..f6e8fd8 100644 --- a/Include/structmember.h +++ b/Include/structmember.h @@ -5,69 +5,50 @@ extern "C" { #endif -/* Interface to map C struct members to Python object attributes */ - -#include <stddef.h> /* For offsetof */ - -/* An array of PyMemberDef structures defines the name, type and offset - of selected members of a C structure. These can be read by - PyMember_GetOne() and set by PyMember_SetOne() (except if their READONLY - flag is set). The array must be terminated with an entry whose name - pointer is NULL. */ - -struct PyMemberDef { - const char *name; - int type; - Py_ssize_t offset; - int flags; - const char *doc; -}; +/* Interface to map C struct members to Python object attributes + * + * This header is deprecated: new code should not use stuff from here. + * New definitions are in descrobject.h. + * + * However, there's nothing wrong with old code continuing to use it, + * and there's not much mainenance overhead in maintaining a few aliases. + * So, don't be too eager to convert old code. + * + * It uses names not prefixed with Py_. + * It is also *not* included from Python.h and must be included individually. + */ + +#include <stddef.h> /* For offsetof (not always provided by Python.h) */ /* Types */ -#define T_SHORT 0 -#define T_INT 1 -#define T_LONG 2 -#define T_FLOAT 3 -#define T_DOUBLE 4 -#define T_STRING 5 -#define T_OBJECT 6 -/* XXX the ordering here is weird for binary compatibility */ -#define T_CHAR 7 /* 1-character string */ -#define T_BYTE 8 /* 8-bit signed int */ -/* unsigned variants: */ -#define T_UBYTE 9 -#define T_USHORT 10 -#define T_UINT 11 -#define T_ULONG 12 - -/* Added by Jack: strings contained in the structure */ -#define T_STRING_INPLACE 13 - -/* Added by Lillo: bools contained in the structure (assumed char) */ -#define T_BOOL 14 - -#define T_OBJECT_EX 16 /* Like T_OBJECT, but raises AttributeError - when the value is NULL, instead of - converting to None. */ -#define T_LONGLONG 17 -#define T_ULONGLONG 18 - -#define T_PYSSIZET 19 /* Py_ssize_t */ -#define T_NONE 20 /* Value is always None */ - +#define T_SHORT Py_T_SHORT +#define T_INT Py_T_INT +#define T_LONG Py_T_LONG +#define T_FLOAT Py_T_FLOAT +#define T_DOUBLE Py_T_DOUBLE +#define T_STRING Py_T_STRING +#define T_OBJECT _Py_T_OBJECT +#define T_CHAR Py_T_CHAR +#define T_BYTE Py_T_BYTE +#define T_UBYTE Py_T_UBYTE +#define T_USHORT Py_T_USHORT +#define T_UINT Py_T_UINT +#define T_ULONG Py_T_ULONG +#define T_STRING_INPLACE Py_T_STRING_INPLACE +#define T_BOOL Py_T_BOOL +#define T_OBJECT_EX Py_T_OBJECT_EX +#define T_LONGLONG Py_T_LONGLONG +#define T_ULONGLONG Py_T_ULONGLONG +#define T_PYSSIZET Py_T_PYSSIZET +#define T_NONE _Py_T_NONE /* Flags */ -#define READONLY 1 -#define READ_RESTRICTED 2 -#define PY_WRITE_RESTRICTED 4 +#define READONLY Py_READONLY +#define PY_AUDIT_READ Py_AUDIT_READ +#define READ_RESTRICTED Py_AUDIT_READ +#define PY_WRITE_RESTRICTED _Py_WRITE_RESTRICTED #define RESTRICTED (READ_RESTRICTED | PY_WRITE_RESTRICTED) -#define PY_AUDIT_READ READ_RESTRICTED - -/* Current API, use this */ -PyAPI_FUNC(PyObject *) PyMember_GetOne(const char *, PyMemberDef *); -PyAPI_FUNC(int) PyMember_SetOne(char *, PyMemberDef *, PyObject *); - #ifdef __cplusplus } diff --git a/Lib/test/test_capi/test_structmembers.py b/Lib/test/test_capi/test_structmembers.py index 07d2f62..2cf46b2 100644 --- a/Lib/test/test_capi/test_structmembers.py +++ b/Lib/test/test_capi/test_structmembers.py @@ -4,32 +4,42 @@ from test.support import warnings_helper # Skip this test if the _testcapi module isn't available. import_helper.import_module('_testcapi') -from _testcapi import _test_structmembersType, \ - CHAR_MAX, CHAR_MIN, UCHAR_MAX, \ - SHRT_MAX, SHRT_MIN, USHRT_MAX, \ - INT_MAX, INT_MIN, UINT_MAX, \ - LONG_MAX, LONG_MIN, ULONG_MAX, \ - LLONG_MAX, LLONG_MIN, ULLONG_MAX, \ - PY_SSIZE_T_MAX, PY_SSIZE_T_MIN - -ts=_test_structmembersType(False, # T_BOOL - 1, # T_BYTE - 2, # T_UBYTE - 3, # T_SHORT - 4, # T_USHORT - 5, # T_INT - 6, # T_UINT - 7, # T_LONG - 8, # T_ULONG - 23, # T_PYSSIZET - 9.99999,# T_FLOAT - 10.1010101010, # T_DOUBLE - "hi" # T_STRING_INPLACE - ) - -class ReadWriteTests(unittest.TestCase): +from _testcapi import (_test_structmembersType_OldAPI, + _test_structmembersType_NewAPI, + CHAR_MAX, CHAR_MIN, UCHAR_MAX, + SHRT_MAX, SHRT_MIN, USHRT_MAX, + INT_MAX, INT_MIN, UINT_MAX, + LONG_MAX, LONG_MIN, ULONG_MAX, + LLONG_MAX, LLONG_MIN, ULLONG_MAX, + PY_SSIZE_T_MAX, PY_SSIZE_T_MIN, + ) + +# There are two classes: one using <structmember.h> and another using +# `Py_`-prefixed API. They should behave the same in Python + +def _make_test_object(cls): + return cls(False, # T_BOOL + 1, # T_BYTE + 2, # T_UBYTE + 3, # T_SHORT + 4, # T_USHORT + 5, # T_INT + 6, # T_UINT + 7, # T_LONG + 8, # T_ULONG + 23, # T_PYSSIZET + 9.99999,# T_FLOAT + 10.1010101010, # T_DOUBLE + "hi", # T_STRING_INPLACE + ) + + +class ReadWriteTests: + def setUp(self): + self.ts = _make_test_object(self.cls) def test_bool(self): + ts = self.ts ts.T_BOOL = True self.assertEqual(ts.T_BOOL, True) ts.T_BOOL = False @@ -37,6 +47,7 @@ class ReadWriteTests(unittest.TestCase): self.assertRaises(TypeError, setattr, ts, 'T_BOOL', 1) def test_byte(self): + ts = self.ts ts.T_BYTE = CHAR_MAX self.assertEqual(ts.T_BYTE, CHAR_MAX) ts.T_BYTE = CHAR_MIN @@ -45,6 +56,7 @@ class ReadWriteTests(unittest.TestCase): self.assertEqual(ts.T_UBYTE, UCHAR_MAX) def test_short(self): + ts = self.ts ts.T_SHORT = SHRT_MAX self.assertEqual(ts.T_SHORT, SHRT_MAX) ts.T_SHORT = SHRT_MIN @@ -53,6 +65,7 @@ class ReadWriteTests(unittest.TestCase): self.assertEqual(ts.T_USHORT, USHRT_MAX) def test_int(self): + ts = self.ts ts.T_INT = INT_MAX self.assertEqual(ts.T_INT, INT_MAX) ts.T_INT = INT_MIN @@ -61,6 +74,7 @@ class ReadWriteTests(unittest.TestCase): self.assertEqual(ts.T_UINT, UINT_MAX) def test_long(self): + ts = self.ts ts.T_LONG = LONG_MAX self.assertEqual(ts.T_LONG, LONG_MAX) ts.T_LONG = LONG_MIN @@ -69,13 +83,17 @@ class ReadWriteTests(unittest.TestCase): self.assertEqual(ts.T_ULONG, ULONG_MAX) def test_py_ssize_t(self): + ts = self.ts ts.T_PYSSIZET = PY_SSIZE_T_MAX self.assertEqual(ts.T_PYSSIZET, PY_SSIZE_T_MAX) ts.T_PYSSIZET = PY_SSIZE_T_MIN self.assertEqual(ts.T_PYSSIZET, PY_SSIZE_T_MIN) - @unittest.skipUnless(hasattr(ts, "T_LONGLONG"), "long long not present") def test_longlong(self): + ts = self.ts + if not hasattr(ts, "T_LONGLONG"): + self.skipTest("long long not present") + ts.T_LONGLONG = LLONG_MAX self.assertEqual(ts.T_LONGLONG, LLONG_MAX) ts.T_LONGLONG = LLONG_MIN @@ -91,6 +109,7 @@ class ReadWriteTests(unittest.TestCase): self.assertEqual(ts.T_ULONGLONG, 4) def test_bad_assignments(self): + ts = self.ts integer_attributes = [ 'T_BOOL', 'T_BYTE', 'T_UBYTE', @@ -109,37 +128,57 @@ class ReadWriteTests(unittest.TestCase): self.assertRaises(TypeError, setattr, ts, attr, nonint) def test_inplace_string(self): + ts = self.ts self.assertEqual(ts.T_STRING_INPLACE, "hi") self.assertRaises(TypeError, setattr, ts, "T_STRING_INPLACE", "s") self.assertRaises(TypeError, delattr, ts, "T_STRING_INPLACE") +class ReadWriteTests_OldAPI(ReadWriteTests, unittest.TestCase): + cls = _test_structmembersType_OldAPI + +class ReadWriteTests_NewAPI(ReadWriteTests, unittest.TestCase): + cls = _test_structmembersType_NewAPI -class TestWarnings(unittest.TestCase): +class TestWarnings: + def setUp(self): + self.ts = _make_test_object(self.cls) def test_byte_max(self): + ts = self.ts with warnings_helper.check_warnings(('', RuntimeWarning)): ts.T_BYTE = CHAR_MAX+1 def test_byte_min(self): + ts = self.ts with warnings_helper.check_warnings(('', RuntimeWarning)): ts.T_BYTE = CHAR_MIN-1 def test_ubyte_max(self): + ts = self.ts with warnings_helper.check_warnings(('', RuntimeWarning)): ts.T_UBYTE = UCHAR_MAX+1 def test_short_max(self): + ts = self.ts with warnings_helper.check_warnings(('', RuntimeWarning)): ts.T_SHORT = SHRT_MAX+1 def test_short_min(self): + ts = self.ts with warnings_helper.check_warnings(('', RuntimeWarning)): ts.T_SHORT = SHRT_MIN-1 def test_ushort_max(self): + ts = self.ts with warnings_helper.check_warnings(('', RuntimeWarning)): ts.T_USHORT = USHRT_MAX+1 +class TestWarnings_OldAPI(TestWarnings, unittest.TestCase): + cls = _test_structmembersType_OldAPI + +class TestWarnings_NewAPI(TestWarnings, unittest.TestCase): + cls = _test_structmembersType_NewAPI + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/C API/2022-11-02-16-51-24.gh-issue-47146.dsYDtI.rst b/Misc/NEWS.d/next/C API/2022-11-02-16-51-24.gh-issue-47146.dsYDtI.rst new file mode 100644 index 0000000..0f41942 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2022-11-02-16-51-24.gh-issue-47146.dsYDtI.rst @@ -0,0 +1,5 @@ +The ``structmember.h`` header is deprecated. Its non-deprecated contents are +now available just by including ``Python.h``, with a ``Py_`` prefix added if +it was missing. (Deprecated contents are :c:macro:`T_OBJECT`, +:c:macro:`T_NONE`, and no-op flags.) Patch by Petr Viktorin, based on +earlier work by Alexander Belopolsky and Matthias Braun. diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 0ba0f51..aa12bcc 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -94,7 +94,7 @@ added = '3.2' struct_abi_kind = 'full-abi' [struct.PyMemberDef] - added = '3.2' + added = '3.2' # Before 3.12, PyMemberDef required #include "structmember.h" struct_abi_kind = 'full-abi' [struct.PyGetSetDef] added = '3.2' @@ -1777,11 +1777,9 @@ added = '3.2' abi_only = true [function.PyMember_GetOne] - added = '3.2' - abi_only = true + added = '3.2' # Before 3.12, available in "structmember.h" [function.PyMember_SetOne] - added = '3.2' - abi_only = true + added = '3.2' # Before 3.12, available in "structmember.h" # TLS api is deprecated; superseded by TSS API @@ -2303,3 +2301,44 @@ added = '3.12' [typedef.releasebufferproc] added = '3.12' + +[const.Py_T_BYTE] + added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix +[const.Py_T_SHORT] + added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix +[const.Py_T_INT] + added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix +[const.Py_T_LONG] + added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix +[const.Py_T_LONGLONG] + added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix +[const.Py_T_UBYTE] + added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix +[const.Py_T_UINT] + added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix +[const.Py_T_USHORT] + added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix +[const.Py_T_ULONG] + added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix +[const.Py_T_ULONGLONG] + added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix +[const.Py_T_PYSSIZET] + added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix +[const.Py_T_FLOAT] + added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix +[const.Py_T_DOUBLE] + added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix +[const.Py_T_BOOL] + added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix +[const.Py_T_STRING] + added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix +[const.Py_T_STRING_INPLACE] + added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix +[const.Py_T_CHAR] + added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix +[const.Py_T_OBJECT_EX] + added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix +[const.Py_READONLY] + added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix +[const.Py_AUDIT_READ] + added = '3.12' # Before 3.12, available in "structmember.h" diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 7307d37..d64752e 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -169,7 +169,7 @@ @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c -@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/unicode.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c +@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/unicode.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/structmember.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c # Some testing modules MUST be built as shared libraries. diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h index e25314a..7ba3c4e 100644 --- a/Modules/_testcapi/parts.h +++ b/Modules/_testcapi/parts.h @@ -35,6 +35,7 @@ int _PyTestCapi_Init_Mem(PyObject *module); int _PyTestCapi_Init_Watchers(PyObject *module); int _PyTestCapi_Init_Long(PyObject *module); int _PyTestCapi_Init_Float(PyObject *module); +int _PyTestCapi_Init_Structmember(PyObject *module); #ifdef LIMITED_API_AVAILABLE int _PyTestCapi_Init_VectorcallLimited(PyObject *module); diff --git a/Modules/_testcapi/structmember.c b/Modules/_testcapi/structmember.c new file mode 100644 index 0000000..0fb872a --- /dev/null +++ b/Modules/_testcapi/structmember.c @@ -0,0 +1,217 @@ +#define PY_SSIZE_T_CLEAN +#include "parts.h" +#include <stddef.h> // for offsetof() + + +// This defines two classes that contain all the simple member types, one +// using "new" Py_-prefixed API, and the other using "old" <structmember.h>. +// They should behave identically in Python. + +typedef struct { + char bool_member; + char byte_member; + unsigned char ubyte_member; + short short_member; + unsigned short ushort_member; + int int_member; + unsigned int uint_member; + long long_member; + unsigned long ulong_member; + Py_ssize_t pyssizet_member; + float float_member; + double double_member; + char inplace_member[6]; + long long longlong_member; + unsigned long long ulonglong_member; +} all_structmembers; + +typedef struct { + PyObject_HEAD + all_structmembers structmembers; +} test_structmembers; + + +static struct PyMemberDef test_members_newapi[] = { + {"T_BOOL", Py_T_BOOL, offsetof(test_structmembers, structmembers.bool_member), 0, NULL}, + {"T_BYTE", Py_T_BYTE, offsetof(test_structmembers, structmembers.byte_member), 0, NULL}, + {"T_UBYTE", Py_T_UBYTE, offsetof(test_structmembers, structmembers.ubyte_member), 0, NULL}, + {"T_SHORT", Py_T_SHORT, offsetof(test_structmembers, structmembers.short_member), 0, NULL}, + {"T_USHORT", Py_T_USHORT, offsetof(test_structmembers, structmembers.ushort_member), 0, NULL}, + {"T_INT", Py_T_INT, offsetof(test_structmembers, structmembers.int_member), 0, NULL}, + {"T_UINT", Py_T_UINT, offsetof(test_structmembers, structmembers.uint_member), 0, NULL}, + {"T_LONG", Py_T_LONG, offsetof(test_structmembers, structmembers.long_member), 0, NULL}, + {"T_ULONG", Py_T_ULONG, offsetof(test_structmembers, structmembers.ulong_member), 0, NULL}, + {"T_PYSSIZET", Py_T_PYSSIZET, offsetof(test_structmembers, structmembers.pyssizet_member), 0, NULL}, + {"T_FLOAT", Py_T_FLOAT, offsetof(test_structmembers, structmembers.float_member), 0, NULL}, + {"T_DOUBLE", Py_T_DOUBLE, offsetof(test_structmembers, structmembers.double_member), 0, NULL}, + {"T_STRING_INPLACE", Py_T_STRING_INPLACE, offsetof(test_structmembers, structmembers.inplace_member), 0, NULL}, + {"T_LONGLONG", Py_T_LONGLONG, offsetof(test_structmembers, structmembers.longlong_member), 0, NULL}, + {"T_ULONGLONG", Py_T_ULONGLONG, offsetof(test_structmembers, structmembers.ulonglong_member), 0, NULL}, + {NULL} +}; + +static PyObject * +test_structmembers_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + static char *keywords[] = { + "T_BOOL", "T_BYTE", "T_UBYTE", "T_SHORT", "T_USHORT", + "T_INT", "T_UINT", "T_LONG", "T_ULONG", "T_PYSSIZET", + "T_FLOAT", "T_DOUBLE", "T_STRING_INPLACE", + "T_LONGLONG", "T_ULONGLONG", + NULL}; + static const char fmt[] = "|bbBhHiIlknfds#LK"; + test_structmembers *ob; + const char *s = NULL; + Py_ssize_t string_len = 0; + ob = PyObject_New(test_structmembers, type); + if (ob == NULL) { + return NULL; + } + memset(&ob->structmembers, 0, sizeof(all_structmembers)); + if (!PyArg_ParseTupleAndKeywords(args, kwargs, fmt, keywords, + &ob->structmembers.bool_member, + &ob->structmembers.byte_member, + &ob->structmembers.ubyte_member, + &ob->structmembers.short_member, + &ob->structmembers.ushort_member, + &ob->structmembers.int_member, + &ob->structmembers.uint_member, + &ob->structmembers.long_member, + &ob->structmembers.ulong_member, + &ob->structmembers.pyssizet_member, + &ob->structmembers.float_member, + &ob->structmembers.double_member, + &s, &string_len, + &ob->structmembers.longlong_member, + &ob->structmembers.ulonglong_member)) + { + Py_DECREF(ob); + return NULL; + } + if (s != NULL) { + if (string_len > 5) { + Py_DECREF(ob); + PyErr_SetString(PyExc_ValueError, "string too long"); + return NULL; + } + strcpy(ob->structmembers.inplace_member, s); + } + else { + strcpy(ob->structmembers.inplace_member, ""); + } + return (PyObject *)ob; +} + +static PyType_Slot test_structmembers_slots[] = { + {Py_tp_new, test_structmembers_new}, + {Py_tp_members, test_members_newapi}, + {0}, +}; + +static PyType_Spec test_structmembers_spec = { + .name = "_testcapi._test_structmembersType_NewAPI", + .flags = Py_TPFLAGS_DEFAULT, + .basicsize = sizeof(test_structmembers), + .slots = test_structmembers_slots, +}; + +#include <structmember.h> + +static struct PyMemberDef test_members[] = { + {"T_BOOL", T_BOOL, offsetof(test_structmembers, structmembers.bool_member), 0, NULL}, + {"T_BYTE", T_BYTE, offsetof(test_structmembers, structmembers.byte_member), 0, NULL}, + {"T_UBYTE", T_UBYTE, offsetof(test_structmembers, structmembers.ubyte_member), 0, NULL}, + {"T_SHORT", T_SHORT, offsetof(test_structmembers, structmembers.short_member), 0, NULL}, + {"T_USHORT", T_USHORT, offsetof(test_structmembers, structmembers.ushort_member), 0, NULL}, + {"T_INT", T_INT, offsetof(test_structmembers, structmembers.int_member), 0, NULL}, + {"T_UINT", T_UINT, offsetof(test_structmembers, structmembers.uint_member), 0, NULL}, + {"T_LONG", T_LONG, offsetof(test_structmembers, structmembers.long_member), 0, NULL}, + {"T_ULONG", T_ULONG, offsetof(test_structmembers, structmembers.ulong_member), 0, NULL}, + {"T_PYSSIZET", T_PYSSIZET, offsetof(test_structmembers, structmembers.pyssizet_member), 0, NULL}, + {"T_FLOAT", T_FLOAT, offsetof(test_structmembers, structmembers.float_member), 0, NULL}, + {"T_DOUBLE", T_DOUBLE, offsetof(test_structmembers, structmembers.double_member), 0, NULL}, + {"T_STRING_INPLACE", T_STRING_INPLACE, offsetof(test_structmembers, structmembers.inplace_member), 0, NULL}, + {"T_LONGLONG", T_LONGLONG, offsetof(test_structmembers, structmembers.longlong_member), 0, NULL}, + {"T_ULONGLONG", T_ULONGLONG, offsetof(test_structmembers, structmembers.ulonglong_member), 0, NULL}, + {NULL} +}; + + +static void +test_structmembers_free(PyObject *ob) +{ + PyObject_Free(ob); +} + +/* Designated initializers would work too, but this does test the *old* API */ +static PyTypeObject test_structmembersType_OldAPI= { + PyVarObject_HEAD_INIT(NULL, 0) + "test_structmembersType_OldAPI", + sizeof(test_structmembers), /* tp_basicsize */ + 0, /* tp_itemsize */ + test_structmembers_free, /* destructor tp_dealloc */ + 0, /* tp_vectorcall_offset */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_as_async */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + PyObject_GenericSetAttr, /* tp_setattro */ + 0, /* tp_as_buffer */ + 0, /* tp_flags */ + "Type containing all structmember types", + 0, /* traverseproc tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + test_members, /* tp_members */ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + test_structmembers_new, /* tp_new */ +}; + + +int +_PyTestCapi_Init_Structmember(PyObject *m) +{ + int res; + res = PyType_Ready(&test_structmembersType_OldAPI); + if (res < 0) { + return -1; + } + res = PyModule_AddObject( + m, + "_test_structmembersType_OldAPI", + (PyObject *)&test_structmembersType_OldAPI); + if (res < 0) { + return -1; + } + + PyObject *test_structmembersType_NewAPI = PyType_FromModuleAndSpec( + m, &test_structmembers_spec, NULL); + if (!test_structmembersType_NewAPI) { + return -1; + } + res = PyModule_AddType(m, (PyTypeObject*)test_structmembersType_NewAPI); + Py_DECREF(test_structmembersType_NewAPI); + if (res < 0) { + return -1; + } + + return 0; +} diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 9dd09f6..83eef73 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -21,7 +21,7 @@ #include "Python.h" #include "marshal.h" // PyMarshal_WriteLongToFile -#include "structmember.h" // PyMemberDef +#include "structmember.h" // for offsetof(), T_OBJECT #include <float.h> // FLT_MAX #include <signal.h> @@ -3371,147 +3371,6 @@ static PyMethodDef TestMethods[] = { {NULL, NULL} /* sentinel */ }; -typedef struct { - char bool_member; - char byte_member; - unsigned char ubyte_member; - short short_member; - unsigned short ushort_member; - int int_member; - unsigned int uint_member; - long long_member; - unsigned long ulong_member; - Py_ssize_t pyssizet_member; - float float_member; - double double_member; - char inplace_member[6]; - long long longlong_member; - unsigned long long ulonglong_member; -} all_structmembers; - -typedef struct { - PyObject_HEAD - all_structmembers structmembers; -} test_structmembers; - -static struct PyMemberDef test_members[] = { - {"T_BOOL", T_BOOL, offsetof(test_structmembers, structmembers.bool_member), 0, NULL}, - {"T_BYTE", T_BYTE, offsetof(test_structmembers, structmembers.byte_member), 0, NULL}, - {"T_UBYTE", T_UBYTE, offsetof(test_structmembers, structmembers.ubyte_member), 0, NULL}, - {"T_SHORT", T_SHORT, offsetof(test_structmembers, structmembers.short_member), 0, NULL}, - {"T_USHORT", T_USHORT, offsetof(test_structmembers, structmembers.ushort_member), 0, NULL}, - {"T_INT", T_INT, offsetof(test_structmembers, structmembers.int_member), 0, NULL}, - {"T_UINT", T_UINT, offsetof(test_structmembers, structmembers.uint_member), 0, NULL}, - {"T_LONG", T_LONG, offsetof(test_structmembers, structmembers.long_member), 0, NULL}, - {"T_ULONG", T_ULONG, offsetof(test_structmembers, structmembers.ulong_member), 0, NULL}, - {"T_PYSSIZET", T_PYSSIZET, offsetof(test_structmembers, structmembers.pyssizet_member), 0, NULL}, - {"T_FLOAT", T_FLOAT, offsetof(test_structmembers, structmembers.float_member), 0, NULL}, - {"T_DOUBLE", T_DOUBLE, offsetof(test_structmembers, structmembers.double_member), 0, NULL}, - {"T_STRING_INPLACE", T_STRING_INPLACE, offsetof(test_structmembers, structmembers.inplace_member), 0, NULL}, - {"T_LONGLONG", T_LONGLONG, offsetof(test_structmembers, structmembers.longlong_member), 0, NULL}, - {"T_ULONGLONG", T_ULONGLONG, offsetof(test_structmembers, structmembers.ulonglong_member), 0, NULL}, - {NULL} -}; - - -static PyObject * -test_structmembers_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) -{ - static char *keywords[] = { - "T_BOOL", "T_BYTE", "T_UBYTE", "T_SHORT", "T_USHORT", - "T_INT", "T_UINT", "T_LONG", "T_ULONG", "T_PYSSIZET", - "T_FLOAT", "T_DOUBLE", "T_STRING_INPLACE", - "T_LONGLONG", "T_ULONGLONG", - NULL}; - static const char fmt[] = "|bbBhHiIlknfds#LK"; - test_structmembers *ob; - const char *s = NULL; - Py_ssize_t string_len = 0; - ob = PyObject_New(test_structmembers, type); - if (ob == NULL) - return NULL; - memset(&ob->structmembers, 0, sizeof(all_structmembers)); - if (!PyArg_ParseTupleAndKeywords(args, kwargs, fmt, keywords, - &ob->structmembers.bool_member, - &ob->structmembers.byte_member, - &ob->structmembers.ubyte_member, - &ob->structmembers.short_member, - &ob->structmembers.ushort_member, - &ob->structmembers.int_member, - &ob->structmembers.uint_member, - &ob->structmembers.long_member, - &ob->structmembers.ulong_member, - &ob->structmembers.pyssizet_member, - &ob->structmembers.float_member, - &ob->structmembers.double_member, - &s, &string_len - , &ob->structmembers.longlong_member, - &ob->structmembers.ulonglong_member - )) { - Py_DECREF(ob); - return NULL; - } - if (s != NULL) { - if (string_len > 5) { - Py_DECREF(ob); - PyErr_SetString(PyExc_ValueError, "string too long"); - return NULL; - } - strcpy(ob->structmembers.inplace_member, s); - } - else { - strcpy(ob->structmembers.inplace_member, ""); - } - return (PyObject *)ob; -} - -static void -test_structmembers_free(PyObject *ob) -{ - PyObject_Free(ob); -} - -static PyTypeObject test_structmembersType = { - PyVarObject_HEAD_INIT(NULL, 0) - "test_structmembersType", - sizeof(test_structmembers), /* tp_basicsize */ - 0, /* tp_itemsize */ - test_structmembers_free, /* destructor tp_dealloc */ - 0, /* tp_vectorcall_offset */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_as_async */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - PyObject_GenericGetAttr, /* tp_getattro */ - PyObject_GenericSetAttr, /* tp_setattro */ - 0, /* tp_as_buffer */ - 0, /* tp_flags */ - "Type containing all structmember types", - 0, /* traverseproc tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - 0, /* tp_methods */ - test_members, /* tp_members */ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - test_structmembers_new, /* tp_new */ -}; - typedef struct { PyObject_HEAD @@ -4064,11 +3923,6 @@ PyInit__testcapi(void) Py_SET_TYPE(&_HashInheritanceTester_Type, &PyType_Type); - Py_SET_TYPE(&test_structmembersType, &PyType_Type); - Py_INCREF(&test_structmembersType); - /* don't use a name starting with "test", since we don't want - test_capi to automatically call this */ - PyModule_AddObject(m, "_test_structmembersType", (PyObject *)&test_structmembersType); if (PyType_Ready(&matmulType) < 0) return NULL; Py_INCREF(&matmulType); @@ -4197,6 +4051,9 @@ PyInit__testcapi(void) if (_PyTestCapi_Init_Float(m) < 0) { return NULL; } + if (_PyTestCapi_Init_Structmember(m) < 0) { + return NULL; + } #ifndef LIMITED_API_AVAILABLE PyModule_AddObjectRef(m, "LIMITED_API_AVAILABLE", Py_False); diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj index d91cdfe..58bf4e1 100644 --- a/PCbuild/_testcapi.vcxproj +++ b/PCbuild/_testcapi.vcxproj @@ -106,6 +106,7 @@ <ClCompile Include="..\Modules\_testcapi\watchers.c" /> <ClCompile Include="..\Modules\_testcapi\float.c" /> <ClCompile Include="..\Modules\_testcapi\long.c" /> + <ClCompile Include="..\Modules\_testcapi\structmember.c" /> </ItemGroup> <ItemGroup> <ResourceCompile Include="..\PC\python_nt.rc" /> diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters index 1b112b1..101c532 100644 --- a/PCbuild/_testcapi.vcxproj.filters +++ b/PCbuild/_testcapi.vcxproj.filters @@ -48,6 +48,9 @@ <ClCompile Include="..\Modules\_testcapi\long.c"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="..\Modules\_testcapi\structmember.c"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ResourceCompile Include="..\PC\python_nt.rc"> |