From 669c882624bcde615710747103dd912ccedc9ae5 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 9 Oct 2024 12:41:02 +0200 Subject: [PATCH] Add PyLong_FromUInt64() and PyLong_AsUInt64() (#114) --- docs/api.rst | 41 +++++++++++--- docs/changelog.rst | 8 +++ pythoncapi_compat.h | 85 +++++++++++++++++++++++++++++ tests/test_pythoncapi_compat_cext.c | 42 ++++++++++++++ 4 files changed, 168 insertions(+), 8 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index ceb232f..b94df42 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -89,6 +89,39 @@ Python 3.14 See `PyUnicodeWriter_Format() documentation `__. +.. c:function:: int PyLong_AsInt32(PyObject *obj, int32_t *pvalue) + + See `PyLong_AsInt32() documentation `__. + +.. c:function:: int PyLong_AsInt64(PyObject *obj, int64_t *pvalue) + + See `PyLong_AsInt64() documentation `__. + +.. c:function:: int PyLong_AsUInt32(PyObject *obj, uint32_t *pvalue) + + See `PyLong_AsUInt32() documentation `__. + +.. c:function:: int PyLong_AsUInt64(PyObject *obj, uint64_t *pvalue) + + See `PyLong_AsUInt64() documentation `__. + +.. c:function:: PyObject* PyLong_FromInt32(int32_t value) + + See `PyLong_FromInt32() documentation `__. + +.. c:function:: PyObject* PyLong_FromInt64(int64_t value) + + See `PyLong_FromInt64() documentation `__. + +.. c:function:: PyObject* PyLong_FromUInt32(uint32_t value) + + See `PyLong_FromUInt32() documentation `__. + +.. c:function:: PyObject* PyLong_FromUInt64(uint64_t value) + + See `PyLong_FromUInt64() documentation `__. + + Not supported: * ``PyConfig_Get()`` @@ -108,14 +141,6 @@ Not supported: * ``PyInitConfig_SetInt()`` * ``PyInitConfig_SetStr()`` * ``PyInitConfig_SetStrList()`` -* ``PyLong_AsInt32()`` -* ``PyLong_AsInt64()`` -* ``PyLong_AsUInt32()`` -* ``PyLong_AsUInt64()`` -* ``PyLong_FromInt32()`` -* ``PyLong_FromInt64()`` -* ``PyLong_FromUInt32()`` -* ``PyLong_FromUInt64()`` * ``PyType_GetBaseByToken()`` * ``PyUnicodeWriter_DecodeUTF8Stateful()`` * ``Py_InitializeFromInitConfig()`` diff --git a/docs/changelog.rst b/docs/changelog.rst index 856e5a9..a2d188c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,6 +5,14 @@ Changelog * ``PyBytes_Join()`` * ``PyIter_NextItem()`` + * ``PyLong_AsInt32()`` + * ``PyLong_AsInt64()`` + * ``PyLong_AsUInt32()`` + * ``PyLong_AsUInt64()`` + * ``PyLong_FromInt32()`` + * ``PyLong_FromInt64()`` + * ``PyLong_FromUInt32()`` + * ``PyLong_FromUInt64()`` * ``PyUnicode_Equal()`` * ``Py_HashBuffer()`` diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index db1e8d2..34a84c9 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -45,6 +45,13 @@ extern "C" { # define _PyObject_CAST(op) _Py_CAST(PyObject*, op) #endif +#ifndef Py_BUILD_ASSERT +# define Py_BUILD_ASSERT(cond) \ + do { \ + (void)sizeof(char [1 - 2 * !(cond)]); \ + } while(0) +#endif + // bpo-42262 added Py_NewRef() to Python 3.10.0a3 #if PY_VERSION_HEX < 0x030A00A3 && !defined(Py_NewRef) @@ -1605,6 +1612,84 @@ static inline int PyIter_NextItem(PyObject *iter, PyObject **item) #endif +#if PY_VERSION_HEX < 0x030E00A0 +static inline PyObject* PyLong_FromInt32(int32_t value) +{ + Py_BUILD_ASSERT(sizeof(long) >= 4); + return PyLong_FromLong(value); +} + +static inline PyObject* PyLong_FromInt64(int64_t value) +{ + Py_BUILD_ASSERT(sizeof(long long) >= 8); + return PyLong_FromLongLong(value); +} + +static inline PyObject* PyLong_FromUInt32(uint32_t value) +{ + Py_BUILD_ASSERT(sizeof(unsigned long) >= 4); + return PyLong_FromUnsignedLong(value); +} + +static inline PyObject* PyLong_FromUInt64(uint64_t value) +{ + Py_BUILD_ASSERT(sizeof(unsigned long long) >= 8); + return PyLong_FromUnsignedLongLong(value); +} + +static inline int PyLong_AsInt32(PyObject *obj, int32_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(int) == 4); + int value = PyLong_AsInt(obj); + if (value == -1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (int32_t)value; + return 0; +} + +static inline int PyLong_AsInt64(PyObject *obj, int64_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long long) == 8); + long long value = PyLong_AsLongLong(obj); + if (value == -1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (int64_t)value; + return 0; +} + +static inline int PyLong_AsUInt32(PyObject *obj, uint32_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long) >= 4); + unsigned long value = PyLong_AsUnsignedLong(obj); + if (value == (unsigned long)-1 && PyErr_Occurred()) { + return -1; + } +#if SIZEOF_LONG > 4 + if ((unsigned long)UINT32_MAX < value) { + PyErr_SetString(PyExc_OverflowError, + "Python int too large to convert to C uint32_t"); + return -1; + } +#endif + *pvalue = (uint32_t)value; + return 0; +} + +static inline int PyLong_AsUInt64(PyObject *obj, uint64_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long long) == 8); + unsigned long long value = PyLong_AsUnsignedLongLong(obj); + if (value == (unsigned long long)-1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (uint64_t)value; + return 0; +} +#endif + + #ifdef __cplusplus } #endif diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index 6ef1873..ecbed45 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -1971,6 +1971,47 @@ test_iter(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) } +static PyObject * +test_long_stdint(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + PyObject *obj; + + // Test PyLong_FromInt32() and PyLong_AsInt32() + obj = PyLong_FromInt32(INT32_C(-0x12345678)); + assert(obj != NULL); + int32_t i32; + assert(PyLong_AsInt32(obj, &i32) == 0); + assert(i32 == INT32_C(-0x12345678)); + Py_DECREF(obj); + + // Test PyLong_FromUInt32() and PyLong_AsUInt32() + obj = PyLong_FromUInt32(UINT32_C(0xDEADBEEF)); + assert(obj != NULL); + uint32_t u32; + assert(PyLong_AsUInt32(obj, &u32) == 0); + assert(u32 == UINT32_C(0xDEADBEEF)); + Py_DECREF(obj); + + // Test PyLong_FromInt64() and PyLong_AsInt64() + obj = PyLong_FromInt64(INT64_C(-0x12345678DEADBEEF)); + assert(obj != NULL); + int64_t i64; + assert(PyLong_AsInt64(obj, &i64) == 0); + assert(i64 == INT64_C(-0x12345678DEADBEEF)); + Py_DECREF(obj); + + // Test PyLong_FromUInt64() and PyLong_AsUInt64() + obj = PyLong_FromUInt64(UINT64_C(0xDEADBEEF12345678)); + assert(obj != NULL); + uint64_t u64; + assert(PyLong_AsUInt64(obj, &u64) == 0); + assert(u64 == UINT64_C(0xDEADBEEF12345678)); + Py_DECREF(obj); + + Py_RETURN_NONE; +} + + static struct PyMethodDef methods[] = { {"test_object", test_object, METH_NOARGS, _Py_NULL}, {"test_py_is", test_py_is, METH_NOARGS, _Py_NULL}, @@ -2016,6 +2057,7 @@ static struct PyMethodDef methods[] = { #endif {"test_bytes", test_bytes, METH_NOARGS, _Py_NULL}, {"test_iter", test_iter, METH_NOARGS, _Py_NULL}, + {"test_long_stdint", test_long_stdint, METH_NOARGS, _Py_NULL}, {_Py_NULL, _Py_NULL, 0, _Py_NULL} };