diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6f43aa5..6ffc74b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,15 +21,17 @@ jobs: python: # Python versions (CPython): # https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json - - "3.7" - "3.8" - "3.9" - "3.10" - "3.11" - "3.12" - # CPython 3.13 final is scheduled for October 2024: - # https://peps.python.org/pep-0719/ - "3.13" + - "3.13t" + # CPython 3.14 final is scheduled for October 2025: + # https://peps.python.org/pep-0719/ + - "3.14" + - "3.14t" # PyPy versions: # - https://github.com/actions/setup-python/blob/main/docs/advanced-usage.md @@ -42,6 +44,7 @@ jobs: - "pypy3.8" - "pypy3.9" - "pypy3.10" + - "pypy3.11" # Old PyPy versions # See https://foss.heptapod.net/pypy/pypy/-/issues/3991 @@ -50,20 +53,45 @@ jobs: - "pypy3.7-v7.3.2" include: - # Windows: test old and new Python + # Windows + - os: windows-latest + python: "3.6" + - os: windows-latest + python: "3.7" + - os: windows-latest + python: "3.8" + - os: windows-latest + python: "3.9" + - os: windows-latest + python: "3.10" - os: windows-latest - python: 3.6 + python: "3.11" - os: windows-latest - python: 3.12 + python: "3.12" + - os: windows-latest + python: "3.13" + - os: windows-latest + python: "3.13t" - # macOS: test only new Python + # macOS + # Python 3.9 is the oldest version available on macOS/arm64. + - os: macos-latest + python: "3.9" + - os: macos-latest + python: "3.10" + - os: macos-latest + python: "3.11" + - os: macos-latest + python: "3.12" + - os: macos-latest + python: "3.13" - os: macos-latest - python: 3.12 + python: "3.13t" # Ubuntu: test deadsnakes Python versions which are not supported by # GHA python-versions. - - os: ubuntu-20.04 - python: 3.6 + - os: ubuntu-22.04 + python: "3.7" steps: # https://github.com/actions/checkout diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 735b67d..21e6b81 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -4,9 +4,9 @@ version: 2 build: - os: ubuntu-22.04 + os: ubuntu-lts-latest tools: - python: "3.12" + python: "3" sphinx: configuration: docs/conf.py diff --git a/docs/api.rst b/docs/api.rst index a75053e..3f30e53 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -10,7 +10,7 @@ Supported Python versions: * Python 3.6 - 3.14 * PyPy 2.7 and PyPy 3.6 - 3.10 -Python 2.7 and Python 3.4 are no longer officially supported since GitHub +Python 2.7 and Python 3.5 are no longer officially supported since GitHub Actions doesn't support them anymore: only best effort support is provided. C++03 and C++11 are supported on Python 3.6 and newer. @@ -23,16 +23,243 @@ Some functions related to frame objects and ``PyThreadState`` are not available on PyPy. Latest version of the header file: -`pythoncapi_compat.h `_. +`pythoncapi_compat.h `_. + + +Python 3.15 +----------- + +.. c:function:: PyBytesWriter* PyBytesWriter_Create(Py_ssize_t size) +.. c:function:: void* PyBytesWriter_GetData(PyBytesWriter *writer) +.. c:function:: Py_ssize_t PyBytesWriter_GetSize(PyBytesWriter *writer) +.. c:function:: PyObject* PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size) +.. c:function:: PyObject* PyBytesWriter_Finish(PyBytesWriter *writer) +.. c:function:: PyObject* PyBytesWriter_FinishWithPointer(PyBytesWriter *writer, void *buf) +.. c:function:: void PyBytesWriter_Discard(PyBytesWriter *writer) +.. c:function:: int PyBytesWriter_Resize(PyBytesWriter *writer, Py_ssize_t size) +.. c:function:: int PyBytesWriter_Grow(PyBytesWriter *writer, Py_ssize_t size) +.. c:function:: void* PyBytesWriter_GrowAndUpdatePointer(PyBytesWriter *writer, Py_ssize_t size, void *buf) +.. c:function:: int PyBytesWriter_WriteBytes(PyBytesWriter *writer, const void *bytes, Py_ssize_t size) +.. c:function:: int PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...) + + See `PyBytesWriter documentation `__. + +.. c:function:: PyObject* PySys_GetAttr(const char *name) + + See `PySys_GetAttr() documentation `__. + +.. c:function:: PyObject* PySys_GetAttrString(const char *name) + + See `PySys_GetAttrString() documentation `__. + +.. c:function:: PyObject* PySys_GetOptionalAttr(const char *name) + + See `PySys_GetOptionalAttr() documentation `__. + +.. c:function:: PyObject* PySys_GetOptionalAttrString(const char *name) + + See `PySys_GetOptionalAttrString() documentation `__. + +.. c:function:: PyObject* PyTuple_FromArray(PyObject *const *array, Py_ssize_t size) + + See `PyTuple_FromArray() documentation `__. + +.. c:function:: Py_hash_t PyUnstable_Unicode_GET_CACHED_HASH(PyObject *op) + + See `PyUnstable_Unicode_GET_CACHED_HASH() documentation `__. + + On PyPy, always returns ``-1``. Python 3.14 ----------- +.. c:struct:: PyLongLayout + + See `PyLongLayout documentation `__. + +.. c:function:: const PyLongLayout* PyLong_GetNativeLayout(void) + + See `PyLong_GetNativeLayout() documentation `__. + +.. c:struct:: PyLongExport + + See `PyLongExport documentation `__. + +.. c:function:: int PyLong_Export(PyObject *obj, PyLongExport *export_long) + + See `PyLong_Export() documentation `__. + +.. c:function:: void PyLong_FreeExport(PyLongExport *export_long) + + See `PyLong_FreeExport() documentation `__. + +.. c:struct:: PyLongWriter + + See `PyLongWriter documentation `__. + +.. c:function:: PyLongWriter* PyLongWriter_Create(int negative, Py_ssize_t ndigits, void **digits) + + See `PyLongWriter_Create() documentation `__. + +.. c:function:: PyObject* PyLongWriter_Finish(PyLongWriter *writer) + + See `PyLongWriter_Finish() documentation `__. + +.. c:function:: void PyLongWriter_Discard(PyLongWriter *writer) + + See `PyLongWriter_Discard() documentation `__. + +.. c:function:: int PyLong_IsPositive(PyObject *obj) + + See `PyLong_IsPositive() documentation `__. + +.. c:function:: int PyLong_IsNegative(PyObject *obj) + + See `PyLong_IsNegative() documentation `__. + +.. c:function:: int PyLong_IsZero(PyObject *obj) + + See `PyLong_IsZero() documentation `__. + .. c:function:: int PyLong_GetSign(PyObject *obj, int *sign) See `PyLong_GetSign() documentation `__. +.. c:function:: PyObject* PyIter_NextItem(PyObject *sep, PyObject *iterable) + + See `PyIter_NextItem() documentation `__. + +.. c:function:: PyObject* PyBytes_Join(PyObject *sep, PyObject *iterable) + + See `PyBytes_Join() documentation `__. + +.. c:function:: Py_hash_t Py_HashBuffer(const void *ptr, Py_ssize_t len) + + See `Py_HashBuffer() documentation `__. + +.. c:function:: int PyUnicode_Equal(PyObject *str1, PyObject *str2) + + See `PyUnicode_Equal() documentation `__. + +.. c:function:: PyUnicodeWriter* PyUnicodeWriter_Create(Py_ssize_t length) + + See `PyUnicodeWriter_Create() documentation `__. + +.. c:function:: PyObject* PyUnicodeWriter_Finish(PyUnicodeWriter *writer) + + See `PyUnicodeWriter_Finish() documentation `__. + +.. c:function:: void PyUnicodeWriter_Discard(PyUnicodeWriter *writer) + + See `PyUnicodeWriter_Discard() documentation `__. + +.. c:function:: int PyUnicodeWriter_WriteChar(PyUnicodeWriter *writer, Py_UCS4 ch) + + See `PyUnicodeWriter_WriteChar() documentation `__. + +.. c:function:: int PyUnicodeWriter_WriteUTF8(PyUnicodeWriter *writer, const char *str, Py_ssize_t size) + + See `PyUnicodeWriter_WriteUTF8() documentation `__. + +.. c:function:: int PyUnicodeWriter_WriteASCII(PyUnicodeWriter *writer, const char *str, Py_ssize_t size) + + See `PyUnicodeWriter_WriteASCII() documentation `__. + +.. c:function:: int PyUnicodeWriter_WriteWideChar(PyUnicodeWriter *writer, const wchar_t *str, Py_ssize_t size) + + See `PyUnicodeWriter_WriteWideChar() documentation `__. + +.. c:function:: int PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj) + + See `PyUnicodeWriter_WriteStr() documentation `__. + +.. c:function:: int PyUnicodeWriter_WriteRepr(PyUnicodeWriter *writer, PyObject *obj) + + See `PyUnicodeWriter_WriteRepr() documentation `__. + +.. c:function:: int PyUnicodeWriter_WriteSubstring(PyUnicodeWriter *writer, PyObject *str, Py_ssize_t start, Py_ssize_t end) + + See `PyUnicodeWriter_WriteSubstring() documentation `__. + +.. c:function:: int PyUnicodeWriter_Format(PyUnicodeWriter *writer, const char *format, ...) + + 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 `__. + +.. c:function:: FILE* Py_fopen(PyObject *path, const char *mode) + + See `Py_fopen() documentation `__. + +.. c:function:: int Py_fclose(FILE *file) + + See `Py_fclose() documentation `__. + +.. c:function:: PyObject* PyConfig_Get(const char *name) + + See `PyConfig_Get() documentation `__. + +.. c:function:: int PyConfig_GetInt(const char *name, int *value) + + See `PyConfig_GetInt() documentation `__. + +.. c:function:: int PyUnstable_Object_IsUniquelyReferenced(PyObject *op) + + See `PyUnstable_Object_IsUniquelyReferenced() documentation `__. + +Not supported: + +* ``PyConfig_Names()`` +* ``PyConfig_Set()`` +* ``PyInitConfig_AddModule()`` +* ``PyInitConfig_Create()`` +* ``PyInitConfig_Free()`` +* ``PyInitConfig_FreeStrList()`` +* ``PyInitConfig_GetError()`` +* ``PyInitConfig_GetExitCode()`` +* ``PyInitConfig_GetInt()`` +* ``PyInitConfig_GetStr()`` +* ``PyInitConfig_GetStrList()`` +* ``PyInitConfig_HasOption()`` +* ``PyInitConfig_SetInt()`` +* ``PyInitConfig_SetStr()`` +* ``PyInitConfig_SetStrList()`` +* ``PyType_GetBaseByToken()`` +* ``PyUnicodeWriter_DecodeUTF8Stateful()`` +* ``Py_InitializeFromInitConfig()`` + + Python 3.13 ----------- diff --git a/docs/changelog.rst b/docs/changelog.rst index 1e7ba2a..007a665 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,96 @@ Changelog ========= +* 2025-10-14: Add functions: + + * ``PyTuple_FromArray()`` + * ``PyUnstable_Unicode_GET_CACHED_HASH()`` + +* 2025-09-18: Add PEP 782 functions: + + * ``PyBytesWriter_Create()`` + * ``PyBytesWriter_Discard()`` + * ``PyBytesWriter_Finish()`` + * ``PyBytesWriter_FinishWithPointer()`` + * ``PyBytesWriter_FinishWithSize()`` + * ``PyBytesWriter_Format()`` + * ``PyBytesWriter_GetData()`` + * ``PyBytesWriter_GetSize()`` + * ``PyBytesWriter_Grow()`` + * ``PyBytesWriter_GrowAndUpdatePointer()`` + * ``PyBytesWriter_Resize()`` + * ``PyBytesWriter_WriteBytes()`` + +* 2025-09-01: Add ``PyUnstable_Object_IsUniquelyReferenced()`` function. +* 2025-06-09: Add ``PyUnicodeWriter_WriteASCII()`` function. +* 2025-06-03: Add functions: + + * ``PySys_GetAttr()`` + * ``PySys_GetAttrString()`` + * ``PySys_GetOptionalAttr()`` + * ``PySys_GetOptionalAttrString()`` + +* 2025-01-19: Add ``PyConfig_Get()`` functions. +* 2025-01-06: Add ``Py_fopen()`` and ``Py_fclose()`` functions. +* 2024-12-16: Add ``structmember.h`` constants: + + * ``Py_T_BOOL`` + * ``Py_T_BYTE`` + * ``Py_T_CHAR`` + * ``Py_T_DOUBLE`` + * ``Py_T_FLOAT`` + * ``Py_T_INT`` + * ``Py_T_LONGLONG`` + * ``Py_T_LONG`` + * ``Py_T_OBJECT_EX`` + * ``Py_T_PYSSIZET`` + * ``Py_T_SHORT`` + * ``Py_T_STRING_INPLACE`` + * ``Py_T_STRING`` + * ``Py_T_UBYTE`` + * ``Py_T_UINT`` + * ``Py_T_ULONGLONG`` + * ``Py_T_ULONG`` + * ``Py_T_USHORT`` + * ``_Py_T_NONE`` + * ``_Py_T_OBJECT`` + * ``Py_AUDIT_READ`` + * ``Py_READONLY`` + * ``_Py_WRITE_RESTRICTED`` + +* 2024-12-13: Add functions and structs: + + * ``PyLongLayout`` + * ``PyLong_GetNativeLayout()`` + * ``PyLongExport`` + * ``PyLong_Export()`` + * ``PyLong_FreeExport()`` + * ``PyLongWriter`` + * ``PyLongWriter_Create()`` + * ``PyLongWriter_Finish()`` + * ``PyLongWriter_Discard()`` + +* 2024-11-12: Add functions: + + * ``PyLong_IsPositive()`` + * ``PyLong_IsNegative()`` + * ``PyLong_IsZero()`` + +* 2024-10-09: Add functions: + + * ``PyBytes_Join()`` + * ``PyIter_NextItem()`` + * ``PyLong_AsInt32()`` + * ``PyLong_AsInt64()`` + * ``PyLong_AsUInt32()`` + * ``PyLong_AsUInt64()`` + * ``PyLong_FromInt32()`` + * ``PyLong_FromInt64()`` + * ``PyLong_FromUInt32()`` + * ``PyLong_FromUInt64()`` + * ``PyUnicode_Equal()`` + * ``Py_HashBuffer()`` + * 2024-07-18: Add functions: * ``PyUnicodeWriter_Create()`` diff --git a/docs/users.rst b/docs/users.rst index b90466f..abf4307 100644 --- a/docs/users.rst +++ b/docs/users.rst @@ -45,7 +45,10 @@ Examples of projects using pythoncapi_compat.h `__) * `PyTorch `_ (`pythoncapi_compat.h copy `__) - +* `PyGObject `_ + (`commit `__, + `pythoncapi-compat Meson subproject + `__) Projects not using pythoncapi_compat.h ====================================== diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..7c5f9fc --- /dev/null +++ b/meson.build @@ -0,0 +1,6 @@ +project( + 'pythoncapi-compat', + 'c' +) + +incdir = include_directories('.') diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index d45828f..cdfdafa 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -7,7 +7,7 @@ // https://github.com/python/pythoncapi_compat // // Latest version: -// https://raw.githubusercontent.com/python/pythoncapi_compat/master/pythoncapi_compat.h +// https://raw.githubusercontent.com/python/pythoncapi-compat/main/pythoncapi_compat.h // // SPDX-License-Identifier: 0BSD @@ -19,6 +19,7 @@ extern "C" { #endif #include +#include // offsetof() // Python 3.11.0b4 added PyFrame_Back() to Python.h #if PY_VERSION_HEX < 0x030b00B4 && !defined(PYPY_VERSION) @@ -33,11 +34,13 @@ extern "C" { // Static inline functions should use _Py_NULL rather than using directly NULL // to prevent C++ compiler warnings. On C23 and newer and on C++11 and newer, // _Py_NULL is defined as nullptr. -#if (defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L) \ - || (defined(__cplusplus) && __cplusplus >= 201103) -# define _Py_NULL nullptr -#else -# define _Py_NULL NULL +#ifndef _Py_NULL +# if (defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L) \ + || (defined(__cplusplus) && __cplusplus >= 201103) +# define _Py_NULL nullptr +# else +# define _Py_NULL NULL +# endif #endif // Cast argument to PyObject* type. @@ -45,6 +48,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) @@ -280,7 +290,7 @@ PyFrame_GetVarString(PyFrameObject *frame, const char *name) // bpo-39947 added PyThreadState_GetInterpreter() to Python 3.9.0a5 -#if PY_VERSION_HEX < 0x030900A5 || defined(PYPY_VERSION) +#if PY_VERSION_HEX < 0x030900A5 || (defined(PYPY_VERSION) && PY_VERSION_HEX < 0x030B0000) static inline PyInterpreterState * PyThreadState_GetInterpreter(PyThreadState *tstate) { @@ -573,7 +583,7 @@ static inline int PyWeakref_GetRef(PyObject *ref, PyObject **pobj) return 0; } *pobj = Py_NewRef(obj); - return (*pobj != NULL); + return 1; } #endif @@ -911,7 +921,7 @@ static inline int PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg) { PyObject **dict = _PyObject_GetDictPtr(obj); - if (*dict == NULL) { + if (dict == NULL || *dict == NULL) { return -1; } Py_VISIT(*dict); @@ -922,7 +932,7 @@ static inline void PyObject_ClearManagedDict(PyObject *obj) { PyObject **dict = _PyObject_GetDictPtr(obj); - if (*dict == NULL) { + if (dict == NULL || *dict == NULL) { return; } Py_CLEAR(*dict); @@ -1197,11 +1207,11 @@ static inline int PyTime_PerfCounter(PyTime_t *result) #endif // gh-111389 added hash constants to Python 3.13.0a5. These constants were -// added first as private macros to Python 3.4.0b1 and PyPy 7.3.9. +// added first as private macros to Python 3.4.0b1 and PyPy 7.3.8. #if (!defined(PyHASH_BITS) \ && ((!defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x030400B1) \ || (defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x03070000 \ - && PYPY_VERSION_NUM >= 0x07090000))) + && PYPY_VERSION_NUM >= 0x07030800))) # define PyHASH_BITS _PyHASH_BITS # define PyHASH_MODULUS _PyHASH_MODULUS # define PyHASH_INF _PyHASH_INF @@ -1338,6 +1348,13 @@ PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value, } #endif +#if PY_VERSION_HEX < 0x030D00B3 +# define Py_BEGIN_CRITICAL_SECTION(op) { +# define Py_END_CRITICAL_SECTION() } +# define Py_BEGIN_CRITICAL_SECTION2(a, b) { +# define Py_END_CRITICAL_SECTION2() } +#endif + #if PY_VERSION_HEX < 0x030E0000 && PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION) typedef struct PyUnicodeWriter PyUnicodeWriter; @@ -1392,7 +1409,7 @@ PyUnicodeWriter_WriteChar(PyUnicodeWriter *writer, Py_UCS4 ch) return _PyUnicodeWriter_WriteChar((_PyUnicodeWriter*)writer, ch); } -int +static inline int PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj) { PyObject *str = PyObject_Str(obj); @@ -1405,7 +1422,7 @@ PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj) return res; } -int +static inline int PyUnicodeWriter_WriteRepr(PyUnicodeWriter *writer, PyObject *obj) { PyObject *str = PyObject_Repr(obj); @@ -1436,6 +1453,18 @@ PyUnicodeWriter_WriteUTF8(PyUnicodeWriter *writer, return res; } +static inline int +PyUnicodeWriter_WriteASCII(PyUnicodeWriter *writer, + const char *str, Py_ssize_t size) +{ + if (size < 0) { + size = (Py_ssize_t)strlen(str); + } + + return _PyUnicodeWriter_WriteASCIIString((_PyUnicodeWriter*)writer, + str, size); +} + static inline int PyUnicodeWriter_WriteWideChar(PyUnicodeWriter *writer, const wchar_t *str, Py_ssize_t size) @@ -1459,7 +1488,8 @@ PyUnicodeWriter_WriteSubstring(PyUnicodeWriter *writer, PyObject *str, Py_ssize_t start, Py_ssize_t end) { if (!PyUnicode_Check(str)) { - PyErr_Format(PyExc_TypeError, "expect str, not %T", str); + PyErr_Format(PyExc_TypeError, "expect str, not %s", + Py_TYPE(str)->tp_name); return -1; } if (start < 0 || start > end) { @@ -1506,6 +1536,1129 @@ static inline int PyLong_GetSign(PyObject *obj, int *sign) } #endif +// gh-126061 added PyLong_IsPositive/Negative/Zero() to Python in 3.14.0a2 +#if PY_VERSION_HEX < 0x030E00A2 +static inline int PyLong_IsPositive(PyObject *obj) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expected int, got %s", Py_TYPE(obj)->tp_name); + return -1; + } + return _PyLong_Sign(obj) == 1; +} + +static inline int PyLong_IsNegative(PyObject *obj) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expected int, got %s", Py_TYPE(obj)->tp_name); + return -1; + } + return _PyLong_Sign(obj) == -1; +} + +static inline int PyLong_IsZero(PyObject *obj) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expected int, got %s", Py_TYPE(obj)->tp_name); + return -1; + } + return _PyLong_Sign(obj) == 0; +} +#endif + + +// gh-124502 added PyUnicode_Equal() to Python 3.14.0a0 +#if PY_VERSION_HEX < 0x030E00A0 +static inline int PyUnicode_Equal(PyObject *str1, PyObject *str2) +{ + if (!PyUnicode_Check(str1)) { + PyErr_Format(PyExc_TypeError, "first argument must be str, not %s", + Py_TYPE(str1)->tp_name); + return -1; + } + if (!PyUnicode_Check(str2)) { + PyErr_Format(PyExc_TypeError, "second argument must be str, not %s", + Py_TYPE(str2)->tp_name); + return -1; + } + +#if PY_VERSION_HEX >= 0x030d0000 && !defined(PYPY_VERSION) + PyAPI_FUNC(int) _PyUnicode_Equal(PyObject *str1, PyObject *str2); + + return _PyUnicode_Equal(str1, str2); +#elif PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION) + return _PyUnicode_EQ(str1, str2); +#elif PY_VERSION_HEX >= 0x03090000 && defined(PYPY_VERSION) + return _PyUnicode_EQ(str1, str2); +#else + return (PyUnicode_Compare(str1, str2) == 0); +#endif +} +#endif + + +// gh-121645 added PyBytes_Join() to Python 3.14.0a0 +#if PY_VERSION_HEX < 0x030E00A0 +static inline PyObject* PyBytes_Join(PyObject *sep, PyObject *iterable) +{ + return _PyBytes_Join(sep, iterable); +} +#endif + + +#if PY_VERSION_HEX < 0x030E00A0 +static inline Py_hash_t Py_HashBuffer(const void *ptr, Py_ssize_t len) +{ +#if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) + PyAPI_FUNC(Py_hash_t) _Py_HashBytes(const void *src, Py_ssize_t len); + + return _Py_HashBytes(ptr, len); +#else + Py_hash_t hash; + PyObject *bytes = PyBytes_FromStringAndSize((const char*)ptr, len); + if (bytes == NULL) { + return -1; + } + hash = PyObject_Hash(bytes); + Py_DECREF(bytes); + return hash; +#endif +} +#endif + + +#if PY_VERSION_HEX < 0x030E00A0 +static inline int PyIter_NextItem(PyObject *iter, PyObject **item) +{ + iternextfunc tp_iternext; + + assert(iter != NULL); + assert(item != NULL); + + tp_iternext = Py_TYPE(iter)->tp_iternext; + if (tp_iternext == NULL) { + *item = NULL; + PyErr_Format(PyExc_TypeError, "expected an iterator, got '%s'", + Py_TYPE(iter)->tp_name); + return -1; + } + + if ((*item = tp_iternext(iter))) { + return 1; + } + if (!PyErr_Occurred()) { + return 0; + } + if (PyErr_ExceptionMatches(PyExc_StopIteration)) { + PyErr_Clear(); + return 0; + } + return -1; +} +#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 + + +// gh-102471 added import and export API for integers to 3.14.0a2. +#if PY_VERSION_HEX < 0x030E00A2 && PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) +// Helpers to access PyLongObject internals. +static inline void +_PyLong_SetSignAndDigitCount(PyLongObject *op, int sign, Py_ssize_t size) +{ +#if PY_VERSION_HEX >= 0x030C0000 + op->long_value.lv_tag = (uintptr_t)(1 - sign) | ((uintptr_t)(size) << 3); +#elif PY_VERSION_HEX >= 0x030900A4 + Py_SET_SIZE(op, sign * size); +#else + Py_SIZE(op) = sign * size; +#endif +} + +static inline Py_ssize_t +_PyLong_DigitCount(const PyLongObject *op) +{ +#if PY_VERSION_HEX >= 0x030C0000 + return (Py_ssize_t)(op->long_value.lv_tag >> 3); +#else + return _PyLong_Sign((PyObject*)op) < 0 ? -Py_SIZE(op) : Py_SIZE(op); +#endif +} + +static inline digit* +_PyLong_GetDigits(const PyLongObject *op) +{ +#if PY_VERSION_HEX >= 0x030C0000 + return (digit*)(op->long_value.ob_digit); +#else + return (digit*)(op->ob_digit); +#endif +} + +typedef struct PyLongLayout { + uint8_t bits_per_digit; + uint8_t digit_size; + int8_t digits_order; + int8_t digit_endianness; +} PyLongLayout; + +typedef struct PyLongExport { + int64_t value; + uint8_t negative; + Py_ssize_t ndigits; + const void *digits; + Py_uintptr_t _reserved; +} PyLongExport; + +typedef struct PyLongWriter PyLongWriter; + +static inline const PyLongLayout* +PyLong_GetNativeLayout(void) +{ + static const PyLongLayout PyLong_LAYOUT = { + PyLong_SHIFT, + sizeof(digit), + -1, // least significant first + PY_LITTLE_ENDIAN ? -1 : 1, + }; + + return &PyLong_LAYOUT; +} + +static inline int +PyLong_Export(PyObject *obj, PyLongExport *export_long) +{ + if (!PyLong_Check(obj)) { + memset(export_long, 0, sizeof(*export_long)); + PyErr_Format(PyExc_TypeError, "expected int, got %s", + Py_TYPE(obj)->tp_name); + return -1; + } + + // Fast-path: try to convert to a int64_t + PyLongObject *self = (PyLongObject*)obj; + int overflow; +#if SIZEOF_LONG == 8 + long value = PyLong_AsLongAndOverflow(obj, &overflow); +#else + // Windows has 32-bit long, so use 64-bit long long instead + long long value = PyLong_AsLongLongAndOverflow(obj, &overflow); +#endif + Py_BUILD_ASSERT(sizeof(value) == sizeof(int64_t)); + // the function cannot fail since obj is a PyLongObject + assert(!(value == -1 && PyErr_Occurred())); + + if (!overflow) { + export_long->value = value; + export_long->negative = 0; + export_long->ndigits = 0; + export_long->digits = 0; + export_long->_reserved = 0; + } + else { + export_long->value = 0; + export_long->negative = _PyLong_Sign(obj) < 0; + export_long->ndigits = _PyLong_DigitCount(self); + if (export_long->ndigits == 0) { + export_long->ndigits = 1; + } + export_long->digits = _PyLong_GetDigits(self); + export_long->_reserved = (Py_uintptr_t)Py_NewRef(obj); + } + return 0; +} + +static inline void +PyLong_FreeExport(PyLongExport *export_long) +{ + PyObject *obj = (PyObject*)export_long->_reserved; + + if (obj) { + export_long->_reserved = 0; + Py_DECREF(obj); + } +} + +static inline PyLongWriter* +PyLongWriter_Create(int negative, Py_ssize_t ndigits, void **digits) +{ + if (ndigits <= 0) { + PyErr_SetString(PyExc_ValueError, "ndigits must be positive"); + return NULL; + } + assert(digits != NULL); + + PyLongObject *obj = _PyLong_New(ndigits); + if (obj == NULL) { + return NULL; + } + _PyLong_SetSignAndDigitCount(obj, negative?-1:1, ndigits); + + *digits = _PyLong_GetDigits(obj); + return (PyLongWriter*)obj; +} + +static inline void +PyLongWriter_Discard(PyLongWriter *writer) +{ + PyLongObject *obj = (PyLongObject *)writer; + + assert(Py_REFCNT(obj) == 1); + Py_DECREF(obj); +} + +static inline PyObject* +PyLongWriter_Finish(PyLongWriter *writer) +{ + PyObject *obj = (PyObject *)writer; + PyLongObject *self = (PyLongObject*)obj; + Py_ssize_t j = _PyLong_DigitCount(self); + Py_ssize_t i = j; + int sign = _PyLong_Sign(obj); + + assert(Py_REFCNT(obj) == 1); + + // Normalize and get singleton if possible + while (i > 0 && _PyLong_GetDigits(self)[i-1] == 0) { + --i; + } + if (i != j) { + if (i == 0) { + sign = 0; + } + _PyLong_SetSignAndDigitCount(self, sign, i); + } + if (i <= 1) { + long val = sign * (long)(_PyLong_GetDigits(self)[0]); + Py_DECREF(obj); + return PyLong_FromLong(val); + } + + return obj; +} +#endif + + +#if PY_VERSION_HEX < 0x030C00A3 +# 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 +# define Py_T_CHAR 7 +# define Py_T_BYTE 8 +# define Py_T_UBYTE 9 +# define Py_T_USHORT 10 +# define Py_T_UINT 11 +# define Py_T_ULONG 12 +# define Py_T_STRING_INPLACE 13 +# 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 + +# if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) +# define _Py_T_NONE 20 +# endif + +# define Py_READONLY 1 +# define Py_AUDIT_READ 2 +# define _Py_WRITE_RESTRICTED 4 +#endif + + +// gh-127350 added Py_fopen() and Py_fclose() to Python 3.14a4 +#if PY_VERSION_HEX < 0x030E00A4 +static inline FILE* Py_fopen(PyObject *path, const char *mode) +{ +#if 0x030400A2 <= PY_VERSION_HEX && !defined(PYPY_VERSION) + PyAPI_FUNC(FILE*) _Py_fopen_obj(PyObject *path, const char *mode); + + return _Py_fopen_obj(path, mode); +#else + FILE *f; + PyObject *bytes; +#if PY_VERSION_HEX >= 0x03000000 + if (!PyUnicode_FSConverter(path, &bytes)) { + return NULL; + } +#else + if (!PyString_Check(path)) { + PyErr_SetString(PyExc_TypeError, "except str"); + return NULL; + } + bytes = Py_NewRef(path); +#endif + const char *path_bytes = PyBytes_AS_STRING(bytes); + + f = fopen(path_bytes, mode); + Py_DECREF(bytes); + + if (f == NULL) { + PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path); + return NULL; + } + return f; +#endif +} + +static inline int Py_fclose(FILE *file) +{ + return fclose(file); +} +#endif + + +#if 0x03080000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030E0000 && !defined(PYPY_VERSION) +PyAPI_FUNC(const PyConfig*) _Py_GetConfig(void); + +static inline PyObject* +PyConfig_Get(const char *name) +{ + typedef enum { + _PyConfig_MEMBER_INT, + _PyConfig_MEMBER_UINT, + _PyConfig_MEMBER_ULONG, + _PyConfig_MEMBER_BOOL, + _PyConfig_MEMBER_WSTR, + _PyConfig_MEMBER_WSTR_OPT, + _PyConfig_MEMBER_WSTR_LIST, + } PyConfigMemberType; + + typedef struct { + const char *name; + size_t offset; + PyConfigMemberType type; + const char *sys_attr; + } PyConfigSpec; + +#define PYTHONCAPI_COMPAT_SPEC(MEMBER, TYPE, sys_attr) \ + {#MEMBER, offsetof(PyConfig, MEMBER), \ + _PyConfig_MEMBER_##TYPE, sys_attr} + + static const PyConfigSpec config_spec[] = { + PYTHONCAPI_COMPAT_SPEC(argv, WSTR_LIST, "argv"), + PYTHONCAPI_COMPAT_SPEC(base_exec_prefix, WSTR_OPT, "base_exec_prefix"), + PYTHONCAPI_COMPAT_SPEC(base_executable, WSTR_OPT, "_base_executable"), + PYTHONCAPI_COMPAT_SPEC(base_prefix, WSTR_OPT, "base_prefix"), + PYTHONCAPI_COMPAT_SPEC(bytes_warning, UINT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(exec_prefix, WSTR_OPT, "exec_prefix"), + PYTHONCAPI_COMPAT_SPEC(executable, WSTR_OPT, "executable"), + PYTHONCAPI_COMPAT_SPEC(inspect, BOOL, _Py_NULL), +#if 0x030C0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(int_max_str_digits, UINT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(interactive, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(module_search_paths, WSTR_LIST, "path"), + PYTHONCAPI_COMPAT_SPEC(optimization_level, UINT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(parser_debug, BOOL, _Py_NULL), +#if 0x03090000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(platlibdir, WSTR, "platlibdir"), +#endif + PYTHONCAPI_COMPAT_SPEC(prefix, WSTR_OPT, "prefix"), + PYTHONCAPI_COMPAT_SPEC(pycache_prefix, WSTR_OPT, "pycache_prefix"), + PYTHONCAPI_COMPAT_SPEC(quiet, BOOL, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(stdlib_dir, WSTR_OPT, "_stdlib_dir"), +#endif + PYTHONCAPI_COMPAT_SPEC(use_environment, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(verbose, UINT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(warnoptions, WSTR_LIST, "warnoptions"), + PYTHONCAPI_COMPAT_SPEC(write_bytecode, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(xoptions, WSTR_LIST, "_xoptions"), + PYTHONCAPI_COMPAT_SPEC(buffered_stdio, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(check_hash_pycs_mode, WSTR, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(code_debug_ranges, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(configure_c_stdio, BOOL, _Py_NULL), +#if 0x030D0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(cpu_count, INT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(dev_mode, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(dump_refs, BOOL, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(dump_refs_file, WSTR_OPT, _Py_NULL), +#endif +#ifdef Py_GIL_DISABLED + PYTHONCAPI_COMPAT_SPEC(enable_gil, INT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(faulthandler, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(filesystem_encoding, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(filesystem_errors, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(hash_seed, ULONG, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(home, WSTR_OPT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(import_time, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(install_signal_handlers, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(isolated, BOOL, _Py_NULL), +#ifdef MS_WINDOWS + PYTHONCAPI_COMPAT_SPEC(legacy_windows_stdio, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(malloc_stats, BOOL, _Py_NULL), +#if 0x030A0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(orig_argv, WSTR_LIST, "orig_argv"), +#endif + PYTHONCAPI_COMPAT_SPEC(parse_argv, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(pathconfig_warnings, BOOL, _Py_NULL), +#if 0x030C0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(perf_profiling, UINT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(program_name, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(run_command, WSTR_OPT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(run_filename, WSTR_OPT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(run_module, WSTR_OPT, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(safe_path, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(show_ref_count, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(site_import, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(skip_source_first_line, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(stdio_encoding, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(stdio_errors, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(tracemalloc, UINT, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(use_frozen_modules, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(use_hash_seed, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(user_site_directory, BOOL, _Py_NULL), +#if 0x030A0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(warn_default_encoding, BOOL, _Py_NULL), +#endif + }; + +#undef PYTHONCAPI_COMPAT_SPEC + + const PyConfigSpec *spec; + int found = 0; + for (size_t i=0; i < sizeof(config_spec) / sizeof(config_spec[0]); i++) { + spec = &config_spec[i]; + if (strcmp(spec->name, name) == 0) { + found = 1; + break; + } + } + if (found) { + if (spec->sys_attr != NULL) { + PyObject *value = PySys_GetObject(spec->sys_attr); + if (value == NULL) { + PyErr_Format(PyExc_RuntimeError, "lost sys.%s", spec->sys_attr); + return NULL; + } + return Py_NewRef(value); + } + + const PyConfig *config = _Py_GetConfig(); + void *member = (char *)config + spec->offset; + switch (spec->type) { + case _PyConfig_MEMBER_INT: + case _PyConfig_MEMBER_UINT: + { + int value = *(int *)member; + return PyLong_FromLong(value); + } + case _PyConfig_MEMBER_BOOL: + { + int value = *(int *)member; + return PyBool_FromLong(value != 0); + } + case _PyConfig_MEMBER_ULONG: + { + unsigned long value = *(unsigned long *)member; + return PyLong_FromUnsignedLong(value); + } + case _PyConfig_MEMBER_WSTR: + case _PyConfig_MEMBER_WSTR_OPT: + { + wchar_t *wstr = *(wchar_t **)member; + if (wstr != NULL) { + return PyUnicode_FromWideChar(wstr, -1); + } + else { + return Py_NewRef(Py_None); + } + } + case _PyConfig_MEMBER_WSTR_LIST: + { + const PyWideStringList *list = (const PyWideStringList *)member; + PyObject *tuple = PyTuple_New(list->length); + if (tuple == NULL) { + return NULL; + } + + for (Py_ssize_t i = 0; i < list->length; i++) { + PyObject *item = PyUnicode_FromWideChar(list->items[i], -1); + if (item == NULL) { + Py_DECREF(tuple); + return NULL; + } + PyTuple_SET_ITEM(tuple, i, item); + } + return tuple; + } + default: + Py_UNREACHABLE(); + } + } + + PyErr_Format(PyExc_ValueError, "unknown config option name: %s", name); + return NULL; +} + +static inline int +PyConfig_GetInt(const char *name, int *value) +{ + PyObject *obj = PyConfig_Get(name); + if (obj == NULL) { + return -1; + } + + if (!PyLong_Check(obj)) { + Py_DECREF(obj); + PyErr_Format(PyExc_TypeError, "config option %s is not an int", name); + return -1; + } + + int as_int = PyLong_AsInt(obj); + Py_DECREF(obj); + if (as_int == -1 && PyErr_Occurred()) { + PyErr_Format(PyExc_OverflowError, + "config option %s value does not fit into a C int", name); + return -1; + } + + *value = as_int; + return 0; +} +#endif // PY_VERSION_HEX > 0x03090000 && !defined(PYPY_VERSION) + +// gh-133144 added PyUnstable_Object_IsUniquelyReferenced() to Python 3.14.0b1. +// Adapted from _PyObject_IsUniquelyReferenced() implementation. +#if PY_VERSION_HEX < 0x030E00B0 +static inline int PyUnstable_Object_IsUniquelyReferenced(PyObject *obj) +{ +#if !defined(Py_GIL_DISABLED) + return Py_REFCNT(obj) == 1; +#else + // NOTE: the entire ob_ref_shared field must be zero, including flags, to + // ensure that other threads cannot concurrently create new references to + // this object. + return (_Py_IsOwnedByCurrentThread(obj) && + _Py_atomic_load_uint32_relaxed(&obj->ob_ref_local) == 1 && + _Py_atomic_load_ssize_relaxed(&obj->ob_ref_shared) == 0); +#endif +} +#endif + +// gh-128926 added PyUnstable_TryIncRef() and PyUnstable_EnableTryIncRef() to +// Python 3.14.0a5. Adapted from _Py_TryIncref() and _PyObject_SetMaybeWeakref(). +#if PY_VERSION_HEX < 0x030E00A5 +static inline int PyUnstable_TryIncRef(PyObject *op) +{ +#ifndef Py_GIL_DISABLED + if (Py_REFCNT(op) > 0) { + Py_INCREF(op); + return 1; + } + return 0; +#else + // _Py_TryIncrefFast() + uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local); + local += 1; + if (local == 0) { + // immortal + return 1; + } + if (_Py_IsOwnedByCurrentThread(op)) { + _Py_INCREF_STAT_INC(); + _Py_atomic_store_uint32_relaxed(&op->ob_ref_local, local); +#ifdef Py_REF_DEBUG + _Py_INCREF_IncRefTotal(); +#endif + return 1; + } + + // _Py_TryIncRefShared() + Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&op->ob_ref_shared); + for (;;) { + // If the shared refcount is zero and the object is either merged + // or may not have weak references, then we cannot incref it. + if (shared == 0 || shared == _Py_REF_MERGED) { + return 0; + } + + if (_Py_atomic_compare_exchange_ssize( + &op->ob_ref_shared, + &shared, + shared + (1 << _Py_REF_SHARED_SHIFT))) { +#ifdef Py_REF_DEBUG + _Py_INCREF_IncRefTotal(); +#endif + _Py_INCREF_STAT_INC(); + return 1; + } + } +#endif +} + +static inline void PyUnstable_EnableTryIncRef(PyObject *op) +{ +#ifdef Py_GIL_DISABLED + // _PyObject_SetMaybeWeakref() + if (_Py_IsImmortal(op)) { + return; + } + for (;;) { + Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&op->ob_ref_shared); + if ((shared & _Py_REF_SHARED_FLAG_MASK) != 0) { + // Nothing to do if it's in WEAKREFS, QUEUED, or MERGED states. + return; + } + if (_Py_atomic_compare_exchange_ssize( + &op->ob_ref_shared, &shared, shared | _Py_REF_MAYBE_WEAKREF)) { + return; + } + } +#else + (void)op; // unused argument +#endif +} +#endif + + +#if PY_VERSION_HEX < 0x030F0000 +static inline PyObject* +PySys_GetAttrString(const char *name) +{ +#if PY_VERSION_HEX >= 0x03000000 + PyObject *value = Py_XNewRef(PySys_GetObject(name)); +#else + PyObject *value = Py_XNewRef(PySys_GetObject((char*)name)); +#endif + if (value != NULL) { + return value; + } + if (!PyErr_Occurred()) { + PyErr_Format(PyExc_RuntimeError, "lost sys.%s", name); + } + return NULL; +} + +static inline PyObject* +PySys_GetAttr(PyObject *name) +{ +#if PY_VERSION_HEX >= 0x03000000 + const char *name_str = PyUnicode_AsUTF8(name); +#else + const char *name_str = PyString_AsString(name); +#endif + if (name_str == NULL) { + return NULL; + } + + return PySys_GetAttrString(name_str); +} + +static inline int +PySys_GetOptionalAttrString(const char *name, PyObject **value) +{ +#if PY_VERSION_HEX >= 0x03000000 + *value = Py_XNewRef(PySys_GetObject(name)); +#else + *value = Py_XNewRef(PySys_GetObject((char*)name)); +#endif + if (*value != NULL) { + return 1; + } + return 0; +} + +static inline int +PySys_GetOptionalAttr(PyObject *name, PyObject **value) +{ +#if PY_VERSION_HEX >= 0x03000000 + const char *name_str = PyUnicode_AsUTF8(name); +#else + const char *name_str = PyString_AsString(name); +#endif + if (name_str == NULL) { + *value = NULL; + return -1; + } + + return PySys_GetOptionalAttrString(name_str, value); +} +#endif // PY_VERSION_HEX < 0x030F00A1 + + +#if PY_VERSION_HEX < 0x030F00A1 +typedef struct PyBytesWriter { + char small_buffer[256]; + PyObject *obj; + Py_ssize_t size; +} PyBytesWriter; + +static inline Py_ssize_t +_PyBytesWriter_GetAllocated(PyBytesWriter *writer) +{ + if (writer->obj == NULL) { + return sizeof(writer->small_buffer); + } + else { + return PyBytes_GET_SIZE(writer->obj); + } +} + + +static inline int +_PyBytesWriter_Resize_impl(PyBytesWriter *writer, Py_ssize_t size, + int resize) +{ + int overallocate = resize; + assert(size >= 0); + + if (size <= _PyBytesWriter_GetAllocated(writer)) { + return 0; + } + + if (overallocate) { +#ifdef MS_WINDOWS + /* On Windows, overallocate by 50% is the best factor */ + if (size <= (PY_SSIZE_T_MAX - size / 2)) { + size += size / 2; + } +#else + /* On Linux, overallocate by 25% is the best factor */ + if (size <= (PY_SSIZE_T_MAX - size / 4)) { + size += size / 4; + } +#endif + } + + if (writer->obj != NULL) { + if (_PyBytes_Resize(&writer->obj, size)) { + return -1; + } + assert(writer->obj != NULL); + } + else { + writer->obj = PyBytes_FromStringAndSize(NULL, size); + if (writer->obj == NULL) { + return -1; + } + + if (resize) { + assert((size_t)size > sizeof(writer->small_buffer)); + memcpy(PyBytes_AS_STRING(writer->obj), + writer->small_buffer, + sizeof(writer->small_buffer)); + } + } + return 0; +} + +static inline void* +PyBytesWriter_GetData(PyBytesWriter *writer) +{ + if (writer->obj == NULL) { + return writer->small_buffer; + } + else { + return PyBytes_AS_STRING(writer->obj); + } +} + +static inline Py_ssize_t +PyBytesWriter_GetSize(PyBytesWriter *writer) +{ + return writer->size; +} + +static inline void +PyBytesWriter_Discard(PyBytesWriter *writer) +{ + if (writer == NULL) { + return; + } + + Py_XDECREF(writer->obj); + PyMem_Free(writer); +} + +static inline PyBytesWriter* +PyBytesWriter_Create(Py_ssize_t size) +{ + if (size < 0) { + PyErr_SetString(PyExc_ValueError, "size must be >= 0"); + return NULL; + } + + PyBytesWriter *writer = (PyBytesWriter*)PyMem_Malloc(sizeof(PyBytesWriter)); + if (writer == NULL) { + PyErr_NoMemory(); + return NULL; + } + + writer->obj = NULL; + writer->size = 0; + + if (size >= 1) { + if (_PyBytesWriter_Resize_impl(writer, size, 0) < 0) { + PyBytesWriter_Discard(writer); + return NULL; + } + writer->size = size; + } + return writer; +} + +static inline PyObject* +PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size) +{ + PyObject *result; + if (size == 0) { + result = PyBytes_FromStringAndSize("", 0); + } + else if (writer->obj != NULL) { + if (size != PyBytes_GET_SIZE(writer->obj)) { + if (_PyBytes_Resize(&writer->obj, size)) { + goto error; + } + } + result = writer->obj; + writer->obj = NULL; + } + else { + result = PyBytes_FromStringAndSize(writer->small_buffer, size); + } + PyBytesWriter_Discard(writer); + return result; + +error: + PyBytesWriter_Discard(writer); + return NULL; +} + +static inline PyObject* +PyBytesWriter_Finish(PyBytesWriter *writer) +{ + return PyBytesWriter_FinishWithSize(writer, writer->size); +} + +static inline PyObject* +PyBytesWriter_FinishWithPointer(PyBytesWriter *writer, void *buf) +{ + Py_ssize_t size = (char*)buf - (char*)PyBytesWriter_GetData(writer); + if (size < 0 || size > _PyBytesWriter_GetAllocated(writer)) { + PyBytesWriter_Discard(writer); + PyErr_SetString(PyExc_ValueError, "invalid end pointer"); + return NULL; + } + + return PyBytesWriter_FinishWithSize(writer, size); +} + +static inline int +PyBytesWriter_Resize(PyBytesWriter *writer, Py_ssize_t size) +{ + if (size < 0) { + PyErr_SetString(PyExc_ValueError, "size must be >= 0"); + return -1; + } + if (_PyBytesWriter_Resize_impl(writer, size, 1) < 0) { + return -1; + } + writer->size = size; + return 0; +} + +static inline int +PyBytesWriter_Grow(PyBytesWriter *writer, Py_ssize_t size) +{ + if (size < 0 && writer->size + size < 0) { + PyErr_SetString(PyExc_ValueError, "invalid size"); + return -1; + } + if (size > PY_SSIZE_T_MAX - writer->size) { + PyErr_NoMemory(); + return -1; + } + size = writer->size + size; + + if (_PyBytesWriter_Resize_impl(writer, size, 1) < 0) { + return -1; + } + writer->size = size; + return 0; +} + +static inline void* +PyBytesWriter_GrowAndUpdatePointer(PyBytesWriter *writer, + Py_ssize_t size, void *buf) +{ + Py_ssize_t pos = (char*)buf - (char*)PyBytesWriter_GetData(writer); + if (PyBytesWriter_Grow(writer, size) < 0) { + return NULL; + } + return (char*)PyBytesWriter_GetData(writer) + pos; +} + +static inline int +PyBytesWriter_WriteBytes(PyBytesWriter *writer, + const void *bytes, Py_ssize_t size) +{ + if (size < 0) { + size_t len = strlen((const char*)bytes); + if (len > (size_t)PY_SSIZE_T_MAX) { + PyErr_NoMemory(); + return -1; + } + size = (Py_ssize_t)len; + } + + Py_ssize_t pos = writer->size; + if (PyBytesWriter_Grow(writer, size) < 0) { + return -1; + } + char *buf = (char*)PyBytesWriter_GetData(writer); + memcpy(buf + pos, bytes, (size_t)size); + return 0; +} + +static inline int +PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...) + Py_GCC_ATTRIBUTE((format(printf, 2, 3))); + +static inline int +PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...) +{ + va_list vargs; + va_start(vargs, format); + PyObject *str = PyBytes_FromFormatV(format, vargs); + va_end(vargs); + + if (str == NULL) { + return -1; + } + int res = PyBytesWriter_WriteBytes(writer, + PyBytes_AS_STRING(str), + PyBytes_GET_SIZE(str)); + Py_DECREF(str); + return res; +} +#endif // PY_VERSION_HEX < 0x030F00A1 + + +#if PY_VERSION_HEX < 0x030F00A1 +static inline PyObject* +PyTuple_FromArray(PyObject *const *array, Py_ssize_t size) +{ + PyObject *tuple = PyTuple_New(size); + if (tuple == NULL) { + return NULL; + } + for (Py_ssize_t i=0; i < size; i++) { + PyObject *item = array[i]; + PyTuple_SET_ITEM(tuple, i, Py_NewRef(item)); + } + return tuple; +} +#endif + + +#if PY_VERSION_HEX < 0x030F00A1 +static inline Py_hash_t +PyUnstable_Unicode_GET_CACHED_HASH(PyObject *op) +{ +#ifdef PYPY_VERSION + (void)op; // unused argument + return -1; +#elif PY_VERSION_HEX >= 0x03000000 + return ((PyASCIIObject*)op)->hash; +#else + return ((PyUnicodeObject*)op)->hash; +#endif +} +#endif + #ifdef __cplusplus } diff --git a/runtests.py b/runtests.py index 5064550..c858516 100755 --- a/runtests.py +++ b/runtests.py @@ -13,7 +13,6 @@ import argparse import os.path import shutil -import subprocess import sys try: from shutil import which @@ -53,6 +52,7 @@ "pypy3.8", "pypy3.9", "pypy3.10", + "pypy3.11", ) diff --git a/tests/setup.py b/tests/setup.py index 2e10c15..33a0e66 100755 --- a/tests/setup.py +++ b/tests/setup.py @@ -13,38 +13,46 @@ # C++ is only supported on Python 3.6 and newer -TEST_CPP = (sys.version_info >= (3, 6)) +TEST_CXX = (sys.version_info >= (3, 6)) SRC_DIR = os.path.normpath(os.path.join(os.path.dirname(__file__), '..')) # Windows uses MSVC compiler MSVC = (os.name == "nt") -# C compiler flags for GCC and clang COMMON_FLAGS = [ - # Treat warnings as error - '-Werror', - # Enable all warnings - '-Wall', '-Wextra', - # Extra warnings - '-Wconversion', - # /usr/lib64/pypy3.7/include/pyport.h:68:20: error: redefinition of typedef - # 'Py_hash_t' is a C11 feature - "-Wno-typedef-redefinition", + '-I' + SRC_DIR, ] -CFLAGS = COMMON_FLAGS + [ - # Use C99 for pythoncapi_compat.c which initializes PyModuleDef with a - # mixture of designated and non-designated initializers - '-std=c99', -] -CPPFLAGS = list(COMMON_FLAGS) -# FIXME: _Py_CAST() emits C++ compilers on Python 3.12. -# See: https://github.com/python/cpython/issues/94731 -if 0: - CPPFLAGS.extend(( - '-Wold-style-cast', - '-Wzero-as-null-pointer-constant', +if not MSVC: + # C compiler flags for GCC and clang + COMMON_FLAGS.extend(( + # Treat warnings as error + '-Werror', + # Enable all warnings + '-Wall', '-Wextra', + # Extra warnings + '-Wconversion', + # /usr/lib64/pypy3.7/include/pyport.h:68:20: error: redefinition of typedef + # 'Py_hash_t' is a C11 feature + '-Wno-typedef-redefinition', + # Formatting checks + '-Wformat', + '-Wformat-nonliteral', + '-Wformat-security', + )) + CFLAGS = COMMON_FLAGS + [ + # Use C99 for pythoncapi_compat.c which initializes PyModuleDef with a + # mixture of designated and non-designated initializers + '-std=c99', + ] +else: + # C compiler flags for MSVC + COMMON_FLAGS.extend(( + # Treat all compiler warnings as compiler errors + '/WX', )) + CFLAGS = list(COMMON_FLAGS) +CXXFLAGS = list(COMMON_FLAGS) def main(): @@ -66,34 +74,31 @@ def main(): # CC env var overrides sysconfig CC variable in setuptools os.environ['CC'] = cmd - cflags = ['-I' + SRC_DIR] - cppflags = list(cflags) - if not MSVC: - cflags.extend(CFLAGS) - cppflags.extend(CPPFLAGS) - # C extension c_ext = Extension( 'test_pythoncapi_compat_cext', sources=['test_pythoncapi_compat_cext.c'], - extra_compile_args=cflags) + extra_compile_args=CFLAGS) extensions = [c_ext] - if TEST_CPP: + if TEST_CXX: # C++ extension # MSVC has /std flag but doesn't support /std:c++11 if not MSVC: versions = [ - ('test_pythoncapi_compat_cpp03ext', '-std=c++03'), - ('test_pythoncapi_compat_cpp11ext', '-std=c++11'), + ('test_pythoncapi_compat_cpp03ext', ['-std=c++03']), + ('test_pythoncapi_compat_cpp11ext', ['-std=c++11']), ] else: - versions = [('test_pythoncapi_compat_cppext', None)] - for name, flag in versions: - flags = list(cppflags) - if flag is not None: - flags.append(flag) + versions = [ + ('test_pythoncapi_compat_cppext', None), + ('test_pythoncapi_compat_cpp14ext', ['/std:c++14', '/Zc:__cplusplus']), + ] + for name, std_flags in versions: + flags = list(CXXFLAGS) + if std_flags is not None: + flags.extend(std_flags) cpp_ext = Extension( name, sources=['test_pythoncapi_compat_cppext.cpp'], diff --git a/tests/test_pythoncapi_compat.py b/tests/test_pythoncapi_compat.py index 17ade5a..8480415 100644 --- a/tests/test_pythoncapi_compat.py +++ b/tests/test_pythoncapi_compat.py @@ -24,12 +24,23 @@ from utils import run_command, command_stdout +# Windows uses MSVC compiler +MSVC = (os.name == "nt") + TESTS = [ ("test_pythoncapi_compat_cext", "C"), - ("test_pythoncapi_compat_cppext", "C++"), - ("test_pythoncapi_compat_cpp03ext", "C++03"), - ("test_pythoncapi_compat_cpp11ext", "C++11"), ] +if not MSVC: + TESTS.extend(( + ("test_pythoncapi_compat_cpp03ext", "C++03"), + ("test_pythoncapi_compat_cpp11ext", "C++11"), + )) +else: + TESTS.extend(( + ("test_pythoncapi_compat_cppext", "C++"), + ("test_pythoncapi_compat_cpp14ext", "C++14"), + )) + VERBOSE = False diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index f813548..e8ada23 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -2,6 +2,7 @@ #undef NDEBUG #include "pythoncapi_compat.h" +#include // T_SHORT, READONLY #ifdef NDEBUG # error "assertions must be enabled" @@ -15,12 +16,14 @@ # define PYTHON3 1 #endif -#if defined(_MSC_VER) && defined(__cplusplus) -# define MODULE_NAME test_pythoncapi_compat_cppext +#if defined(__cplusplus) && __cplusplus >= 201402 +# define MODULE_NAME test_pythoncapi_compat_cpp14ext #elif defined(__cplusplus) && __cplusplus >= 201103 # define MODULE_NAME test_pythoncapi_compat_cpp11ext -#elif defined(__cplusplus) +#elif defined(__cplusplus) && !defined(_MSC_VER) # define MODULE_NAME test_pythoncapi_compat_cpp03ext +#elif defined(__cplusplus) +# define MODULE_NAME test_pythoncapi_compat_cppext #else # define MODULE_NAME test_pythoncapi_compat_cext #endif @@ -756,7 +759,7 @@ test_import(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) static void gc_collect(void) { -#if defined(PYPY_VERSION) +#if defined(PYPY_VERSION) && PY_VERSION_HEX < 0x030B0000 PyObject *mod = PyImport_ImportModule("gc"); assert(mod != _Py_NULL); @@ -1419,14 +1422,82 @@ test_long_api(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) assert(PyLong_GetSign(obj, &sign) == 0); assert(sign == 1); + // test PyLong_IsPositive(), PyLong_IsNegative() and PyLong_IsZero() + assert(PyLong_IsPositive(obj) == 1); + assert(PyLong_IsNegative(obj) == 0); + assert(PyLong_IsZero(obj) == 0); + +#if defined(PYTHON3) && !defined(PYPY_VERSION) + // test import/export API + digit *digits; + PyLongWriter *writer; + static PyLongExport long_export; + + writer = PyLongWriter_Create(1, 1, (void **)&digits); + if (writer == NULL) { + return NULL; + } + PyLongWriter_Discard(writer); + + writer = PyLongWriter_Create(1, 1, (void **)&digits); + if (writer == NULL) { + return NULL; + } + digits[0] = 123; + obj = PyLongWriter_Finish(writer); + if (obj == NULL) { + return NULL; + } + + check_int(obj, -123); + if (PyLong_Export(obj, &long_export) < 0) { + return NULL; + } + assert(long_export.value == -123); + assert(long_export.digits == NULL); + PyLong_FreeExport(&long_export); + Py_DECREF(obj); + + writer = PyLongWriter_Create(0, 5, (void **)&digits); + if (writer == NULL) { + return NULL; + } + digits[0] = 1; + digits[1] = 0; + digits[2] = 0; + digits[3] = 0; + digits[4] = 1; + obj = PyLongWriter_Finish(writer); + if (obj == NULL) { + return NULL; + } + + if (PyLong_Export(obj, &long_export) < 0) { + return NULL; + } + assert(long_export.value == 0); + digits = (digit*)long_export.digits; + assert(digits[0] == 1); + assert(digits[1] == 0); + assert(digits[2] == 0); + assert(digits[3] == 0); + assert(digits[4] == 1); + PyLong_FreeExport(&long_export); + Py_DECREF(obj); + + const PyLongLayout *layout = PyLong_GetNativeLayout(); + assert(layout->digits_order == -1); + assert(layout->digit_size == sizeof(digit)); +#endif // defined(PYTHON3) && !defined(PYPY_VERSION) + Py_RETURN_NONE; } // --- HeapCTypeWithManagedDict -------------------------------------------- -// Py_TPFLAGS_MANAGED_DICT was added to Python 3.11.0a3 -#if PY_VERSION_HEX >= 0x030B00A3 +// Py_TPFLAGS_MANAGED_DICT was added to Python 3.11.0a3 but is not implemented on PyPy +#if PY_VERSION_HEX >= 0x030B00A3 && ! defined(PYPY_VERSION) # define TEST_MANAGED_DICT typedef struct { @@ -1437,12 +1508,14 @@ static int heapmanaged_traverse(PyObject *self, visitproc visit, void *arg) { Py_VISIT(Py_TYPE(self)); + // Test PyObject_VisitManagedDict() return PyObject_VisitManagedDict(self, visit, arg); } static int heapmanaged_clear(PyObject *self) { + // Test PyObject_ClearManagedDict() PyObject_ClearManagedDict(self); return 0; } @@ -1475,6 +1548,7 @@ static PyType_Spec HeapCTypeWithManagedDict_spec = { static PyObject * test_managed_dict(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) { + // Test PyObject_VisitManagedDict() and PyObject_ClearManagedDict() PyObject *type = PyType_FromSpec(&HeapCTypeWithManagedDict_spec); if (type == NULL) { return NULL; @@ -1531,6 +1605,22 @@ test_unicode(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) assert(PyErr_ExceptionMatches(PyExc_MemoryError)); PyErr_Clear(); + // Test PyUnicode_Equal() + assert(PyUnicode_Equal(abc, abc) == 1); + assert(PyUnicode_Equal(abc, abc0def) == 0); + assert(PyUnicode_Equal(abc, Py_True) == -1); + assert(PyErr_ExceptionMatches(PyExc_TypeError)); + PyErr_Clear(); + + // Test PyUnstable_Unicode_GET_CACHED_HASH() +#ifdef PYPY_VERSION + assert(PyUnstable_Unicode_GET_CACHED_HASH(abc) == -1); +#else + Py_hash_t hash = PyObject_Hash(abc); + assert(hash != -1); + assert(PyUnstable_Unicode_GET_CACHED_HASH(abc) == hash); +#endif + Py_DECREF(abc); Py_DECREF(abc0def); Py_RETURN_NONE; @@ -1599,7 +1689,7 @@ test_hash(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) #if ((!defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x030400B1) \ || (defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x03070000 \ - && PYPY_VERSION_NUM >= 0x07090000)) + && PYPY_VERSION_NUM >= 0x07030800)) // Just check that constants are available size_t bits = PyHASH_BITS; assert(bits >= 8); @@ -1611,6 +1701,20 @@ test_hash(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) assert(imag != 0); #endif + // Test Py_HashBuffer() + { + PyObject *abc = PyBytes_FromString("abc"); + if (abc == NULL) { + return NULL; + } + Py_hash_t hash = Py_HashBuffer(PyBytes_AS_STRING(abc), + PyBytes_GET_SIZE(abc)); + Py_hash_t hash2 = PyObject_Hash(abc); + assert(hash == hash2); + + Py_DECREF(abc); + } + Py_RETURN_NONE; } @@ -1662,7 +1766,7 @@ check_get_constant(PyObject* (*get_constant)(unsigned int), int borrowed) // Py_CONSTANT_FALSE obj = get_constant(Py_CONSTANT_FALSE); - assert(obj = Py_False); + assert(obj == Py_False); CLEAR(obj); // Py_CONSTANT_TRUE @@ -1772,6 +1876,11 @@ test_unicodewriter(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) goto error; } + // test PyUnicodeWriter_WriteASCII() + if (PyUnicodeWriter_WriteASCII(writer, " non-ASCII", -1) < 0) { + goto error; + } + // test PyUnicodeWriter_WriteUTF8() if (PyUnicodeWriter_WriteUTF8(writer, " valu\xC3\xA9", -1) < 0) { goto error; @@ -1795,7 +1904,7 @@ test_unicodewriter(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) if (result == NULL) { return NULL; } - assert(PyUnicode_EqualToUTF8(result, "var=long valu\xC3\xA9 'repr'")); + assert(PyUnicode_EqualToUTF8(result, "var=long non-ASCII valu\xC3\xA9 'repr'")); Py_DECREF(result); } @@ -1873,6 +1982,513 @@ test_unicodewriter_format(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) } #endif +static PyObject * +test_uniquely_referenced(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) +{ + PyObject *obj = Py_BuildValue("(s, s)", "hello", "world"); + if (obj == NULL) { + return NULL; + } + + assert(PyUnstable_Object_IsUniquelyReferenced(obj)); + + Py_INCREF(obj); + + assert(!PyUnstable_Object_IsUniquelyReferenced(obj)); + + Py_DECREF(obj); + Py_DECREF(obj); + + Py_RETURN_NONE; +} + +static PyObject * +test_bytes(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + // Test PyBytes_Join() + PyObject *abc = PyBytes_FromString("a b c"); + if (abc == NULL) { + return NULL; + } + PyObject *list = PyObject_CallMethod(abc, "split", NULL); + Py_DECREF(abc); + if (list == NULL) { + return NULL; + } + PyObject *sep = PyBytes_FromString("-"); + if (sep == NULL) { + Py_DECREF(list); + return NULL; + } + + PyObject *join = PyBytes_Join(sep, list); + assert(join != NULL); + assert(PyBytes_Check(join)); + assert(memcmp(PyBytes_AS_STRING(join), "a-b-c", 5) == 0); + Py_DECREF(join); + + Py_DECREF(list); + Py_DECREF(sep); + Py_RETURN_NONE; +} + + +static PyObject * +test_iter(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + // Test PyIter_NextItem() + PyObject *tuple = Py_BuildValue("(i)", 123); + if (tuple == NULL) { + return NULL; + } + PyObject *iter = PyObject_GetIter(tuple); + Py_DECREF(tuple); + if (iter == NULL) { + return NULL; + } + + // first item + PyObject *item = UNINITIALIZED_OBJ; + assert(PyIter_NextItem(iter, &item) == 1); + { + PyObject *expected = PyLong_FromLong(123); + assert(PyObject_RichCompareBool(item, expected, Py_EQ) == 1); + assert(expected != NULL); + Py_DECREF(expected); + } + + // StopIteration + item = UNINITIALIZED_OBJ; + assert(PyIter_NextItem(iter, &item) == 0); + assert(item == NULL); + assert(!PyErr_Occurred()); + + // non-iterable object + item = UNINITIALIZED_OBJ; + assert(PyIter_NextItem(Py_None, &item) == -1); + assert(item == NULL); + assert(PyErr_ExceptionMatches(PyExc_TypeError)); + PyErr_Clear(); + + Py_DECREF(iter); + Py_RETURN_NONE; +} + + +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 PyObject * +test_structmember(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + assert(Py_T_SHORT == T_SHORT); + assert(Py_T_SHORT == T_SHORT); + assert(Py_T_INT == T_INT); + assert(Py_T_LONG == T_LONG); + assert(Py_T_FLOAT == T_FLOAT); + assert(Py_T_DOUBLE == T_DOUBLE); + assert(Py_T_STRING == T_STRING); + assert(_Py_T_OBJECT == T_OBJECT); + assert(Py_T_CHAR == T_CHAR); + assert(Py_T_BYTE == T_BYTE); + assert(Py_T_UBYTE == T_UBYTE); + assert(Py_T_USHORT == T_USHORT); + assert(Py_T_UINT == T_UINT); + assert(Py_T_ULONG == T_ULONG); + assert(Py_T_STRING_INPLACE == T_STRING_INPLACE); + assert(Py_T_BOOL == T_BOOL); + assert(Py_T_OBJECT_EX == T_OBJECT_EX); + assert(Py_T_LONGLONG == T_LONGLONG); + assert(Py_T_ULONGLONG == T_ULONGLONG); + assert(Py_T_PYSSIZET == T_PYSSIZET); +#if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) + assert(_Py_T_NONE == T_NONE); +#endif + assert(Py_READONLY == READONLY); + assert(Py_AUDIT_READ == READ_RESTRICTED); + assert(_Py_WRITE_RESTRICTED == PY_WRITE_RESTRICTED); + + Py_RETURN_NONE; +} + + +static PyObject * +test_file(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + const char *filename = __FILE__; + PyObject *path = create_string(filename); + + FILE *fp = Py_fopen(path, "rb"); + Py_DECREF(path); + assert(fp != NULL); + Py_fclose(fp); + + Py_RETURN_NONE; +} + + +#if 0x03090000 <= PY_VERSION_HEX && !defined(PYPY_VERSION) +static PyObject * +test_config(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + // Test PyConfig_Get() + PyObject *sys = PyImport_ImportModule("sys"); + if (sys == _Py_NULL) { + return _Py_NULL; + } + + PyObject *obj = PyConfig_Get("argv"); + PyObject *sys_attr = PyObject_GetAttrString(sys, "argv"); + assert(obj == sys_attr); + Py_DECREF(obj); + Py_DECREF(sys_attr); + + obj = PyConfig_Get("module_search_paths"); + sys_attr = PyObject_GetAttrString(sys, "path"); + assert(obj == sys_attr); + Py_DECREF(obj); + Py_DECREF(sys_attr); + + obj = PyConfig_Get("xoptions"); + sys_attr = PyObject_GetAttrString(sys, "_xoptions"); + assert(obj == sys_attr); + Py_DECREF(obj); + Py_DECREF(sys_attr); + + obj = PyConfig_Get("use_environment"); + assert(PyBool_Check(obj)); + Py_DECREF(obj); + + obj = PyConfig_Get("verbose"); + assert(PyLong_Check(obj)); + Py_DECREF(obj); + + // Get the last member +#if 0x030A0000 <= PY_VERSION_HEX + obj = PyConfig_Get("warn_default_encoding"); +#else + obj = PyConfig_Get("user_site_directory"); +#endif + assert(PyLong_Check(obj)); + Py_DECREF(obj); + + assert(PyConfig_Get("nonexistent") == NULL); + assert(PyErr_ExceptionMatches(PyExc_ValueError)); + PyErr_Clear(); + + // Test PyConfig_GetInt() + int value = -3; + + assert(PyConfig_GetInt("verbose", &value) == 0); + assert(value >= 0); + + assert(PyConfig_GetInt("argv", &value) == -1); + assert(PyErr_ExceptionMatches(PyExc_TypeError)); + PyErr_Clear(); + + assert(PyConfig_GetInt("nonexistent", &value) == -1); + assert(PyErr_ExceptionMatches(PyExc_ValueError)); + PyErr_Clear(); + + Py_DECREF(sys); + Py_RETURN_NONE; +} +#endif + + +static PyObject * +test_sys(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + const char *stdout_str = "stdout"; + PyObject *stdout_obj = create_string(stdout_str); +#if PYTHON3 + PyObject *sys_stdout = PySys_GetObject(stdout_str); // borrowed ref +#else + PyObject *sys_stdout = PySys_GetObject((char*)stdout_str); // borrowed ref +#endif + const char *nonexistent_str = "nonexistent"; + PyObject *nonexistent_obj = create_string(nonexistent_str); + PyObject *error_obj = PyLong_FromLong(1); + PyObject *value; + + // get sys.stdout + value = PySys_GetAttr(stdout_obj); + assert(value == sys_stdout); + Py_DECREF(value); + + value = PySys_GetAttrString(stdout_str); + assert(value == sys_stdout); + Py_DECREF(value); + + value = UNINITIALIZED_OBJ; + assert(PySys_GetOptionalAttr(stdout_obj, &value) == 1); + assert(value == sys_stdout); + Py_DECREF(value); + + value = UNINITIALIZED_OBJ; + assert(PySys_GetOptionalAttrString(stdout_str, &value) == 1); + assert(value == sys_stdout); + Py_DECREF(value); + + // non existent attribute + value = PySys_GetAttr(nonexistent_obj); + assert(value == NULL); + assert(PyErr_ExceptionMatches(PyExc_RuntimeError)); + PyErr_Clear(); + + value = PySys_GetAttrString(nonexistent_str); + assert(value == NULL); + assert(PyErr_ExceptionMatches(PyExc_RuntimeError)); + PyErr_Clear(); + + value = UNINITIALIZED_OBJ; + assert(PySys_GetOptionalAttr(nonexistent_obj, &value) == 0); + assert(value == NULL); + + value = UNINITIALIZED_OBJ; + assert(PySys_GetOptionalAttrString(nonexistent_str, &value) == 0); + assert(value == NULL); + + // invalid attribute type + value = PySys_GetAttr(error_obj); + assert(value == NULL); + assert(PyErr_ExceptionMatches(PyExc_TypeError)); + PyErr_Clear(); + + value = UNINITIALIZED_OBJ; + assert(PySys_GetOptionalAttr(error_obj, &value) == -1); + assert(value == NULL); + assert(PyErr_ExceptionMatches(PyExc_TypeError)); + PyErr_Clear(); + + Py_DECREF(stdout_obj); + Py_DECREF(nonexistent_obj); + Py_RETURN_NONE; +} + + +static int +test_byteswriter_highlevel(void) +{ + PyObject *obj; + PyBytesWriter *writer = PyBytesWriter_Create(0); + if (writer == NULL) { + goto error; + } + if (PyBytesWriter_WriteBytes(writer, "Hello", -1) < 0) { + goto error; + } + if (PyBytesWriter_Format(writer, " %s!", "World") < 0) { + goto error; + } + + obj = PyBytesWriter_Finish(writer); + if (obj == NULL) { + return -1; + } + assert(PyBytes_Check(obj)); + assert(strcmp(PyBytes_AS_STRING(obj), "Hello World!") == 0); + Py_DECREF(obj); + return 0; + +error: + PyBytesWriter_Discard(writer); + return -1; +} + +static int +test_byteswriter_abc(void) +{ + PyBytesWriter *writer = PyBytesWriter_Create(3); + if (writer == NULL) { + return -1; + } + + char *str = (char*)PyBytesWriter_GetData(writer); + memcpy(str, "abc", 3); + + PyObject *obj = PyBytesWriter_Finish(writer); + if (obj == NULL) { + return -1; + } + assert(PyBytes_Check(obj)); + assert(strcmp(PyBytes_AS_STRING(obj), "abc") == 0); + Py_DECREF(obj); + return 0; +} + +static int +test_byteswriter_grow(void) +{ + PyBytesWriter *writer = PyBytesWriter_Create(10); + if (writer == NULL) { + return -1; + } + + char *buf = (char*)PyBytesWriter_GetData(writer); + memcpy(buf, "Hello ", strlen("Hello ")); + buf += strlen("Hello "); + + buf = (char*)PyBytesWriter_GrowAndUpdatePointer(writer, 10, buf); + if (buf == NULL) { + PyBytesWriter_Discard(writer); + return -1; + } + + memcpy(buf, "World", strlen("World")); + buf += strlen("World"); + + PyObject *obj = PyBytesWriter_FinishWithPointer(writer, buf); + if (obj == NULL) { + return -1; + } + assert(PyBytes_Check(obj)); + assert(strcmp(PyBytes_AS_STRING(obj), "Hello World") == 0); + Py_DECREF(obj); + return 0; +} + +static PyObject * +test_byteswriter(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + if (test_byteswriter_highlevel() < 0) { + return NULL; + } + if (test_byteswriter_abc() < 0) { + return NULL; + } + if (test_byteswriter_grow() < 0) { + return NULL; + } + Py_RETURN_NONE; +} + + +static PyObject* +test_tuple_fromarray(void) +{ + PyObject* array[] = { + PyLong_FromLong(1), + PyLong_FromLong(2), + PyLong_FromLong(3) + }; + PyObject *tuple = PyTuple_FromArray(array, 3); + if (tuple == NULL) { + goto error; + } + + assert(PyTuple_GET_SIZE(tuple) == 3); + assert(PyTuple_GET_ITEM(tuple, 0) == array[0]); + assert(PyTuple_GET_ITEM(tuple, 1) == array[1]); + assert(PyTuple_GET_ITEM(tuple, 2) == array[2]); + + Py_DECREF(tuple); + Py_DECREF(array[0]); + Py_DECREF(array[1]); + Py_DECREF(array[2]); + + // Test PyTuple_FromArray(NULL, 0) + tuple = PyTuple_FromArray(NULL, 0); + if (tuple == NULL) { + return NULL; + } + assert(PyTuple_GET_SIZE(tuple) == 0); + Py_DECREF(tuple); + + Py_RETURN_NONE; + +error: + Py_DECREF(array[0]); + Py_DECREF(array[1]); + Py_DECREF(array[2]); + return NULL; +} + + +static PyObject* +test_tuple(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + return test_tuple_fromarray(); +} + +// Test adapted from CPython's _testcapi/object.c +static int TryIncref_dealloc_called = 0; + +static void +TryIncref_dealloc(PyObject *op) +{ + // PyUnstable_TryIncRef should return 0 if object is being deallocated + assert(Py_REFCNT(op) == 0); + assert(!PyUnstable_TryIncRef(op)); + assert(Py_REFCNT(op) == 0); + + TryIncref_dealloc_called++; + Py_TYPE(op)->tp_free(op); +} + +static PyTypeObject TryIncrefType; + +static PyObject* +test_try_incref(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + TryIncref_dealloc_called = 0; + + PyObject *obj = PyObject_New(PyObject, &TryIncrefType); + if (obj == _Py_NULL) { + return _Py_NULL; + } + + PyUnstable_EnableTryIncRef(obj); + + Py_ssize_t refcount = Py_REFCNT(obj); + assert(PyUnstable_TryIncRef(obj)); + assert(Py_REFCNT(obj) == refcount + 1); + + Py_DECREF(obj); + Py_DECREF(obj); + + assert(TryIncref_dealloc_called == 1); + Py_RETURN_NONE; +} + static struct PyMethodDef methods[] = { {"test_object", test_object, METH_NOARGS, _Py_NULL}, @@ -1917,6 +2533,19 @@ static struct PyMethodDef methods[] = { {"test_unicodewriter_widechar", test_unicodewriter_widechar, METH_NOARGS, _Py_NULL}, {"test_unicodewriter_format", test_unicodewriter_format, METH_NOARGS, _Py_NULL}, #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}, + {"test_structmember", test_structmember, METH_NOARGS, _Py_NULL}, + {"test_file", test_file, METH_NOARGS, _Py_NULL}, +#if 0x03090000 <= PY_VERSION_HEX && !defined(PYPY_VERSION) + {"test_config", test_config, METH_NOARGS, _Py_NULL}, +#endif + {"test_sys", test_sys, METH_NOARGS, _Py_NULL}, + {"test_uniquely_referenced", test_uniquely_referenced, METH_NOARGS, _Py_NULL}, + {"test_byteswriter", test_byteswriter, METH_NOARGS, _Py_NULL}, + {"test_tuple", test_tuple, METH_NOARGS, _Py_NULL}, + {"test_try_incref", test_try_incref, METH_NOARGS, _Py_NULL}, {_Py_NULL, _Py_NULL, 0, _Py_NULL} }; @@ -1945,6 +2574,13 @@ module_exec(PyObject *module) return -1; } #endif + TryIncrefType.tp_name = "TryIncrefType"; + TryIncrefType.tp_basicsize = sizeof(PyObject); + TryIncrefType.tp_dealloc = TryIncref_dealloc; + TryIncrefType.tp_free = PyObject_Del; + if (PyType_Ready(&TryIncrefType) < 0) { + return -1; + } return 0; } diff --git a/tests/test_upgrade_pythoncapi.py b/tests/test_upgrade_pythoncapi.py index bf6f648..48bad12 100644 --- a/tests/test_upgrade_pythoncapi.py +++ b/tests/test_upgrade_pythoncapi.py @@ -43,33 +43,12 @@ def reformat(source): class Tests(unittest.TestCase): maxDiff = 80 * 30 - def _test_patch_file(self, tmp_dir): - # test Patcher.patcher() - source = """ - PyTypeObject* - test_type(PyObject *obj, PyTypeObject *type) - { - Py_TYPE(obj) = type; - return Py_TYPE(obj); - } - """ - expected = """ - #include "pythoncapi_compat.h" - - PyTypeObject* - test_type(PyObject *obj, PyTypeObject *type) - { - Py_SET_TYPE(obj, type); - return Py_TYPE(obj); - } - """ - source = reformat(source) - expected = reformat(expected) - + def _patch_file(self, source, tmp_dir=None): + # test Patcher.patcher() filename = tempfile.mktemp(suffix='.c', dir=tmp_dir) old_filename = filename + ".old" try: - with open(filename, "w", encoding="utf-8") as fp: + with open(filename, "w", encoding="utf-8", newline="") as fp: fp.write(source) old_stderr = sys.stderr @@ -93,10 +72,10 @@ def _test_patch_file(self, tmp_dir): sys.stderr = old_stderr sys.argv = old_argv - with open(filename, encoding="utf-8") as fp: + with open(filename, encoding="utf-8", newline="") as fp: new_contents = fp.read() - with open(old_filename, encoding="utf-8") as fp: + with open(old_filename, encoding="utf-8", newline="") as fp: old_contents = fp.read() finally: try: @@ -108,15 +87,53 @@ def _test_patch_file(self, tmp_dir): except FileNotFoundError: pass - self.assertEqual(new_contents, expected) self.assertEqual(old_contents, source) + return new_contents def test_patch_file(self): - self._test_patch_file(None) + source = """ + PyTypeObject* + test_type(PyObject *obj, PyTypeObject *type) + { + Py_TYPE(obj) = type; + return Py_TYPE(obj); + } + """ + expected = """ + #include "pythoncapi_compat.h" + + PyTypeObject* + test_type(PyObject *obj, PyTypeObject *type) + { + Py_SET_TYPE(obj, type); + return Py_TYPE(obj); + } + """ + source = reformat(source) + expected = reformat(expected) + + new_contents = self._patch_file(source) + self.assertEqual(new_contents, expected) - def test_patch_directory(self): with tempfile.TemporaryDirectory() as tmp_dir: - self._test_patch_file(tmp_dir) + new_contents = self._patch_file(source, tmp_dir) + self.assertEqual(new_contents, expected) + + def test_patch_file_preserve_newlines(self): + source = """ + Py_ssize_t get_size(PyVarObject *obj)\r\n\ + \n\ + { return obj->ob_size; }\r\ + """ + expected = """ + Py_ssize_t get_size(PyVarObject *obj)\r\n\ + \n\ + { return Py_SIZE(obj); }\r\ + """ + source = reformat(source) + expected = reformat(expected) + new_contents = self._patch_file(source) + self.assertEqual(new_contents, expected) def check_replace(self, source, expected, **kwargs): source = reformat(source) diff --git a/upgrade_pythoncapi.py b/upgrade_pythoncapi.py index ea6e722..4348e70 100755 --- a/upgrade_pythoncapi.py +++ b/upgrade_pythoncapi.py @@ -10,7 +10,7 @@ PYTHONCAPI_COMPAT_URL = ('https://raw.githubusercontent.com/python/' - 'pythoncapi_compat/master/pythoncapi_compat.h') + 'pythoncapi-compat/main/pythoncapi_compat.h') PYTHONCAPI_COMPAT_H = 'pythoncapi_compat.h' INCLUDE_PYTHONCAPI_COMPAT = f'#include "{PYTHONCAPI_COMPAT_H}"' INCLUDE_PYTHONCAPI_COMPAT2 = f'#include <{PYTHONCAPI_COMPAT_H}>' @@ -507,7 +507,7 @@ def __init__(self, args=None): self.operations = None self.applied_operations = set() - # Set temporariliy by patch() + # Set temporarily by patch() self._has_pythoncapi_compat = None self._applied_operations = None @@ -554,13 +554,17 @@ def _get_operations(self, parser): return operations def add_line(self, content, line): - line = line + '\n' + # Use the first matching newline + match = re.search(r'(?:\r\n|\n|\r)', content) + newline = match.group(0) if match else '\n' + + line = line + newline # FIXME: tolerate trailing spaces if line not in content: # FIXME: add macro after the first header comment # FIXME: add macro after includes # FIXME: add macro after: #define PY_SSIZE_T_CLEAN - return line + '\n' + content + return line + newline + content else: return content @@ -601,7 +605,7 @@ def patch_file(self, filename): encoding = "utf-8" errors = "surrogateescape" - with open(filename, encoding=encoding, errors=errors) as fp: + with open(filename, encoding=encoding, errors=errors, newline="") as fp: old_contents = fp.read() new_contents, operations = self._patch(old_contents) @@ -620,7 +624,7 @@ def patch_file(self, filename): # If old_filename already exists, replace it os.replace(filename, old_filename) - with open(filename, "w", encoding=encoding, errors=errors) as fp: + with open(filename, "w", encoding=encoding, errors=errors, newline="") as fp: fp.write(new_contents) self.applied_operations |= set(operations)