From b83e7a3c89900fe7827382a917b8995488a44ae1 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 15 Apr 2025 13:44:30 +0300 Subject: [PATCH 1/6] gh-117031: Add support for arbitrary C integer types for PyMemberDef.type Py_T_INTEGER(type) represents arbitrary C integer type. --- Doc/c-api/hash.rst | 2 +- Doc/c-api/structures.rst | 18 ++++-- Doc/conf.py | 2 + Doc/whatsnew/3.14.rst | 4 ++ Include/descrobject.h | 2 + Lib/test/test_capi/test_structmembers.py | 61 ++++++++++++++++--- ...-03-19-21-13-20.gh-issue-117031.0s3Ruq.rst | 2 + Modules/_multiprocessing/multiprocessing.h | 11 +--- Modules/_testcapi/structmember.c | 41 +++++++++++++ Modules/_testcapimodule.c | 9 +++ Modules/selectmodule.c | 47 +++----------- Python/structmember.c | 30 +++++++++ 12 files changed, 169 insertions(+), 60 deletions(-) create mode 100644 Misc/NEWS.d/next/C_API/2024-03-19-21-13-20.gh-issue-117031.0s3Ruq.rst diff --git a/Doc/c-api/hash.rst b/Doc/c-api/hash.rst index 00f8cb887dc7eb..7aff2c1a0c9533 100644 --- a/Doc/c-api/hash.rst +++ b/Doc/c-api/hash.rst @@ -83,7 +83,7 @@ See also the :c:member:`PyTypeObject.tp_hash` member and :ref:`numeric-hash`. .. c:function:: Py_hash_t Py_HashPointer(const void *ptr) Hash a pointer value: process the pointer value as an integer (cast it to - ``uintptr_t`` internally). The pointer is not dereferenced. + :c:expr:`uintptr_t` internally). The pointer is not dereferenced. The function cannot fail: it cannot return ``-1``. diff --git a/Doc/c-api/structures.rst b/Doc/c-api/structures.rst index 987bc167c68d6c..9568ec0050e585 100644 --- a/Doc/c-api/structures.rst +++ b/Doc/c-api/structures.rst @@ -615,6 +615,7 @@ using e.g. :keyword:`del` or :py:func:`delattr`. ================================ ============================= ====================== Macro name C type Python type ================================ ============================= ====================== +.. c:macro:: Py_T_INTEGER(type) *type* (INT) :py:class:`int` .. 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` @@ -630,18 +631,21 @@ Macro name C type Python type .. 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_STRING :c:expr:`const char *` (STR) :py:class:`str` (RO) +.. c:macro:: Py_T_STRING_INPLACE :c:expr:`const char[]` (STR) :py:class:`str` (RO) +.. c:macro:: Py_T_CHAR :c:expr:`char` (0-127) :py:class:`str` (CH) .. c:macro:: Py_T_OBJECT_EX :c:expr:`PyObject *` :py:class:`object` (D) ================================ ============================= ====================== - (*): Zero-terminated, UTF8-encoded C string. + (INT): Macro :c:macro:`Py_T_INTEGER(type)` represents arbitrary + C integer type *type*. + + (STR): Zero-terminated, UTF8-encoded C string. With :c:macro:`!Py_T_STRING` the C representation is a pointer; with :c:macro:`!Py_T_STRING_INPLACE` the string is stored directly in the structure. - (**): String of length 1. Only ASCII is accepted. + (CH): String of length 1. Only ASCII is accepted. (RO): Implies :c:macro:`Py_READONLY`. @@ -687,6 +691,10 @@ Macro name C type Python type Always ``None``. Must be used with :c:macro:`Py_READONLY`. +.. versionadded:: next + Added :c:macro:`Py_T_INTEGER()`. + + Defining Getters and Setters ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/Doc/conf.py b/Doc/conf.py index 467961dd5e2bff..17394e9c2e44c3 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -153,7 +153,9 @@ ('c:type', 'int32_t'), ('c:type', 'int64_t'), ('c:type', 'intmax_t'), + ('c:type', 'intptr_t'), ('c:type', 'off_t'), + ('c:type', 'pid_t'), ('c:type', 'ptrdiff_t'), ('c:type', 'siginfo_t'), ('c:type', 'size_t'), diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 0e30500fc9b997..f38dbf83ce9bd8 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -1818,6 +1818,10 @@ New features (Contributed by Victor Stinner in :gh:`120389`.) +* Add support of arbitrary C integer types in :c:member:`PyMemberDef.type`. + Macro :c:macro:`Py_T_INTEGER(type)` represents a C type *type*. + (Contributed by Serhiy Storchaka and Petr Viktorin in :gh:`117031`.) + * Add :c:func:`PyBytes_Join(sep, iterable) ` function, similar to ``sep.join(iterable)`` in Python. (Contributed by Victor Stinner in :gh:`121645`.) diff --git a/Include/descrobject.h b/Include/descrobject.h index fd66d17b497a31..de1aa683be120a 100644 --- a/Include/descrobject.h +++ b/Include/descrobject.h @@ -79,6 +79,8 @@ struct PyMemberDef { #define Py_T_PYSSIZET 19 /* Py_ssize_t */ #define _Py_T_NONE 20 // Deprecated. Value is always None. +#define Py_T_INTEGER(type) ((sizeof(type) << 8) | (_Py_IS_TYPE_SIGNED(type) ? 0 : 1)) + /* Flags */ #define Py_READONLY 1 #define Py_AUDIT_READ 2 // Added in 3.10, harmless no-op before that diff --git a/Lib/test/test_capi/test_structmembers.py b/Lib/test/test_capi/test_structmembers.py index f14ad9a9a5f512..d0bdfea778311d 100644 --- a/Lib/test/test_capi/test_structmembers.py +++ b/Lib/test/test_capi/test_structmembers.py @@ -10,7 +10,9 @@ 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, + PY_SSIZE_T_MAX, PY_SSIZE_T_MIN, SIZE_MAX, + SIZEOF_INTMAX_T, SIZEOF_INTPTR_T, SIZEOF_PTRDIFF_T, SIZEOF_OFF_T, + SIZEOF_PID_T, SIZEOF_INT, ) @@ -19,6 +21,8 @@ def __init__(self, value): self.value = value def __index__(self): return self.value + def __repr__(self): + return f'Index({self.value!r})' # There are two classes: one using and another using # `Py_`-prefixed API. They should behave the same in Python @@ -60,22 +64,22 @@ def _test_warn(self, name, value, expected=None): if expected is not None: self.assertEqual(getattr(ts, name), expected) - def _test_overflow(self, name, value): + def _test_overflow(self, name, value, error=OverflowError): ts = self.ts - self.assertRaises(OverflowError, setattr, ts, name, value) + self.assertRaises(error, setattr, ts, name, value) def _test_int_range(self, name, minval, maxval, *, hardlimit=None, - indexlimit=None): + indexlimit=None, negvalueerror=OverflowError): if hardlimit is None: hardlimit = (minval, maxval) ts = self.ts self._test_write(name, minval) self._test_write(name, maxval) hardminval, hardmaxval = hardlimit - self._test_overflow(name, hardminval-1) + self._test_overflow(name, hardminval-1, error=negvalueerror) self._test_overflow(name, hardmaxval+1) self._test_overflow(name, 2**1000) - self._test_overflow(name, -2**1000) + self._test_overflow(name, -2**1000, error=negvalueerror) if hardminval < minval: self._test_warn(name, hardminval) self._test_warn(name, minval-1, maxval) @@ -89,10 +93,10 @@ def _test_int_range(self, name, minval, maxval, *, hardlimit=None, else: self._test_write(name, Index(minval), minval) self._test_write(name, Index(maxval), maxval) - self._test_overflow(name, Index(hardminval-1)) + self._test_overflow(name, Index(hardminval-1), error=negvalueerror) self._test_overflow(name, Index(hardmaxval+1)) self._test_overflow(name, Index(2**1000)) - self._test_overflow(name, Index(-2**1000)) + self._test_overflow(name, Index(-2**1000), error=negvalueerror) if hardminval < minval: self._test_warn(name, Index(hardminval)) self._test_warn(name, Index(minval-1), maxval) @@ -181,6 +185,47 @@ class ReadWriteTests_OldAPI(ReadWriteTests, unittest.TestCase): class ReadWriteTests_NewAPI(ReadWriteTests, unittest.TestCase): cls = _test_structmembersType_NewAPI + def test_size(self): + self._test_int_range('T_SIZE', 0, SIZE_MAX, negvalueerror=ValueError) + + def test_int8(self): + self._test_int_range('T_INT8', -2**7, 2**7-1) + self._test_int_range('T_UINT8', 0, 2**8-1, negvalueerror=ValueError) + + def test_int16(self): + self._test_int_range('T_INT16', -2**15, 2**15-1) + self._test_int_range('T_UINT16', 0, 2**16-1, negvalueerror=ValueError) + + def test_int32(self): + self._test_int_range('T_INT32', -2**31, 2**31-1) + self._test_int_range('T_UINT32', 0, 2**32-1, negvalueerror=ValueError) + + def test_int64(self): + self._test_int_range('T_INT64', -2**63, 2**63-1) + self._test_int_range('T_UINT64', 0, 2**64-1, negvalueerror=ValueError) + + def test_intmax(self): + bits = 8*SIZEOF_INTMAX_T + self._test_int_range('T_INTMAX', -2**(bits-1), 2**(bits-1)-1) + self._test_int_range('T_UINTMAX', 0, 2**bits-1, negvalueerror=ValueError) + + def test_intptr(self): + bits = 8*SIZEOF_INTPTR_T + self._test_int_range('T_INTPTR', -2**(bits-1), 2**(bits-1)-1) + self._test_int_range('T_UINTPTR', 0, 2**bits-1, negvalueerror=ValueError) + + def test_ptrdiff(self): + bits = 8*SIZEOF_PTRDIFF_T + self._test_int_range('T_PTRDIFF', -2**(bits-1), 2**(bits-1)-1) + + def test_off(self): + bits = 8*SIZEOF_OFF_T + self._test_int_range('T_OFF', -2**(bits-1), 2**(bits-1)-1) + + def test_pid(self): + bits = 8*SIZEOF_PID_T + self._test_int_range('T_PID', -2**(bits-1), 2**(bits-1)-1) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/C_API/2024-03-19-21-13-20.gh-issue-117031.0s3Ruq.rst b/Misc/NEWS.d/next/C_API/2024-03-19-21-13-20.gh-issue-117031.0s3Ruq.rst new file mode 100644 index 00000000000000..e0f2c5547f8170 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2024-03-19-21-13-20.gh-issue-117031.0s3Ruq.rst @@ -0,0 +1,2 @@ +Add support for arbitrary C integer types for :c:member:`PyMemberDef.type`. +Macro :c:macro:`Py_T_INTEGER(type)` represents a C type *type*. diff --git a/Modules/_multiprocessing/multiprocessing.h b/Modules/_multiprocessing/multiprocessing.h index 099004b437828e..f304a7bb5c7735 100644 --- a/Modules/_multiprocessing/multiprocessing.h +++ b/Modules/_multiprocessing/multiprocessing.h @@ -60,15 +60,8 @@ * Format codes */ -#if SIZEOF_VOID_P == SIZEOF_LONG -# define F_POINTER "k" -# define T_POINTER T_ULONG -#elif SIZEOF_VOID_P == SIZEOF_LONG_LONG -# define F_POINTER "K" -# define T_POINTER T_ULONGLONG -#else -# error "can't find format code for unsigned integer of same size as void*" -#endif +#define F_POINTER _Py_PARSE_UINTPTR +#define T_POINTER Py_T_INTEGER(uintptr_t) #ifdef MS_WINDOWS # define F_HANDLE F_POINTER diff --git a/Modules/_testcapi/structmember.c b/Modules/_testcapi/structmember.c index ef30a5a9944e3c..ca81de3b4fd95f 100644 --- a/Modules/_testcapi/structmember.c +++ b/Modules/_testcapi/structmember.c @@ -17,12 +17,32 @@ typedef struct { long long_member; unsigned long ulong_member; Py_ssize_t pyssizet_member; + size_t size_member; float float_member; double double_member; char inplace_member[6]; long long longlong_member; unsigned long long ulonglong_member; char char_member; + int8_t int8_member; + uint8_t uint8_member; + int16_t int16_member; + uint16_t uint16_member; + int32_t int32_member; + uint32_t uint32_member; + int64_t int64_member; + uint64_t uint64_member; + intmax_t intmax_member; + uintmax_t uintmax_member; + intptr_t intptr_member; + uintptr_t uintptr_member; + ptrdiff_t ptrdiff_member; +#ifdef MS_WINDOWS + long long off_member; +#else + off_t off_member; +#endif + pid_t pid_member; } all_structmembers; typedef struct { @@ -42,12 +62,33 @@ static struct PyMemberDef test_members_newapi[] = { {"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_SSIZE", Py_T_INTEGER(ssize_t), offsetof(test_structmembers, structmembers.pyssizet_member), 0, NULL}, + {"T_SIZE", Py_T_INTEGER(size_t), offsetof(test_structmembers, structmembers.size_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}, {"T_CHAR", Py_T_CHAR, offsetof(test_structmembers, structmembers.char_member), 0, NULL}, + {"T_INT8", Py_T_INTEGER(int8_t), offsetof(test_structmembers, structmembers.int8_member), 0, NULL}, + {"T_UINT8", Py_T_INTEGER(uint8_t), offsetof(test_structmembers, structmembers.uint8_member), 0, NULL}, + {"T_INT16", Py_T_INTEGER(int16_t), offsetof(test_structmembers, structmembers.int16_member), 0, NULL}, + {"T_UINT16", Py_T_INTEGER(uint16_t), offsetof(test_structmembers, structmembers.uint16_member), 0, NULL}, + {"T_INT32", Py_T_INTEGER(int32_t), offsetof(test_structmembers, structmembers.int32_member), 0, NULL}, + {"T_UINT32", Py_T_INTEGER(uint32_t), offsetof(test_structmembers, structmembers.uint32_member), 0, NULL}, + {"T_INT64", Py_T_INTEGER(int64_t), offsetof(test_structmembers, structmembers.int64_member), 0, NULL}, + {"T_UINT64", Py_T_INTEGER(uint64_t), offsetof(test_structmembers, structmembers.uint64_member), 0, NULL}, + {"T_INTMAX", Py_T_INTEGER(intmax_t), offsetof(test_structmembers, structmembers.intmax_member), 0, NULL}, + {"T_UINTMAX", Py_T_INTEGER(uintmax_t), offsetof(test_structmembers, structmembers.uintmax_member), 0, NULL}, + {"T_INTPTR", Py_T_INTEGER(intptr_t), offsetof(test_structmembers, structmembers.intptr_member), 0, NULL}, + {"T_UINTPTR", Py_T_INTEGER(uintptr_t), offsetof(test_structmembers, structmembers.uintptr_member), 0, NULL}, + {"T_PTRDIFF", Py_T_INTEGER(ptrdiff_t), offsetof(test_structmembers, structmembers.ptrdiff_member), 0, NULL}, +#ifdef MS_WINDOWS + {"T_OFF", Py_T_INTEGER(long long), offsetof(test_structmembers, structmembers.off_member), 0, NULL}, +#else + {"T_OFF", Py_T_INTEGER(off_t), offsetof(test_structmembers, structmembers.off_member), 0, NULL}, +#endif + {"T_PID", Py_T_INTEGER(pid_t), offsetof(test_structmembers, structmembers.pid_member), 0, NULL}, {NULL} }; diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 3aa6e4c9e43a26..084d9183f82fa9 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3272,6 +3272,15 @@ PyInit__testcapi(void) PyModule_AddObject(m, "SIZEOF_WCHAR_T", PyLong_FromSsize_t(sizeof(wchar_t))); PyModule_AddObject(m, "SIZEOF_VOID_P", PyLong_FromSsize_t(sizeof(void*))); PyModule_AddObject(m, "SIZEOF_TIME_T", PyLong_FromSsize_t(sizeof(time_t))); + PyModule_AddObject(m, "SIZEOF_INTMAX_T", PyLong_FromSsize_t(sizeof(intmax_t))); + PyModule_AddObject(m, "SIZEOF_INTPTR_T", PyLong_FromSsize_t(sizeof(intptr_t))); + PyModule_AddObject(m, "SIZEOF_PTRDIFF_T", PyLong_FromSsize_t(sizeof(ptrdiff_t))); +#ifdef MS_WINDOWS + PyModule_AddObject(m, "SIZEOF_OFF_T", PyLong_FromSsize_t(sizeof(long long))); +#else + PyModule_AddObject(m, "SIZEOF_OFF_T", PyLong_FromSsize_t(sizeof(off_t))); +#endif + PyModule_AddObject(m, "SIZEOF_INT", PyLong_FromSsize_t(sizeof(int))); PyModule_AddObject(m, "SIZEOF_PID_T", PyLong_FromSsize_t(sizeof(pid_t))); PyModule_AddObject(m, "Py_Version", PyLong_FromUnsignedLong(Py_Version)); Py_INCREF(&PyInstanceMethod_Type); diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c index d701026b50887c..1574e8a541998e 100644 --- a/Modules/selectmodule.c +++ b/Modules/selectmodule.c @@ -1799,48 +1799,21 @@ typedef struct kqueue_queue_Object { #define kqueue_queue_Object_CAST(op) ((kqueue_queue_Object *)(op)) -#if (SIZEOF_UINTPTR_T != SIZEOF_VOID_P) -# error uintptr_t does not match void *! -#elif (SIZEOF_UINTPTR_T == SIZEOF_LONG_LONG) -# define T_UINTPTRT Py_T_ULONGLONG -# define T_INTPTRT Py_T_LONGLONG -# define UINTPTRT_FMT_UNIT "K" -# define INTPTRT_FMT_UNIT "L" -#elif (SIZEOF_UINTPTR_T == SIZEOF_LONG) -# define T_UINTPTRT Py_T_ULONG -# define T_INTPTRT Py_T_LONG -# define UINTPTRT_FMT_UNIT "k" -# define INTPTRT_FMT_UNIT "l" -#elif (SIZEOF_UINTPTR_T == SIZEOF_INT) -# define T_UINTPTRT Py_T_UINT -# define T_INTPTRT Py_T_INT -# define UINTPTRT_FMT_UNIT "I" -# define INTPTRT_FMT_UNIT "i" -#else -# error uintptr_t does not match int, long, or long long! -#endif - #if SIZEOF_LONG_LONG == 8 -# define T_INT64 Py_T_LONGLONG # define INT64_FMT_UNIT "L" #elif SIZEOF_LONG == 8 -# define T_INT64 Py_T_LONG # define INT64_FMT_UNIT "l" #elif SIZEOF_INT == 8 -# define T_INT64 Py_T_INT # define INT64_FMT_UNIT "i" #else # define INT64_FMT_UNIT "_" #endif #if SIZEOF_LONG_LONG == 4 -# define T_UINT32 Py_T_ULONGLONG # define UINT32_FMT_UNIT "K" #elif SIZEOF_LONG == 4 -# define T_UINT32 Py_T_ULONG # define UINT32_FMT_UNIT "k" #elif SIZEOF_INT == 4 -# define T_UINT32 Py_T_UINT # define UINT32_FMT_UNIT "I" #else # define UINT32_FMT_UNIT "_" @@ -1850,11 +1823,11 @@ typedef struct kqueue_queue_Object { * kevent is not standard and its members vary across BSDs. */ #ifdef __NetBSD__ -# define FILTER_TYPE T_UINT32 +# define FILTER_TYPE Py_T_UINT32 # define FILTER_FMT_UNIT UINT32_FMT_UNIT -# define FLAGS_TYPE T_UINT32 +# define FLAGS_TYPE Py_T_UINT32 # define FLAGS_FMT_UNIT UINT32_FMT_UNIT -# define FFLAGS_TYPE T_UINT32 +# define FFLAGS_TYPE Py_T_UINT32 # define FFLAGS_FMT_UNIT UINT32_FMT_UNIT #else # define FILTER_TYPE Py_T_SHORT @@ -1866,11 +1839,11 @@ typedef struct kqueue_queue_Object { #endif #if defined(__NetBSD__) || defined(__OpenBSD__) -# define DATA_TYPE T_INT64 +# define DATA_TYPE Py_T_INT64 # define DATA_FMT_UNIT INT64_FMT_UNIT #else -# define DATA_TYPE T_INTPTRT -# define DATA_FMT_UNIT INTPTRT_FMT_UNIT +# define DATA_TYPE Py_T_INTPTR +# define DATA_FMT_UNIT _Py_PARSE_INTPTR #endif /* Unfortunately, we can't store python objects in udata, because @@ -1880,12 +1853,12 @@ typedef struct kqueue_queue_Object { #define KQ_OFF(x) offsetof(kqueue_event_Object, x) static struct PyMemberDef kqueue_event_members[] = { - {"ident", T_UINTPTRT, KQ_OFF(e.ident)}, + {"ident", Py_T_INTEGER(uintptr_t), KQ_OFF(e.ident)}, {"filter", FILTER_TYPE, KQ_OFF(e.filter)}, {"flags", FLAGS_TYPE, KQ_OFF(e.flags)}, - {"fflags", Py_T_UINT, KQ_OFF(e.fflags)}, + {"fflags", Py_T_UINT, KQ_OFF(e.fflags)}, {"data", DATA_TYPE, KQ_OFF(e.data)}, - {"udata", T_UINTPTRT, KQ_OFF(e.udata)}, + {"udata", Py_T_INTEGER(uintptr_t), KQ_OFF(e.udata)}, {NULL} /* Sentinel */ }; #undef KQ_OFF @@ -1909,7 +1882,7 @@ kqueue_event_init(PyObject *op, PyObject *args, PyObject *kwds) "data", "udata", NULL}; static const char fmt[] = "O|" FILTER_FMT_UNIT FLAGS_FMT_UNIT FFLAGS_FMT_UNIT DATA_FMT_UNIT - UINTPTRT_FMT_UNIT ":kevent"; + _Py_PARSE_UINTPTR ":kevent"; kqueue_event_Object *self = kqueue_event_Object_CAST(op); EV_SET(&(self->e), 0, EVFILT_READ, EV_ADD, 0, 0, 0); /* defaults */ diff --git a/Python/structmember.c b/Python/structmember.c index d36e049d6b5d20..25155630d9ec4d 100644 --- a/Python/structmember.c +++ b/Python/structmember.c @@ -7,6 +7,8 @@ #include "pycore_object.h" // _Py_TryIncrefCompare(), FT_ATOMIC_*() #include "pycore_critical_section.h" +#include + static inline PyObject * member_get_object(const char *addr, const char *obj_addr, PyMemberDef *l) @@ -116,6 +118,16 @@ PyMember_GetOne(const char *obj_addr, PyMemberDef *l) v = Py_NewRef(Py_None); break; default: + if (l->type > 0xff && l->type <= 0xffff && (l->type & (0xff & ~0x01)) == 0) { + Py_ssize_t size = l->type >> 8; + if (l->type & 0x01) { + v = PyLong_FromUnsignedNativeBytes(addr, size, -1); + } + else { + v = PyLong_FromNativeBytes(addr, size, -1); + } + break; + } PyErr_SetString(PyExc_SystemError, "bad memberdescr type"); v = NULL; } @@ -362,6 +374,24 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v) break; } default: + if (l->type > 0xff && l->type <= 0xffff && (l->type & (0xff & ~0x01)) == 0) { + Py_ssize_t size = l->type >> 8; + int flags = Py_ASNATIVEBYTES_NATIVE_ENDIAN + | Py_ASNATIVEBYTES_ALLOW_INDEX; + if (l->type & 0x01) { + flags |= Py_ASNATIVEBYTES_UNSIGNED_BUFFER + | Py_ASNATIVEBYTES_REJECT_NEGATIVE; + } + Py_ssize_t bytes = PyLong_AsNativeBytes(v, addr, size, flags); + if (bytes < 0) { + return -1; + } + if (bytes > size) { + PyErr_SetString(PyExc_OverflowError, "int too big to convert"); + return -1; + } + break; + } PyErr_Format(PyExc_SystemError, "bad memberdescr type for %s", l->name); return -1; From 9bae8bd46c4811966711ed7f5ddc09fa08e27672 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 15 Apr 2025 18:10:53 +0300 Subject: [PATCH 2/6] Fix errors. --- Doc/c-api/structures.rst | 2 +- Doc/whatsnew/3.14.rst | 2 +- Modules/_testcapi/structmember.c | 1 - Modules/selectmodule.c | 10 +++++----- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Doc/c-api/structures.rst b/Doc/c-api/structures.rst index 9568ec0050e585..9e5ef22e93bdf2 100644 --- a/Doc/c-api/structures.rst +++ b/Doc/c-api/structures.rst @@ -637,7 +637,7 @@ Macro name C type Python type .. c:macro:: Py_T_OBJECT_EX :c:expr:`PyObject *` :py:class:`object` (D) ================================ ============================= ====================== - (INT): Macro :c:macro:`Py_T_INTEGER(type)` represents arbitrary + (INT): Macro :c:macro:`Py_T_INTEGER(type) ` represents arbitrary C integer type *type*. (STR): Zero-terminated, UTF8-encoded C string. diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index f38dbf83ce9bd8..5e137a290c10ac 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -1819,7 +1819,7 @@ New features (Contributed by Victor Stinner in :gh:`120389`.) * Add support of arbitrary C integer types in :c:member:`PyMemberDef.type`. - Macro :c:macro:`Py_T_INTEGER(type)` represents a C type *type*. + Macro :c:macro:`Py_T_INTEGER(type) ` represents a C type *type*. (Contributed by Serhiy Storchaka and Petr Viktorin in :gh:`117031`.) * Add :c:func:`PyBytes_Join(sep, iterable) ` function, diff --git a/Modules/_testcapi/structmember.c b/Modules/_testcapi/structmember.c index ca81de3b4fd95f..560876ba0777b1 100644 --- a/Modules/_testcapi/structmember.c +++ b/Modules/_testcapi/structmember.c @@ -62,7 +62,6 @@ static struct PyMemberDef test_members_newapi[] = { {"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_SSIZE", Py_T_INTEGER(ssize_t), offsetof(test_structmembers, structmembers.pyssizet_member), 0, NULL}, {"T_SIZE", Py_T_INTEGER(size_t), offsetof(test_structmembers, structmembers.size_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}, diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c index 1574e8a541998e..7e95e097f66725 100644 --- a/Modules/selectmodule.c +++ b/Modules/selectmodule.c @@ -1823,11 +1823,11 @@ typedef struct kqueue_queue_Object { * kevent is not standard and its members vary across BSDs. */ #ifdef __NetBSD__ -# define FILTER_TYPE Py_T_UINT32 +# define FILTER_TYPE Py_T_INTEGER(uint32_t) # define FILTER_FMT_UNIT UINT32_FMT_UNIT -# define FLAGS_TYPE Py_T_UINT32 +# define FLAGS_TYPE Py_T_INTEGER(uint32_t) # define FLAGS_FMT_UNIT UINT32_FMT_UNIT -# define FFLAGS_TYPE Py_T_UINT32 +# define FFLAGS_TYPE Py_T_INTEGER(uint32_t) # define FFLAGS_FMT_UNIT UINT32_FMT_UNIT #else # define FILTER_TYPE Py_T_SHORT @@ -1839,10 +1839,10 @@ typedef struct kqueue_queue_Object { #endif #if defined(__NetBSD__) || defined(__OpenBSD__) -# define DATA_TYPE Py_T_INT64 +# define DATA_TYPE Py_T_INTEGER(int64_t) # define DATA_FMT_UNIT INT64_FMT_UNIT #else -# define DATA_TYPE Py_T_INTPTR +# define DATA_TYPE Py_T_INTEGER(intptr_t) # define DATA_FMT_UNIT _Py_PARSE_INTPTR #endif From 812814a28865b3e281ba9df3f841a9859cd8dc15 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 15 Apr 2025 18:12:25 +0300 Subject: [PATCH 3/6] Remove some redundant tests. --- Lib/test/test_capi/test_structmembers.py | 9 --------- Modules/_testcapi/structmember.c | 6 ------ 2 files changed, 15 deletions(-) diff --git a/Lib/test/test_capi/test_structmembers.py b/Lib/test/test_capi/test_structmembers.py index d0bdfea778311d..fdd69ce5f878f2 100644 --- a/Lib/test/test_capi/test_structmembers.py +++ b/Lib/test/test_capi/test_structmembers.py @@ -204,20 +204,11 @@ def test_int64(self): self._test_int_range('T_INT64', -2**63, 2**63-1) self._test_int_range('T_UINT64', 0, 2**64-1, negvalueerror=ValueError) - def test_intmax(self): - bits = 8*SIZEOF_INTMAX_T - self._test_int_range('T_INTMAX', -2**(bits-1), 2**(bits-1)-1) - self._test_int_range('T_UINTMAX', 0, 2**bits-1, negvalueerror=ValueError) - def test_intptr(self): bits = 8*SIZEOF_INTPTR_T self._test_int_range('T_INTPTR', -2**(bits-1), 2**(bits-1)-1) self._test_int_range('T_UINTPTR', 0, 2**bits-1, negvalueerror=ValueError) - def test_ptrdiff(self): - bits = 8*SIZEOF_PTRDIFF_T - self._test_int_range('T_PTRDIFF', -2**(bits-1), 2**(bits-1)-1) - def test_off(self): bits = 8*SIZEOF_OFF_T self._test_int_range('T_OFF', -2**(bits-1), 2**(bits-1)-1) diff --git a/Modules/_testcapi/structmember.c b/Modules/_testcapi/structmember.c index 560876ba0777b1..7bf6b25019a44d 100644 --- a/Modules/_testcapi/structmember.c +++ b/Modules/_testcapi/structmember.c @@ -32,11 +32,8 @@ typedef struct { uint32_t uint32_member; int64_t int64_member; uint64_t uint64_member; - intmax_t intmax_member; - uintmax_t uintmax_member; intptr_t intptr_member; uintptr_t uintptr_member; - ptrdiff_t ptrdiff_member; #ifdef MS_WINDOWS long long off_member; #else @@ -77,11 +74,8 @@ static struct PyMemberDef test_members_newapi[] = { {"T_UINT32", Py_T_INTEGER(uint32_t), offsetof(test_structmembers, structmembers.uint32_member), 0, NULL}, {"T_INT64", Py_T_INTEGER(int64_t), offsetof(test_structmembers, structmembers.int64_member), 0, NULL}, {"T_UINT64", Py_T_INTEGER(uint64_t), offsetof(test_structmembers, structmembers.uint64_member), 0, NULL}, - {"T_INTMAX", Py_T_INTEGER(intmax_t), offsetof(test_structmembers, structmembers.intmax_member), 0, NULL}, - {"T_UINTMAX", Py_T_INTEGER(uintmax_t), offsetof(test_structmembers, structmembers.uintmax_member), 0, NULL}, {"T_INTPTR", Py_T_INTEGER(intptr_t), offsetof(test_structmembers, structmembers.intptr_member), 0, NULL}, {"T_UINTPTR", Py_T_INTEGER(uintptr_t), offsetof(test_structmembers, structmembers.uintptr_member), 0, NULL}, - {"T_PTRDIFF", Py_T_INTEGER(ptrdiff_t), offsetof(test_structmembers, structmembers.ptrdiff_member), 0, NULL}, #ifdef MS_WINDOWS {"T_OFF", Py_T_INTEGER(long long), offsetof(test_structmembers, structmembers.off_member), 0, NULL}, #else From 6c175a8265f130b81a1d18d73c8f04138026e082 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 15 Apr 2025 18:32:59 +0300 Subject: [PATCH 4/6] Apply suggestions from code review Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> --- Doc/c-api/structures.rst | 2 +- Doc/whatsnew/3.14.rst | 4 ++-- .../next/C_API/2024-03-19-21-13-20.gh-issue-117031.0s3Ruq.rst | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/c-api/structures.rst b/Doc/c-api/structures.rst index 9e5ef22e93bdf2..f702f542f226ac 100644 --- a/Doc/c-api/structures.rst +++ b/Doc/c-api/structures.rst @@ -637,7 +637,7 @@ Macro name C type Python type .. c:macro:: Py_T_OBJECT_EX :c:expr:`PyObject *` :py:class:`object` (D) ================================ ============================= ====================== - (INT): Macro :c:macro:`Py_T_INTEGER(type) ` represents arbitrary + (INT): Macro :c:macro:`Py_T_INTEGER(type) ` represents an arbitrary C integer type *type*. (STR): Zero-terminated, UTF8-encoded C string. diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 5e137a290c10ac..f3ccdd444839ac 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -1818,8 +1818,8 @@ New features (Contributed by Victor Stinner in :gh:`120389`.) -* Add support of arbitrary C integer types in :c:member:`PyMemberDef.type`. - Macro :c:macro:`Py_T_INTEGER(type) ` represents a C type *type*. +* Add support for arbitrary C integer types in :c:member:`PyMemberDef.type`. + The macro :c:macro:`Py_T_INTEGER(type) ` represents a C type *type*. (Contributed by Serhiy Storchaka and Petr Viktorin in :gh:`117031`.) * Add :c:func:`PyBytes_Join(sep, iterable) ` function, diff --git a/Misc/NEWS.d/next/C_API/2024-03-19-21-13-20.gh-issue-117031.0s3Ruq.rst b/Misc/NEWS.d/next/C_API/2024-03-19-21-13-20.gh-issue-117031.0s3Ruq.rst index e0f2c5547f8170..b961ceeaa92b82 100644 --- a/Misc/NEWS.d/next/C_API/2024-03-19-21-13-20.gh-issue-117031.0s3Ruq.rst +++ b/Misc/NEWS.d/next/C_API/2024-03-19-21-13-20.gh-issue-117031.0s3Ruq.rst @@ -1,2 +1,2 @@ -Add support for arbitrary C integer types for :c:member:`PyMemberDef.type`. -Macro :c:macro:`Py_T_INTEGER(type)` represents a C type *type*. +Add support for arbitrary C integer types in :c:member:`PyMemberDef.type`. +The macro :c:macro:`Py_T_INTEGER(type)` represents a C type *type*. From 436144e6d83ec0f83a3c1944be0ee3d330f91df7 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 15 Apr 2025 19:15:01 +0300 Subject: [PATCH 5/6] Fix reference. --- .../next/C_API/2024-03-19-21-13-20.gh-issue-117031.0s3Ruq.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/C_API/2024-03-19-21-13-20.gh-issue-117031.0s3Ruq.rst b/Misc/NEWS.d/next/C_API/2024-03-19-21-13-20.gh-issue-117031.0s3Ruq.rst index b961ceeaa92b82..624128cf558f8f 100644 --- a/Misc/NEWS.d/next/C_API/2024-03-19-21-13-20.gh-issue-117031.0s3Ruq.rst +++ b/Misc/NEWS.d/next/C_API/2024-03-19-21-13-20.gh-issue-117031.0s3Ruq.rst @@ -1,2 +1,2 @@ Add support for arbitrary C integer types in :c:member:`PyMemberDef.type`. -The macro :c:macro:`Py_T_INTEGER(type)` represents a C type *type*. +The macro :c:macro:`Py_T_INTEGER(type) ` represents a C type *type*. From 34eee62aa58c96ad372ae1b1769e7418d6921cd3 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 17 Apr 2025 16:14:34 +0300 Subject: [PATCH 6/6] Support combined types. --- Include/descrobject.h | 2 +- Lib/test/test_capi/test_structmembers.py | 10 +++++++--- Modules/_testcapi/structmember.c | 8 ++++++++ Python/structmember.c | 20 ++++++++++++++------ 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Include/descrobject.h b/Include/descrobject.h index de1aa683be120a..a7c1a702e71d6a 100644 --- a/Include/descrobject.h +++ b/Include/descrobject.h @@ -79,7 +79,7 @@ struct PyMemberDef { #define Py_T_PYSSIZET 19 /* Py_ssize_t */ #define _Py_T_NONE 20 // Deprecated. Value is always None. -#define Py_T_INTEGER(type) ((sizeof(type) << 8) | (_Py_IS_TYPE_SIGNED(type) ? 0 : 1)) +#define Py_T_INTEGER(type) ((sizeof(type) << 8) | (_Py_IS_TYPE_SIGNED(type) ? 1 : 2)) /* Flags */ #define Py_READONLY 1 diff --git a/Lib/test/test_capi/test_structmembers.py b/Lib/test/test_capi/test_structmembers.py index fdd69ce5f878f2..dc1b770b780e24 100644 --- a/Lib/test/test_capi/test_structmembers.py +++ b/Lib/test/test_capi/test_structmembers.py @@ -69,12 +69,12 @@ def _test_overflow(self, name, value, error=OverflowError): self.assertRaises(error, setattr, ts, name, value) def _test_int_range(self, name, minval, maxval, *, hardlimit=None, - indexlimit=None, negvalueerror=OverflowError): + indexlimit=None, negvalueerror=OverflowError, wrap=False): if hardlimit is None: hardlimit = (minval, maxval) ts = self.ts self._test_write(name, minval) - self._test_write(name, maxval) + self._test_write(name, maxval, -1 if wrap else maxval) hardminval, hardmaxval = hardlimit self._test_overflow(name, hardminval-1, error=negvalueerror) self._test_overflow(name, hardmaxval+1) @@ -92,7 +92,7 @@ def _test_int_range(self, name, minval, maxval, *, hardlimit=None, self.assertRaises(TypeError, setattr, ts, name, Index(maxval)) else: self._test_write(name, Index(minval), minval) - self._test_write(name, Index(maxval), maxval) + self._test_write(name, Index(maxval), -1 if wrap else maxval) self._test_overflow(name, Index(hardminval-1), error=negvalueerror) self._test_overflow(name, Index(hardmaxval+1)) self._test_overflow(name, Index(2**1000)) @@ -191,18 +191,22 @@ def test_size(self): def test_int8(self): self._test_int_range('T_INT8', -2**7, 2**7-1) self._test_int_range('T_UINT8', 0, 2**8-1, negvalueerror=ValueError) + self._test_int_range('T_XINT8', -2**7, 2**8-1, wrap=True) def test_int16(self): self._test_int_range('T_INT16', -2**15, 2**15-1) self._test_int_range('T_UINT16', 0, 2**16-1, negvalueerror=ValueError) + self._test_int_range('T_XINT16', -2**15, 2**16-1, wrap=True) def test_int32(self): self._test_int_range('T_INT32', -2**31, 2**31-1) self._test_int_range('T_UINT32', 0, 2**32-1, negvalueerror=ValueError) + self._test_int_range('T_XINT32', -2**31, 2**32-1, wrap=True) def test_int64(self): self._test_int_range('T_INT64', -2**63, 2**63-1) self._test_int_range('T_UINT64', 0, 2**64-1, negvalueerror=ValueError) + self._test_int_range('T_XINT64', -2**63, 2**64-1, wrap=True) def test_intptr(self): bits = 8*SIZEOF_INTPTR_T diff --git a/Modules/_testcapi/structmember.c b/Modules/_testcapi/structmember.c index 7bf6b25019a44d..dded419cbbd56a 100644 --- a/Modules/_testcapi/structmember.c +++ b/Modules/_testcapi/structmember.c @@ -26,12 +26,16 @@ typedef struct { char char_member; int8_t int8_member; uint8_t uint8_member; + uint8_t xint8_member; int16_t int16_member; uint16_t uint16_member; + uint16_t xint16_member; int32_t int32_member; uint32_t uint32_member; + uint32_t xint32_member; int64_t int64_member; uint64_t uint64_member; + uint64_t xint64_member; intptr_t intptr_member; uintptr_t uintptr_member; #ifdef MS_WINDOWS @@ -68,12 +72,16 @@ static struct PyMemberDef test_members_newapi[] = { {"T_CHAR", Py_T_CHAR, offsetof(test_structmembers, structmembers.char_member), 0, NULL}, {"T_INT8", Py_T_INTEGER(int8_t), offsetof(test_structmembers, structmembers.int8_member), 0, NULL}, {"T_UINT8", Py_T_INTEGER(uint8_t), offsetof(test_structmembers, structmembers.uint8_member), 0, NULL}, + {"T_XINT8", Py_T_INTEGER(int8_t)|Py_T_INTEGER(uint8_t), offsetof(test_structmembers, structmembers.xint8_member), 0, NULL}, {"T_INT16", Py_T_INTEGER(int16_t), offsetof(test_structmembers, structmembers.int16_member), 0, NULL}, {"T_UINT16", Py_T_INTEGER(uint16_t), offsetof(test_structmembers, structmembers.uint16_member), 0, NULL}, + {"T_XINT16", Py_T_INTEGER(int16_t)|Py_T_INTEGER(uint16_t), offsetof(test_structmembers, structmembers.xint16_member), 0, NULL}, {"T_INT32", Py_T_INTEGER(int32_t), offsetof(test_structmembers, structmembers.int32_member), 0, NULL}, {"T_UINT32", Py_T_INTEGER(uint32_t), offsetof(test_structmembers, structmembers.uint32_member), 0, NULL}, + {"T_XINT32", Py_T_INTEGER(int32_t)|Py_T_INTEGER(uint32_t), offsetof(test_structmembers, structmembers.xint32_member), 0, NULL}, {"T_INT64", Py_T_INTEGER(int64_t), offsetof(test_structmembers, structmembers.int64_member), 0, NULL}, {"T_UINT64", Py_T_INTEGER(uint64_t), offsetof(test_structmembers, structmembers.uint64_member), 0, NULL}, + {"T_XINT64", Py_T_INTEGER(int64_t)|Py_T_INTEGER(uint64_t), offsetof(test_structmembers, structmembers.xint64_member), 0, NULL}, {"T_INTPTR", Py_T_INTEGER(intptr_t), offsetof(test_structmembers, structmembers.intptr_member), 0, NULL}, {"T_UINTPTR", Py_T_INTEGER(uintptr_t), offsetof(test_structmembers, structmembers.uintptr_member), 0, NULL}, #ifdef MS_WINDOWS diff --git a/Python/structmember.c b/Python/structmember.c index 25155630d9ec4d..dc5cff25a46239 100644 --- a/Python/structmember.c +++ b/Python/structmember.c @@ -118,15 +118,20 @@ PyMember_GetOne(const char *obj_addr, PyMemberDef *l) v = Py_NewRef(Py_None); break; default: - if (l->type > 0xff && l->type <= 0xffff && (l->type & (0xff & ~0x01)) == 0) { + if (l->type > 0xff && l->type <= 0xffff && (l->type & (0xff & ~0x03)) == 0) { Py_ssize_t size = l->type >> 8; - if (l->type & 0x01) { + if ((l->type & 0x03) == 1) { + v = PyLong_FromNativeBytes(addr, size, -1); + break; + } + else if ((l->type & 0x03) == 2) { v = PyLong_FromUnsignedNativeBytes(addr, size, -1); + break; } - else { + else if ((l->type & 0x03) == 3) { v = PyLong_FromNativeBytes(addr, size, -1); + break; } - break; } PyErr_SetString(PyExc_SystemError, "bad memberdescr type"); v = NULL; @@ -374,14 +379,17 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v) break; } default: - if (l->type > 0xff && l->type <= 0xffff && (l->type & (0xff & ~0x01)) == 0) { + if (l->type > 0xff && l->type <= 0xffff && (l->type & (0xff & ~0x03)) == 0) { Py_ssize_t size = l->type >> 8; int flags = Py_ASNATIVEBYTES_NATIVE_ENDIAN | Py_ASNATIVEBYTES_ALLOW_INDEX; - if (l->type & 0x01) { + if ((l->type & 0x03) == 2) { flags |= Py_ASNATIVEBYTES_UNSIGNED_BUFFER | Py_ASNATIVEBYTES_REJECT_NEGATIVE; } + else if ((l->type & 0x03) == 3) { + flags |= Py_ASNATIVEBYTES_UNSIGNED_BUFFER; + } Py_ssize_t bytes = PyLong_AsNativeBytes(v, addr, size, flags); if (bytes < 0) { return -1;