diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ab5a2bc..8f8b429 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,16 +21,18 @@ 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" - # CPython 3.12 final is scheduled for October 2023: - # https://peps.python.org/pep-0693/ - "3.12" - # Python 2.7 was removed from GHA setup-python in June 2023: - # https://github.com/actions/setup-python/issues/672 + - "3.13" + - "3.13t" + - "3.14" + - "3.14t" + # CPython 3.15 final is scheduled for October 2026: + # https://peps.python.org/pep-0790/ + - "3.15-dev" # PyPy versions: # - https://github.com/actions/setup-python/blob/main/docs/advanced-usage.md @@ -43,6 +45,7 @@ jobs: - "pypy3.8" - "pypy3.9" - "pypy3.10" + - "pypy3.11" # Old PyPy versions # See https://foss.heptapod.net/pypy/pypy/-/issues/3991 @@ -51,28 +54,60 @@ jobs: - "pypy3.7-v7.3.2" include: - # Windows: test old and new Python + # Windows - os: windows-latest - python: 3.6 + python: "3.6" - os: windows-latest - python: 3.11 + 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.11" + - os: windows-latest + python: "3.12" + - os: windows-latest + python: "3.13" + - os: windows-latest + python: "3.13t" + - os: windows-latest + python: "3.14" + - os: windows-latest + python: "3.14t" - # macOS: test old and 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.6 + python: "3.10" - os: macos-latest - python: 3.11 - - os: ubuntu-20.04 - python: 3.5 - - os: ubuntu-20.04 - python: 3.6 + python: "3.11" + - os: macos-latest + python: "3.12" + - os: macos-latest + python: "3.13" + - os: macos-latest + python: "3.13t" + - os: macos-latest + python: "3.14" + - os: macos-latest + python: "3.14t" + + # Ubuntu: test deadsnakes Python versions which are not supported by + # GHA python-versions. + - os: ubuntu-22.04 + python: "3.7" steps: # https://github.com/actions/checkout - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Python # https://github.com/actions/setup-python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} allow-prereleases: true @@ -82,3 +117,21 @@ jobs: run: python -VV - name: Run tests run: python runtests.py --current --verbose + + test_python27: + # Get Python 2.7 from Ubuntu 22.04. + # + # Python 2.7 was removed from GHA setup-python in June 2023: + # https://github.com/actions/setup-python/issues/672 + name: 'Test Python 2.7' + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - name: Install Python 2.7 + run: | + sudo apt-get update + sudo apt-get -yq install python2.7 python2.7-dev + - name: Display the Python version + run: python2.7 -VV + - name: Run tests + run: python2.7 runtests.py --current --verbose diff --git a/.readthedocs.yaml b/.readthedocs.yaml index de85e78..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.11" + python: "3" sphinx: configuration: docs/conf.py diff --git a/docs/api.rst b/docs/api.rst index 9204d9a..2681a4d 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -7,24 +7,263 @@ functions for old Python versions. Supported Python versions: -* Python 3.5 - 3.11 -* PyPy 2.7 -* PyPy 3.6 - 3.9 +* Python 3.6 - 3.15 +* 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. -A C99 subset is required, like ``static inline`` functions: see `PEP 7 -`_. ISO C90 is partially supported -for Python 2.7. +A C11 subset (without optional features) is required, like ``static inline`` +functions: see `PEP 7 `_. ISO C90 +is partially supported for Python 2.7. 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``. + +.. c:function:: int PyUnstable_SetImmortal(PyObject *op) + + See `PyUnstable_SetImmortal() documentation `__. + + Availability: Python 3.13 and newer, not available on PyPy. + +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()`` +* ``PyUnicodeWriter_WriteUCS4()`` +* ``Py_InitializeFromInitConfig()`` Python 3.13 @@ -113,6 +352,83 @@ Python 3.13 Available on Python 3.5.2 and newer. +.. c:function:: int PyUnicode_EqualToUTF8(PyObject *unicode, const char *str) + + See `PyUnicode_EqualToUTF8() documentation `__. + +.. c:function:: int PyUnicode_EqualToUTF8AndSize(PyObject *unicode, const char *str, Py_ssize_t size) + + See `PyUnicode_EqualToUTF8AndSize() documentation `__. + +.. c:function:: int PyList_Extend(PyObject *list, PyObject *iterable) + + See `PyList_Extend() documentation `__. + +.. c:function:: int PyList_Clear(PyObject *list) + + See `PyList_Clear() documentation `__. + +.. c:function:: int PyDict_Pop(PyObject *dict, PyObject *key, PyObject **result) + + See `PyDict_Pop() documentation `__. + +.. c:function:: int PyDict_PopString(PyObject *dict, const char *key, PyObject **result) + + See `PyDict_PopString() documentation `__. + +.. c:function:: Py_hash_t Py_HashPointer(const void *ptr) + + See `Py_HashPointer() documentation `__. + +.. c:type:: PyTime_t + + A timestamp or duration in nanoseconds, represented as a signed 64-bit + integer. + +.. c:var:: PyTime_t PyTime_MIN + + Minimum value of :c:type:`PyTime_t`. + +.. c:var:: PyTime_t PyTime_MAX + + Maximum value of :c:type:`PyTime_t`. + +.. c:function:: double PyTime_AsSecondsDouble(PyTime_t t) + + See `PyTime_AsSecondsDouble() documentation `__. + +.. c:function:: int PyTime_Monotonic(PyTime_t *result) + + See `PyTime_Monotonic() documentation `__. + +.. c:function:: int PyTime_Time(PyTime_t *result) + + See `PyTime_Time() documentation `__. + +.. c:function:: int PyTime_PerfCounter(PyTime_t *result) + + See `PyTime_PerfCounter() documentation `__. + +.. c:function:: PyObject* PyList_GetItemRef(PyObject *op, Py_ssize_t index) + + See `PyList_GetItemRef() documentation `__. + +.. c:function:: int PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value, PyObject **result) + + See `PyDict_SetDefaultRef() documentation `__. + + +Not supported: + +* ``PyErr_FormatUnraisable()``. +* ``PyLong_AsNativeBytes()`` +* ``PyLong_FromNativeBytes()`` +* ``PyLong_FromUnsignedNativeBytes()`` +* ``PyObject_GenericHash()``. +* ``PySys_Audit()``. +* ``PySys_AuditTuple()``. +* ``PyType_GetFullyQualifiedName()`` +* ``PyType_GetModuleName()`` Python 3.12 ----------- @@ -129,6 +445,29 @@ Python 3.12 Not available on PyPy. +.. c:function:: PyObject* Py_GetConstant(unsigned int constant_id) + + See `Py_GetConstant() documentation `__. + +.. c:function:: PyObject* Py_GetConstantBorrowed(unsigned int constant_id) + + See `Py_GetConstantBorrowed() documentation `__. + + +Not supported: + +* ``PyDict_AddWatcher()``, ``PyDict_Watch()``. +* ``PyCode_AddWatcher()``, ``PyCode_ClearWatcher()``. +* ``PyErr_GetRaisedException()``, ``PyErr_SetRaisedException()``. +* ``_PyErr_ChainExceptions1()``. +* ``PyErr_DisplayException()``. +* ``_Py_IsImmortal()``. +* ``Py_NewInterpreterFromConfig()``. +* ``PyException_GetArgs()``, ``PyException_SetArgs()``. +* ``PyEval_SetProfileAllThreads()``, ``PyEval_SetTraceAllThreads()``. +* ``PyFunction_SetVectorcall()``. +* ``PyType_FromMetaclass()``: implementation too big to be backported. +* ``PyVectorcall_Call()``. Python 3.11 ----------- @@ -229,6 +568,15 @@ Python 3.11 Not available on PyPy +Not supported: + +* ``PyType_GetModuleByDef()``. +* ``PyType_GetName()``. +* ``PyType_GetQualName()``. +* ``Py_Version`` constant. +* ``PyErr_GetHandledException()``, ``PyErr_SetHandledException()``. +* ``PyFrame_GetGenerator()``. + Python 3.10 ----------- @@ -260,6 +608,17 @@ Python 3.10 See `PyModule_AddObjectRef() documentation `__. +Not supported: + +* ``PyCodec_Unregister()``. +* ``PyDateTime_DATE_GET_TZINFO()``, ``PyDateTime_TIME_GET_TZINFO()``. +* ``PyErr_SetInterruptEx()``. +* ``PyGC_Enable()``, ``PyGC_Disable()`` and ``PyGC_IsEnabled()``. +* ``PyIter_Send()``. +* ``PySet_CheckExact()``. +* ``Py_TPFLAGS_DISALLOW_INSTANTIATION`` constant. +* ``Py_TPFLAGS_IMMUTABLETYPE`` constant. + Python 3.9 ---------- @@ -302,6 +661,12 @@ PyObject See `PY_VECTORCALL_ARGUMENTS_OFFSET documentation `__. +Not supported: + +* ``PyVectorcall_CallMethod()``. +* ``PyType_FromModuleAndSpec()`` + + PyFrameObject ^^^^^^^^^^^^^ @@ -366,6 +731,35 @@ Module helper See `PyModule_AddType() documentation `__. +Python 3.8 +---------- + +Not supported: + +* ``PyCode_NewWithPosOnlyArgs()``. + +Python 3.7 +---------- + +Not supported: + +* ``PyImport_GetModule()``. +* ``PyInterpreterState_GetID()``. +* ``PySlice_Unpack()``, ``PySlice_AdjustIndices()``. +* ``PyTimeZone_FromOffset()``, ``PyTimeZone_FromOffsetAndName()``. +* ``Py_RETURN_RICHCOMPARE()``. +* ``Py_UNREACHABLE`` macro. + +Python 3.6 +---------- + +Not supported: + +* ``PyErr_ResourceWarning()``. +* ``PyErr_SetImportErrorSubclass()``. +* ``PyOS_FSPath()``. +* ``Py_FinalizeEx()``. + Python 3.5.2 ------------ @@ -373,6 +767,15 @@ Python 3.5.2 .. c:macro:: Py_XSETREF(op, op2) +Not supported: + +* ``PyCodec_NameReplaceErrors()``. +* ``PyErr_FormatV()``. +* ``PyExc_RecursionError``. +* ``PyModule_FromDefAndSpec()``, ``PyModule_FromDefAndSpec2()``, + and ``PyModule_ExecDef()``. +* ``PyNumber_MatrixMultiply()`` and ``PyNumber_InPlaceMatrixMultiply()``. + Python 3.4 ---------- @@ -380,6 +783,24 @@ Python 3.4 See `Py_UNUSED() documentation `__. +Python 3.2 +---------- + +Not supported: + +* ``Py_VA_COPY``. +* ``PySys_SetArgvEx()``. +* ``PyLong_AsLongLongAndOverflow()``. +* ``PyErr_NewExceptionWithDoc()``. + +Python 3.1 +---------- + +Not supported: + +* ``PyOS_string_to_double()``. +* ``PyCapsule`` API. + Borrow variant -------------- diff --git a/docs/changelog.rst b/docs/changelog.rst index 9fd3d06..c68d88b 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,154 @@ Changelog ========= +* 2026-02-12: Add functions: + + * ``PyUnstable_SetImmortal()`` + +* 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()`` + * ``PyUnicodeWriter_Discard()`` + * ``PyUnicodeWriter_Finish()`` + * ``PyUnicodeWriter_WriteChar()`` + * ``PyUnicodeWriter_WriteUTF8()`` + * ``PyUnicodeWriter_WriteStr()`` + * ``PyUnicodeWriter_WriteRepr()`` + * ``PyUnicodeWriter_WriteSubstring()`` + * ``PyUnicodeWriter_WriteWideChar()`` + * ``PyUnicodeWriter_Format()`` + +* 2024-06-03: Add ``PyLong_GetSign()``. +* 2024-04-23: Drop Python 3.5 support. It cannot be tested anymore (pip fails). +* 2024-04-02: Add ``PyDict_SetDefaultRef()`` function. +* 2024-03-29: Add ``PyList_GetItemRef()`` function. +* 2024-03-21: Add functions: + + * ``Py_GetConstant()`` + * ``Py_GetConstantBorrowed()`` + +* 2024-03-09: Add hash constants: + + * ``PyHASH_BITS`` + * ``PyHASH_IMAG`` + * ``PyHASH_INF`` + * ``PyHASH_MODULUS`` + +* 2024-02-20: Add PyTime API: + + * ``PyTime_t`` type + * ``PyTime_MIN`` and ``PyTime_MAX`` constants + * ``PyTime_AsSecondsDouble()`` + * ``PyTime_Monotonic()`` + * ``PyTime_PerfCounter()`` + * ``PyTime_Time()`` + +* 2023-12-15: Add function ``Py_HashPointer()``. +* 2023-11-14: Add functions: + + * ``PyDict_Pop()`` + * ``PyDict_PopString()`` + +* 2023-11-13: Add functions: + + * ``PyList_Extend()`` + * ``PyList_Clear()`` + +* 2023-10-04: Add functions: + + * ``PyUnicode_EqualToUTF8()`` + * ``PyUnicode_EqualToUTF8AndSize()`` + * 2023-10-03: Add functions: * ``PyObject_VisitManagedDict()`` diff --git a/docs/users.rst b/docs/users.rst index b817996..abf4307 100644 --- a/docs/users.rst +++ b/docs/users.rst @@ -26,6 +26,9 @@ Examples of projects using pythoncapi_compat.h * `mypy `_ (mypyc, `commit `__) +* `numpy `_: + `pythoncapi-compat Git submodule + `_ * `pybluez `_ (`commit `__) * `python-snappy `_ @@ -42,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 dcd97ff..08b6915 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) @@ -30,10 +31,12 @@ extern "C" { # define _Py_CAST(type, expr) ((type)(expr)) #endif -// On C++11 and newer, _Py_NULL is defined as nullptr on C++11, -// otherwise it is defined as NULL. +// 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. #ifndef _Py_NULL -# if defined(__cplusplus) && __cplusplus >= 201103 +# if (defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L) \ + || (defined(__cplusplus) && __cplusplus >= 201103) # define _Py_NULL nullptr # else # define _Py_NULL NULL @@ -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 @@ -853,7 +863,7 @@ PyModule_Add(PyObject *mod, const char *name, PyObject *value) // gh-108014 added Py_IsFinalizing() to Python 3.13.0a1 // bpo-1856 added _Py_Finalizing to Python 3.2.1b1. -// Py_IsFinalizing() was added to PyPy 7.3.0. +// _Py_IsFinalizing() was added to PyPy 7.3.0. #if (0x030201B1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030D00A1) \ && (!defined(PYPY_VERSION_NUM) || PYPY_VERSION_NUM >= 0x7030000) static inline int Py_IsFinalizing(void) @@ -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); @@ -939,6 +949,1764 @@ PyThreadState_GetUnchecked(void) } #endif +// gh-110289 added PyUnicode_EqualToUTF8() and PyUnicode_EqualToUTF8AndSize() +// to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyUnicode_EqualToUTF8AndSize(PyObject *unicode, const char *str, Py_ssize_t str_len) +{ + Py_ssize_t len; + const void *utf8; + PyObject *exc_type, *exc_value, *exc_tb; + int res; + + // API cannot report errors so save/restore the exception + PyErr_Fetch(&exc_type, &exc_value, &exc_tb); + + // Python 3.3.0a1 added PyUnicode_AsUTF8AndSize() +#if PY_VERSION_HEX >= 0x030300A1 + if (PyUnicode_IS_ASCII(unicode)) { + utf8 = PyUnicode_DATA(unicode); + len = PyUnicode_GET_LENGTH(unicode); + } + else { + utf8 = PyUnicode_AsUTF8AndSize(unicode, &len); + if (utf8 == NULL) { + // Memory allocation failure. The API cannot report error, + // so ignore the exception and return 0. + res = 0; + goto done; + } + } + + if (len != str_len) { + res = 0; + goto done; + } + res = (memcmp(utf8, str, (size_t)len) == 0); +#else + PyObject *bytes = PyUnicode_AsUTF8String(unicode); + if (bytes == NULL) { + // Memory allocation failure. The API cannot report error, + // so ignore the exception and return 0. + res = 0; + goto done; + } + +#if PY_VERSION_HEX >= 0x03000000 + len = PyBytes_GET_SIZE(bytes); + utf8 = PyBytes_AS_STRING(bytes); +#else + len = PyString_GET_SIZE(bytes); + utf8 = PyString_AS_STRING(bytes); +#endif + if (len != str_len) { + Py_DECREF(bytes); + res = 0; + goto done; + } + + res = (memcmp(utf8, str, (size_t)len) == 0); + Py_DECREF(bytes); +#endif + +done: + PyErr_Restore(exc_type, exc_value, exc_tb); + return res; +} + +static inline int +PyUnicode_EqualToUTF8(PyObject *unicode, const char *str) +{ + return PyUnicode_EqualToUTF8AndSize(unicode, str, (Py_ssize_t)strlen(str)); +} +#endif + + +// gh-111138 added PyList_Extend() and PyList_Clear() to Python 3.13.0a2 +#if PY_VERSION_HEX < 0x030D00A2 +static inline int +PyList_Extend(PyObject *list, PyObject *iterable) +{ + return PyList_SetSlice(list, PY_SSIZE_T_MAX, PY_SSIZE_T_MAX, iterable); +} + +static inline int +PyList_Clear(PyObject *list) +{ + return PyList_SetSlice(list, 0, PY_SSIZE_T_MAX, NULL); +} +#endif + +// gh-111262 added PyDict_Pop() and PyDict_PopString() to Python 3.13.0a2 +#if PY_VERSION_HEX < 0x030D00A2 +static inline int +PyDict_Pop(PyObject *dict, PyObject *key, PyObject **result) +{ + PyObject *value; + + if (!PyDict_Check(dict)) { + PyErr_BadInternalCall(); + if (result) { + *result = NULL; + } + return -1; + } + + // bpo-16991 added _PyDict_Pop() to Python 3.5.0b2. + // Python 3.6.0b3 changed _PyDict_Pop() first argument type to PyObject*. + // Python 3.13.0a1 removed _PyDict_Pop(). +#if defined(PYPY_VERSION) || PY_VERSION_HEX < 0x030500b2 || PY_VERSION_HEX >= 0x030D0000 + value = PyObject_CallMethod(dict, "pop", "O", key); +#elif PY_VERSION_HEX < 0x030600b3 + value = _PyDict_Pop(_Py_CAST(PyDictObject*, dict), key, NULL); +#else + value = _PyDict_Pop(dict, key, NULL); +#endif + if (value == NULL) { + if (result) { + *result = NULL; + } + if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_KeyError)) { + return -1; + } + PyErr_Clear(); + return 0; + } + if (result) { + *result = value; + } + else { + Py_DECREF(value); + } + return 1; +} + +static inline int +PyDict_PopString(PyObject *dict, const char *key, PyObject **result) +{ + PyObject *key_obj = PyUnicode_FromString(key); + if (key_obj == NULL) { + if (result != NULL) { + *result = NULL; + } + return -1; + } + + int res = PyDict_Pop(dict, key_obj, result); + Py_DECREF(key_obj); + return res; +} +#endif + + +#if PY_VERSION_HEX < 0x030200A4 +// Python 3.2.0a4 added Py_hash_t type +typedef Py_ssize_t Py_hash_t; +#endif + + +// gh-111545 added Py_HashPointer() to Python 3.13.0a3 +#if PY_VERSION_HEX < 0x030D00A3 +static inline Py_hash_t Py_HashPointer(const void *ptr) +{ +#if PY_VERSION_HEX >= 0x030900A4 && !defined(PYPY_VERSION) + return _Py_HashPointer(ptr); +#else + return _Py_HashPointer(_Py_CAST(void*, ptr)); +#endif +} +#endif + + +// Python 3.13a4 added a PyTime API. +// Use the private API added to Python 3.5. +#if PY_VERSION_HEX < 0x030D00A4 && PY_VERSION_HEX >= 0x03050000 +typedef _PyTime_t PyTime_t; +#define PyTime_MIN _PyTime_MIN +#define PyTime_MAX _PyTime_MAX + +static inline double PyTime_AsSecondsDouble(PyTime_t t) +{ return _PyTime_AsSecondsDouble(t); } + +static inline int PyTime_Monotonic(PyTime_t *result) +{ return _PyTime_GetMonotonicClockWithInfo(result, NULL); } + +static inline int PyTime_Time(PyTime_t *result) +{ return _PyTime_GetSystemClockWithInfo(result, NULL); } + +static inline int PyTime_PerfCounter(PyTime_t *result) +{ +#if PY_VERSION_HEX >= 0x03070000 && !defined(PYPY_VERSION) + return _PyTime_GetPerfCounterWithInfo(result, NULL); +#elif PY_VERSION_HEX >= 0x03070000 + // Call time.perf_counter_ns() and convert Python int object to PyTime_t. + // Cache time.perf_counter_ns() function for best performance. + static PyObject *func = NULL; + if (func == NULL) { + PyObject *mod = PyImport_ImportModule("time"); + if (mod == NULL) { + return -1; + } + + func = PyObject_GetAttrString(mod, "perf_counter_ns"); + Py_DECREF(mod); + if (func == NULL) { + return -1; + } + } + + PyObject *res = PyObject_CallNoArgs(func); + if (res == NULL) { + return -1; + } + long long value = PyLong_AsLongLong(res); + Py_DECREF(res); + + if (value == -1 && PyErr_Occurred()) { + return -1; + } + + Py_BUILD_ASSERT(sizeof(value) >= sizeof(PyTime_t)); + *result = (PyTime_t)value; + return 0; +#else + // Call time.perf_counter() and convert C double to PyTime_t. + // Cache time.perf_counter() function for best performance. + static PyObject *func = NULL; + if (func == NULL) { + PyObject *mod = PyImport_ImportModule("time"); + if (mod == NULL) { + return -1; + } + + func = PyObject_GetAttrString(mod, "perf_counter"); + Py_DECREF(mod); + if (func == NULL) { + return -1; + } + } + + PyObject *res = PyObject_CallNoArgs(func); + if (res == NULL) { + return -1; + } + double d = PyFloat_AsDouble(res); + Py_DECREF(res); + + if (d == -1.0 && PyErr_Occurred()) { + return -1; + } + + // Avoid floor() to avoid having to link to libm + *result = (PyTime_t)(d * 1e9); + return 0; +#endif +} + +#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.8. +#if (!defined(PyHASH_BITS) \ + && ((!defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x030400B1) \ + || (defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x03070000 \ + && PYPY_VERSION_NUM >= 0x07030800))) +# define PyHASH_BITS _PyHASH_BITS +# define PyHASH_MODULUS _PyHASH_MODULUS +# define PyHASH_INF _PyHASH_INF +# define PyHASH_IMAG _PyHASH_IMAG +#endif + + +// gh-111545 added Py_GetConstant() and Py_GetConstantBorrowed() +// to Python 3.13.0a6 +#if PY_VERSION_HEX < 0x030D00A6 && !defined(Py_CONSTANT_NONE) + +#define Py_CONSTANT_NONE 0 +#define Py_CONSTANT_FALSE 1 +#define Py_CONSTANT_TRUE 2 +#define Py_CONSTANT_ELLIPSIS 3 +#define Py_CONSTANT_NOT_IMPLEMENTED 4 +#define Py_CONSTANT_ZERO 5 +#define Py_CONSTANT_ONE 6 +#define Py_CONSTANT_EMPTY_STR 7 +#define Py_CONSTANT_EMPTY_BYTES 8 +#define Py_CONSTANT_EMPTY_TUPLE 9 + +static inline PyObject* Py_GetConstant(unsigned int constant_id) +{ + static PyObject* constants[Py_CONSTANT_EMPTY_TUPLE + 1] = {NULL}; + + if (constants[Py_CONSTANT_NONE] == NULL) { + constants[Py_CONSTANT_NONE] = Py_None; + constants[Py_CONSTANT_FALSE] = Py_False; + constants[Py_CONSTANT_TRUE] = Py_True; + constants[Py_CONSTANT_ELLIPSIS] = Py_Ellipsis; + constants[Py_CONSTANT_NOT_IMPLEMENTED] = Py_NotImplemented; + + constants[Py_CONSTANT_ZERO] = PyLong_FromLong(0); + if (constants[Py_CONSTANT_ZERO] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_ONE] = PyLong_FromLong(1); + if (constants[Py_CONSTANT_ONE] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_EMPTY_STR] = PyUnicode_FromStringAndSize("", 0); + if (constants[Py_CONSTANT_EMPTY_STR] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_EMPTY_BYTES] = PyBytes_FromStringAndSize("", 0); + if (constants[Py_CONSTANT_EMPTY_BYTES] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_EMPTY_TUPLE] = PyTuple_New(0); + if (constants[Py_CONSTANT_EMPTY_TUPLE] == NULL) { + goto fatal_error; + } + // goto dance to avoid compiler warnings about Py_FatalError() + goto init_done; + +fatal_error: + // This case should never happen + Py_FatalError("Py_GetConstant() failed to get constants"); + } + +init_done: + if (constant_id <= Py_CONSTANT_EMPTY_TUPLE) { + return Py_NewRef(constants[constant_id]); + } + else { + PyErr_BadInternalCall(); + return NULL; + } +} + +static inline PyObject* Py_GetConstantBorrowed(unsigned int constant_id) +{ + PyObject *obj = Py_GetConstant(constant_id); + Py_XDECREF(obj); + return obj; +} +#endif + + +// gh-114329 added PyList_GetItemRef() to Python 3.13.0a4 +#if PY_VERSION_HEX < 0x030D00A4 +static inline PyObject * +PyList_GetItemRef(PyObject *op, Py_ssize_t index) +{ + PyObject *item = PyList_GetItem(op, index); + Py_XINCREF(item); + return item; +} +#endif + + +// gh-114329 added PyList_GetItemRef() to Python 3.13.0a4 +#if PY_VERSION_HEX < 0x030D00A4 +static inline int +PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value, + PyObject **result) +{ + PyObject *value; + if (PyDict_GetItemRef(d, key, &value) < 0) { + // get error + if (result) { + *result = NULL; + } + return -1; + } + if (value != NULL) { + // present + if (result) { + *result = value; + } + else { + Py_DECREF(value); + } + return 1; + } + + // missing: set the item + if (PyDict_SetItem(d, key, default_value) < 0) { + // set error + if (result) { + *result = NULL; + } + return -1; + } + if (result) { + *result = Py_NewRef(default_value); + } + return 0; +} +#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; + +static inline void PyUnicodeWriter_Discard(PyUnicodeWriter *writer) +{ + _PyUnicodeWriter_Dealloc((_PyUnicodeWriter*)writer); + PyMem_Free(writer); +} + +static inline PyUnicodeWriter* PyUnicodeWriter_Create(Py_ssize_t length) +{ + if (length < 0) { + PyErr_SetString(PyExc_ValueError, + "length must be positive"); + return NULL; + } + + const size_t size = sizeof(_PyUnicodeWriter); + PyUnicodeWriter *pub_writer = (PyUnicodeWriter *)PyMem_Malloc(size); + if (pub_writer == _Py_NULL) { + PyErr_NoMemory(); + return _Py_NULL; + } + _PyUnicodeWriter *writer = (_PyUnicodeWriter *)pub_writer; + + _PyUnicodeWriter_Init(writer); + if (_PyUnicodeWriter_Prepare(writer, length, 127) < 0) { + PyUnicodeWriter_Discard(pub_writer); + return NULL; + } + writer->overallocate = 1; + return pub_writer; +} + +static inline PyObject* PyUnicodeWriter_Finish(PyUnicodeWriter *writer) +{ + PyObject *str = _PyUnicodeWriter_Finish((_PyUnicodeWriter*)writer); + assert(((_PyUnicodeWriter*)writer)->buffer == NULL); + PyMem_Free(writer); + return str; +} + +static inline int +PyUnicodeWriter_WriteChar(PyUnicodeWriter *writer, Py_UCS4 ch) +{ + if (ch > 0x10ffff) { + PyErr_SetString(PyExc_ValueError, + "character must be in range(0x110000)"); + return -1; + } + + return _PyUnicodeWriter_WriteChar((_PyUnicodeWriter*)writer, ch); +} + +static inline int +PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj) +{ + PyObject *str = PyObject_Str(obj); + if (str == NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str); + Py_DECREF(str); + return res; +} + +static inline int +PyUnicodeWriter_WriteRepr(PyUnicodeWriter *writer, PyObject *obj) +{ + if (obj == NULL) { + return _PyUnicodeWriter_WriteASCIIString((_PyUnicodeWriter*)writer, + "", 6); + } + + PyObject *str = PyObject_Repr(obj); + if (str == NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str); + Py_DECREF(str); + return res; +} + +static inline int +PyUnicodeWriter_WriteUTF8(PyUnicodeWriter *writer, + const char *str, Py_ssize_t size) +{ + if (size < 0) { + size = (Py_ssize_t)strlen(str); + } + + PyObject *str_obj = PyUnicode_FromStringAndSize(str, size); + if (str_obj == _Py_NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str_obj); + Py_DECREF(str_obj); + 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) +{ + if (size < 0) { + size = (Py_ssize_t)wcslen(str); + } + + PyObject *str_obj = PyUnicode_FromWideChar(str, size); + if (str_obj == _Py_NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str_obj); + Py_DECREF(str_obj); + return res; +} + +static inline int +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 %s", + Py_TYPE(str)->tp_name); + return -1; + } + if (start < 0 || start > end) { + PyErr_Format(PyExc_ValueError, "invalid start argument"); + return -1; + } + if (end > PyUnicode_GET_LENGTH(str)) { + PyErr_Format(PyExc_ValueError, "invalid end argument"); + return -1; + } + + return _PyUnicodeWriter_WriteSubstring((_PyUnicodeWriter*)writer, str, + start, end); +} + +static inline int +PyUnicodeWriter_Format(PyUnicodeWriter *writer, const char *format, ...) +{ + va_list vargs; + va_start(vargs, format); + PyObject *str = PyUnicode_FromFormatV(format, vargs); + va_end(vargs); + if (str == _Py_NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str); + Py_DECREF(str); + return res; +} +#endif // PY_VERSION_HEX < 0x030E0000 + +// gh-116560 added PyLong_GetSign() to Python 3.14.0a0 +#if PY_VERSION_HEX < 0x030E00A0 +static inline int PyLong_GetSign(PyObject *obj, int *sign) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expect int, got %s", Py_TYPE(obj)->tp_name); + return -1; + } + + *sign = _PyLong_Sign(obj); + return 0; +} +#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 + +#if PY_VERSION_HEX >= 0x030d0000 && !defined(PYPY_VERSION) +PyAPI_FUNC(int) _PyUnicode_Equal(PyObject *str1, PyObject *str2); +#endif + +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) + 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 + +#if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) +PyAPI_FUNC(Py_hash_t) _Py_HashBytes(const void *src, Py_ssize_t len); +#endif + +static inline Py_hash_t Py_HashBuffer(const void *ptr, Py_ssize_t len) +{ +#if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) + 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 + +#if 0x030400A2 <= PY_VERSION_HEX && !defined(PYPY_VERSION) +PyAPI_FUNC(FILE*) _Py_fopen_obj(PyObject *path, const char *mode); +#endif + +static inline FILE* Py_fopen(PyObject *path, const char *mode) +{ +#if 0x030400A2 <= PY_VERSION_HEX && !defined(PYPY_VERSION) + 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 + +#if 0x030D0000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030F00A7 && !defined(PYPY_VERSION) +// Immortal objects were implemented in Python 3.12, however there is no easy API +// to make objects immortal until 3.14 which has _Py_SetImmortal(). Since +// immortal objects are primarily needed for free-threading, this API is implemented +// for 3.14 using _Py_SetImmortal() and uses private macros on 3.13. +#if 0x030E0000 <= PY_VERSION_HEX +PyAPI_FUNC(void) _Py_SetImmortal(PyObject *op); +#endif + +static inline int +PyUnstable_SetImmortal(PyObject *op) +{ + assert(op != NULL); + if (!PyUnstable_Object_IsUniquelyReferenced(op) || PyUnicode_Check(op)) { + return 0; + } +#if 0x030E0000 <= PY_VERSION_HEX + _Py_SetImmortal(op); +#else + // Python 3.13 doesn't export _Py_SetImmortal() function + if (PyObject_GC_IsTracked(op)) { + PyObject_GC_UnTrack(op); + } +#ifdef Py_GIL_DISABLED + op->ob_tid = _Py_UNOWNED_TID; + op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL; + op->ob_ref_shared = 0; +#else + op->ob_refcnt = _Py_IMMORTAL_REFCNT; +#endif +#endif + return 1; +} +#endif #ifdef __cplusplus } diff --git a/runtests.py b/runtests.py index 38507a1..fdee19e 100755 --- a/runtests.py +++ b/runtests.py @@ -4,15 +4,15 @@ Usage:: - python3 test_matrix.py - python3 test_matrix.py -v # verbose mode + python3 runtests.py + python3 runtests.py --verbose + python3 runtests.py --current --verbose """ from __future__ import absolute_import from __future__ import print_function import argparse import os.path import shutil -import subprocess import sys try: from shutil import which @@ -29,11 +29,10 @@ TEST_UPGRADE = os.path.join(TEST_DIR, "test_upgrade_pythoncapi.py") PYTHONS = ( + # CPython "python3-debug", "python3", "python2.7", - "python3.4", - "python3.5", "python3.6", "python3.7", "python3.8", @@ -41,6 +40,14 @@ "python3.10", "python3.11", "python3.12", + "python3.13", + "python3.13t", + "python3.14", + "python3.14t", + "python3.15", + "python3.15t", + + # PyPy "pypy", "pypy2", "pypy2.7", @@ -50,6 +57,7 @@ "pypy3.8", "pypy3.9", "pypy3.10", + "pypy3.11", ) diff --git a/tests/setup.py b/tests/setup.py index 4deaa36..b25bc8d 100755 --- a/tests/setup.py +++ b/tests/setup.py @@ -13,42 +13,68 @@ # C++ is only supported on Python 3.6 and newer -TEST_CPP = (sys.version_info >= (3, 6)) -if 0x30b0000 <= sys.hexversion <= 0x30b00b3: - # Don't test C++ on Python 3.11b1 - 3.11b3: these versions have C++ - # compatibility issues. - TEST_CPP = False +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', + # Formatting checks + '-Wformat', + '-Wformat-nonliteral', + '-Wformat-security', + )) + CFLAGS = COMMON_FLAGS +else: + # C compiler flags for MSVC + COMMON_FLAGS.extend(( + # Treat all compiler warnings as compiler errors + '/WX', )) + # Python 3.11 and older emits C4100 "unreferenced parameter" warnings + # on Py_UNUSED() parameters. Py_UNUSED() was modified in Python 3.12 + # to support MSVC. + if sys.version_info >= (3, 12): + COMMON_FLAGS.extend(( + # Display warnings level 1 to 4 + '/W4', + )) + CFLAGS = list(COMMON_FLAGS) +CXXFLAGS = list(COMMON_FLAGS) + +if not MSVC: + C_VERSIONS = ('c99', 'c11') +else: + # MSVC doesn't support /std:c99 flag + C_VERSIONS = ('c11',) + +if not MSVC: + CXX_VERSIONS = [ + ('test_pythoncapi_compat_cpp03ext', ['-std=c++03']), + ('test_pythoncapi_compat_cpp11ext', ['-std=c++11']), + ('test_pythoncapi_compat_cpp14ext', ['-std=c++14']), + ('test_pythoncapi_compat_cpp17ext', ['-std=c++17']), + ('test_pythoncapi_compat_cpp20ext', ['-std=c++20']), + ] +else: + # MSVC doesn't support /std:c++11 + CXX_VERSIONS = [ + ('test_pythoncapi_compat_cppext', None), + ('test_pythoncapi_compat_cpp14ext', ['/std:c++14', '/Zc:__cplusplus']), + ] def main(): @@ -70,34 +96,25 @@ 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) - extensions = [c_ext] - - if TEST_CPP: - # C++ extension - - # MSVC has /std flag but doesn't support /std:c++11 + extensions = [] + for std in C_VERSIONS: if not MSVC: - versions = [ - ('test_pythoncapi_compat_cpp03ext', '-std=c++03'), - ('test_pythoncapi_compat_cpp11ext', '-std=c++11'), - ] + cflags = CFLAGS + ['-std=%s' % std] else: - versions = [('test_pythoncapi_compat_cppext', None)] - for name, flag in versions: - flags = list(cppflags) - if flag is not None: - flags.append(flag) + cflags = CFLAGS + ['/std:%s' % std] + c_ext = Extension( + 'test_pythoncapi_compat_cext_%s' % std, + sources=['test_pythoncapi_compat_cext.c'], + extra_compile_args=cflags) + extensions.append(c_ext) + + if TEST_CXX: + # C++ extension + for name, std_flags in CXX_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 948906c..93005cd 100644 --- a/tests/test_pythoncapi_compat.py +++ b/tests/test_pythoncapi_compat.py @@ -19,17 +19,43 @@ except ImportError: # Python 2 faulthandler = None +try: + import sysconfig +except ImportError: + # Python 3.1 and older + sysconfig = None # test.utils from utils import run_command, command_stdout -TESTS = [ - ("test_pythoncapi_compat_cext", "C"), - ("test_pythoncapi_compat_cppext", "C++"), - ("test_pythoncapi_compat_cpp03ext", "C++03"), - ("test_pythoncapi_compat_cpp11ext", "C++11"), -] +# Windows uses MSVC compiler +MSVC = (os.name == "nt") + +# C++ is only supported on Python 3.6 and newer +TEST_CXX = (sys.version_info >= (3, 6)) + +if not MSVC: + C_TESTS = [ + ("test_pythoncapi_compat_cext_c99", "C99"), + ("test_pythoncapi_compat_cext_c11", "C11"), + ] + CXX_TESTS = [ + ("test_pythoncapi_compat_cpp03ext", "C++03"), + ("test_pythoncapi_compat_cpp11ext", "C++11"), + ("test_pythoncapi_compat_cpp14ext", "C++14"), + ("test_pythoncapi_compat_cpp17ext", "C++17"), + ("test_pythoncapi_compat_cpp20ext", "C++20"), + ] +else: + C_TESTS = [ + ("test_pythoncapi_compat_cext_c11", "C11"), + ] + CXX_TESTS = [ + ("test_pythoncapi_compat_cppext", "C++"), + ("test_pythoncapi_compat_cpp14ext", "C++14"), + ] + VERBOSE = False @@ -38,9 +64,7 @@ def display_title(title): if not VERBOSE: return - ver = sys.version_info - title = "Python %s.%s: %s" % (ver.major, ver.minor, title) - + title = "%s: %s" % (python_version(), title) print(title) print("=" * len(title)) print() @@ -70,9 +94,12 @@ def import_tests(module_name): if not pythonpath: raise Exception("Failed to find the build directory") - sys.path.append(pythonpath) - - return __import__(module_name) + old_sys_path = list(sys.path) + try: + sys.path.append(pythonpath) + return __import__(module_name) + finally: + sys.path[:] = old_sys_path def _run_tests(tests, verbose): @@ -83,10 +110,13 @@ def _run_tests(tests, verbose): test_func() +_HAS_CLEAR_INTERNAL_CACHES = hasattr(sys, '_clear_internal_caches') _HAS_CLEAR_TYPE_CACHE = hasattr(sys, '_clear_type_cache') def _refleak_cleanup(): - if _HAS_CLEAR_TYPE_CACHE: + if _HAS_CLEAR_INTERNAL_CACHES: + sys._clear_internal_caches() + elif _HAS_CLEAR_TYPE_CACHE: sys._clear_type_cache() gc.collect() @@ -123,25 +153,22 @@ def python_version(): python_impl = "PyPy" else: python_impl = 'Python' - return "%s %s.%s (%s build)" % (python_impl, ver.major, ver.minor, build) + pyver = "%s.%s" % (ver.major, ver.minor) + if ver >= (3, 13): + if sysconfig.get_config_var('Py_GIL_DISABLED'): + # Free-threaded build + pyver += "t" + return "%s %s (%s build)" % (python_impl, pyver, build) def run_tests(module_name, lang): + if VERBOSE: + print("") + title = "Test %s (%s)" % (module_name, lang) display_title(title) - try: - testmod = import_tests(module_name) - except ImportError: - # The C extension must always be available - if lang == "C": - raise - - if VERBOSE: - print("%s: skip %s, missing %s extension" - % (python_version(), lang, module_name)) - print() - return + testmod = import_tests(module_name) if VERBOSE: empty_line = False @@ -188,6 +215,10 @@ def main(): global VERBOSE VERBOSE = ("-v" in sys.argv[1:] or "--verbose" in sys.argv[1:]) + if (3, 13) <= sys.version_info <= (3, 13, 0, 'alpha', 4): + print("SKIP Python 3.13 alpha 1..4: not supported!") + return + if faulthandler is not None: faulthandler.enable() @@ -197,7 +228,10 @@ def main(): build_ext() - for module_name, lang in TESTS: + tests = list(C_TESTS) + if TEST_CXX: + tests += CXX_TESTS + for module_name, lang in tests: run_tests(module_name, lang) diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index 78ee1c8..1074266 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,24 @@ # define PYTHON3 1 #endif -#if defined(_MSC_VER) && defined(__cplusplus) -# define MODULE_NAME test_pythoncapi_compat_cppext -#elif defined(__cplusplus) && __cplusplus >= 201103 +#if defined(__cplusplus) && __cplusplus >= 202002L +# define MODULE_NAME test_pythoncapi_compat_cpp20ext +#elif defined(__cplusplus) && __cplusplus >= 201703L +# define MODULE_NAME test_pythoncapi_compat_cpp17ext +#elif defined(__cplusplus) && __cplusplus >= 201402L +# define MODULE_NAME test_pythoncapi_compat_cpp14ext +#elif defined(__cplusplus) && __cplusplus >= 201103L # 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 +#elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 202311L +# define MODULE_NAME test_pythoncapi_compat_cext_c23 +#elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L +# define MODULE_NAME test_pythoncapi_compat_cext_c11 +#elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +# define MODULE_NAME test_pythoncapi_compat_cext_c99 #else # define MODULE_NAME test_pythoncapi_compat_cext #endif @@ -53,6 +66,7 @@ // Marker to check that pointer value was set static const char uninitialized[] = "uninitialized"; #define UNINITIALIZED_OBJ ((PyObject *)uninitialized) +#define UNINITIALIZED_INT 0x83ff979 static PyObject* @@ -255,7 +269,10 @@ test_frame(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored)) // test PyFrame_GetLocals() PyObject *locals = PyFrame_GetLocals(frame); assert(locals != _Py_NULL); + // Python 3.13 creates a local proxy +#if PY_VERSION_HEX < 0x030D0000 assert(PyDict_Check(locals)); +#endif // test PyFrame_GetGlobals() PyObject *globals = PyFrame_GetGlobals(frame); @@ -752,7 +769,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); @@ -1253,6 +1270,138 @@ test_dict_api(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) } +static PyObject * +test_dict_pop(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + PyObject *dict = PyDict_New(); + if (dict == NULL) { + return NULL; + } + + PyObject *key = PyUnicode_FromString("key"); + assert(key != NULL); + PyObject *value = PyUnicode_FromString("abc"); + assert(value != NULL); + + // test PyDict_Pop(), get the removed value, key is present + assert(PyDict_SetItem(dict, key, value) == 0); + PyObject *removed = UNINITIALIZED_OBJ; + assert(PyDict_Pop(dict, key, &removed) == 1); + assert(removed == value); + Py_DECREF(removed); + + // test PyDict_Pop(), ignore the removed value, key is present + assert(PyDict_SetItem(dict, key, value) == 0); + assert(PyDict_Pop(dict, key, NULL) == 1); + + // test PyDict_Pop(), key is missing + removed = UNINITIALIZED_OBJ; + assert(PyDict_Pop(dict, key, &removed) == 0); + assert(removed == NULL); + assert(PyDict_Pop(dict, key, NULL) == 0); + + // test PyDict_PopString(), get the removed value, key is present + assert(PyDict_SetItem(dict, key, value) == 0); + removed = UNINITIALIZED_OBJ; + assert(PyDict_PopString(dict, "key", &removed) == 1); + assert(removed == value); + Py_DECREF(removed); + + // test PyDict_PopString(), ignore the removed value, key is present + assert(PyDict_SetItem(dict, key, value) == 0); + assert(PyDict_PopString(dict, "key", NULL) == 1); + + // test PyDict_PopString(), key is missing + removed = UNINITIALIZED_OBJ; + assert(PyDict_PopString(dict, "key", &removed) == 0); + assert(removed == NULL); + assert(PyDict_PopString(dict, "key", NULL) == 0); + + // dict error + removed = UNINITIALIZED_OBJ; + assert(PyDict_Pop(key, key, &removed) == -1); + assert(removed == NULL); + assert(PyErr_ExceptionMatches(PyExc_SystemError)); + PyErr_Clear(); + + assert(PyDict_Pop(key, key, NULL) == -1); + assert(PyErr_ExceptionMatches(PyExc_SystemError)); + PyErr_Clear(); + + removed = UNINITIALIZED_OBJ; + assert(PyDict_PopString(key, "key", &removed) == -1); + assert(removed == NULL); + assert(PyErr_ExceptionMatches(PyExc_SystemError)); + PyErr_Clear(); + + assert(PyDict_PopString(key, "key", NULL) == -1); + assert(PyErr_ExceptionMatches(PyExc_SystemError)); + PyErr_Clear(); + + // exit + Py_DECREF(dict); + Py_DECREF(key); + Py_DECREF(value); + Py_RETURN_NONE; +} + + +static PyObject * +test_dict_setdefault(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + PyObject *dict = PyDict_New(); + if (dict == NULL) { + return NULL; + } + PyObject *key = PyUnicode_FromString("key"); + assert(key != NULL); + PyObject *value = PyUnicode_FromString("abc"); + assert(value != NULL); + PyObject *invalid_key = PyList_New(0); // not hashable key + assert(invalid_key != NULL); + + // insert item + PyObject *result = UNINITIALIZED_OBJ; + assert(PyDict_SetDefaultRef(dict, key, value, &result) == 0); + assert(result == value); + Py_DECREF(result); + + // item already present + result = UNINITIALIZED_OBJ; + assert(PyDict_SetDefaultRef(dict, key, value, &result) == 1); + assert(result == value); + Py_DECREF(result); + + // error: invalid key + assert(!PyErr_Occurred()); + result = UNINITIALIZED_OBJ; + assert(PyDict_SetDefaultRef(dict, invalid_key, value, &result) == -1); + assert(result == NULL); + assert(PyErr_Occurred()); + PyErr_Clear(); + + // insert item with NULL result + assert(PyDict_Pop(dict, key, NULL) == 1); + assert(PyDict_SetDefaultRef(dict, key, value, NULL) == 0); + + // item already present with NULL result + assert(PyDict_SetDefaultRef(dict, key, value, NULL) == 1); + + // error: invalid key with NULL result + assert(!PyErr_Occurred()); + assert(PyDict_SetDefaultRef(dict, invalid_key, value, NULL) == -1); + assert(PyErr_Occurred()); + PyErr_Clear(); + + // exit + Py_DECREF(dict); + Py_DECREF(key); + Py_DECREF(value); + Py_DECREF(invalid_key); + Py_RETURN_NONE; +} + + static PyObject * test_long_api(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) { @@ -1278,14 +1427,87 @@ test_long_api(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) PyErr_Clear(); Py_DECREF(obj2); + // test PyLong_GetSign() + int sign = UNINITIALIZED_INT; + 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 { @@ -1296,12 +1518,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; } @@ -1334,6 +1558,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; @@ -1361,70 +1586,1061 @@ test_managed_dict(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) #endif // PY_VERSION_HEX >= 0x030B00A3 -static struct PyMethodDef methods[] = { - {"test_object", test_object, METH_NOARGS, _Py_NULL}, - {"test_py_is", test_py_is, METH_NOARGS, _Py_NULL}, +static PyObject * +test_unicode(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + PyObject *abc = PyUnicode_FromString("abc"); + if (abc == NULL) { + return NULL; + } + + PyObject *abc0def = PyUnicode_FromStringAndSize("abc\0def", 7); + if (abc0def == NULL) { + Py_DECREF(abc); + return NULL; + } + + // PyUnicode_EqualToUTF8() and PyUnicode_EqualToUTF8AndSize() can be called + // with an exception raised and they must not clear the current exception. + PyErr_NoMemory(); + + assert(PyUnicode_EqualToUTF8AndSize(abc, "abc", 3) == 1); + assert(PyUnicode_EqualToUTF8AndSize(abc, "Python", 6) == 0); + assert(PyUnicode_EqualToUTF8AndSize(abc0def, "abc\0def", 7) == 1); + + assert(PyUnicode_EqualToUTF8(abc, "abc") == 1); + assert(PyUnicode_EqualToUTF8(abc, "Python") == 0); + assert(PyUnicode_EqualToUTF8(abc0def, "abc\0def") == 0); + + 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; +} + + +static PyObject * +test_list(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + PyObject *list = PyList_New(0); + if (list == NULL) { + return NULL; + } + + // test PyList_Extend() + { + PyObject *abc = PyUnicode_FromString("abc"); + if (abc == NULL) { + Py_DECREF(list); + return NULL; + } + + assert(PyList_Extend(list, abc) == 0); + Py_DECREF(abc); + assert(PyList_GET_SIZE(list) == 3); + } + + // test PyList_GetItemRef() + PyObject *item = PyList_GetItemRef(list, 1); + assert(item != NULL); + assert(item == PyList_GetItem(list, 1)); + Py_DECREF(item); + + // test PyList_Clear() + assert(PyList_Clear(list) == 0); + assert(PyList_GET_SIZE(list) == 0); + + Py_DECREF(list); + Py_RETURN_NONE; +} + + +static PyObject * +test_hash(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + void *ptr0 = NULL; + assert(Py_HashPointer(ptr0) == 0); + #ifndef PYPY_VERSION - {"test_frame", test_frame, METH_NOARGS, _Py_NULL}, +#if SIZEOF_VOID_P == 8 + void *ptr1 = (void*)(uintptr_t)0xABCDEF1234567890; + assert(Py_HashPointer(ptr1) == (uintptr_t)0x0ABCDEF123456789); +#else + void *ptr1 = (void*)(uintptr_t)0xDEADCAFE; + assert(Py_HashPointer(ptr1) == (uintptr_t)0xEDEADCAF); #endif - {"test_thread_state", test_thread_state, METH_NOARGS, _Py_NULL}, - {"test_interpreter", test_interpreter, METH_NOARGS, _Py_NULL}, - {"test_calls", test_calls, METH_NOARGS, _Py_NULL}, - {"test_gc", test_gc, METH_NOARGS, _Py_NULL}, - {"test_module", test_module, METH_NOARGS, _Py_NULL}, -#if (PY_VERSION_HEX <= 0x030B00A1 || 0x030B00A7 <= PY_VERSION_HEX) && !defined(PYPY_VERSION) - {"test_float_pack", test_float_pack, METH_NOARGS, _Py_NULL}, +#else + // PyPy +#if SIZEOF_VOID_P == 8 + void *ptr1 = (void*)(uintptr_t)0xABCDEF1234567890; +#else + void *ptr1 = (void*)(uintptr_t)0xDEADCAFE; #endif -#ifndef PYPY_VERSION - {"test_code", test_code, METH_NOARGS, _Py_NULL}, + assert(Py_HashPointer(ptr1) == (Py_hash_t)ptr1); #endif - {"test_api_casts", test_api_casts, METH_NOARGS, _Py_NULL}, - {"test_import", test_import, METH_NOARGS, _Py_NULL}, - {"test_weakref", test_weakref, METH_NOARGS, _Py_NULL}, - {"func_varargs", (PyCFunction)(void*)func_varargs, METH_VARARGS | METH_KEYWORDS, _Py_NULL}, - {"test_vectorcall", test_vectorcall, METH_NOARGS, _Py_NULL}, - {"test_getattr", test_getattr, METH_NOARGS, _Py_NULL}, - {"test_getitem", test_getitem, METH_NOARGS, _Py_NULL}, - {"test_dict_api", test_dict_api, METH_NOARGS, _Py_NULL}, - {"test_long_api", test_long_api, METH_NOARGS, _Py_NULL}, -#ifdef TEST_MANAGED_DICT - {"test_managed_dict", test_managed_dict, METH_NOARGS, _Py_NULL}, + +#if ((!defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x030400B1) \ + || (defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x03070000 \ + && PYPY_VERSION_NUM >= 0x07030800)) + // Just check that constants are available + size_t bits = PyHASH_BITS; + assert(bits >= 8); + size_t mod = PyHASH_MODULUS; + assert(mod >= 7); + size_t inf = PyHASH_INF; + assert(inf != 0); + size_t imag = PyHASH_IMAG; + assert(imag != 0); #endif - {_Py_NULL, _Py_NULL, 0, _Py_NULL} -}; + // 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); -static int -module_exec(PyObject *module) + Py_DECREF(abc); + } + + Py_RETURN_NONE; +} + + +#if PY_VERSION_HEX >= 0x03050000 +#define TEST_PYTIME + +static PyObject * +test_time(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) { -#ifdef __cplusplus - if (PyModule_AddIntMacro(module, __cplusplus)) { - return -1; + PyTime_t t; +#define UNINITIALIZED_TIME ((PyTime_t)-483884113929936179) + + t = UNINITIALIZED_TIME; + assert(PyTime_Time(&t) == 0); + assert(t != UNINITIALIZED_TIME); + + t = UNINITIALIZED_TIME; + assert(PyTime_Monotonic(&t) == 0); + assert(t != UNINITIALIZED_TIME); + + // Test multiple times since an implementation uses a cache + for (int i=0; i < 5; i++) { + t = UNINITIALIZED_TIME; + assert(PyTime_PerfCounter(&t) == 0); + assert(t != UNINITIALIZED_TIME); } + + assert(PyTime_AsSecondsDouble(1) == 1e-9); + assert(PyTime_AsSecondsDouble(1500 * 1000 * 1000) == 1.5); + assert(PyTime_AsSecondsDouble(-500 * 1000 * 1000) == -0.5); + + Py_RETURN_NONE; +} #endif - if (PyModule_AddStringMacro(module, PY_VERSION)) { - return -1; + + +static void +check_get_constant(PyObject* (*get_constant)(unsigned int), int borrowed) +{ +#define CLEAR(var) if (!borrowed) { Py_DECREF(var); } + + PyObject *obj, *expected; + + // Py_CONSTANT_NONE + obj = get_constant(Py_CONSTANT_NONE); + assert(obj == Py_None); + CLEAR(obj); + + // Py_CONSTANT_FALSE + obj = get_constant(Py_CONSTANT_FALSE); + assert(obj == Py_False); + CLEAR(obj); + + // Py_CONSTANT_TRUE + obj = get_constant(Py_CONSTANT_TRUE); + assert(obj == Py_True); + CLEAR(obj); + + // Py_CONSTANT_ELLIPSIS + obj = get_constant(Py_CONSTANT_ELLIPSIS); + assert(obj == Py_Ellipsis); + CLEAR(obj); + + // Py_CONSTANT_NOT_IMPLEMENTED + obj = get_constant(Py_CONSTANT_NOT_IMPLEMENTED); + assert(obj == Py_NotImplemented); + CLEAR(obj); + + // Py_CONSTANT_ZERO + obj = get_constant(Py_CONSTANT_ZERO); + expected = PyLong_FromLong(0); + assert(expected != NULL); + assert(Py_TYPE(obj) == &PyLong_Type); + assert(PyObject_RichCompareBool(obj, expected, Py_EQ) == 1); + CLEAR(obj); + Py_DECREF(expected); + + // Py_CONSTANT_ONE + obj = get_constant(Py_CONSTANT_ONE); + expected = PyLong_FromLong(1); + assert(expected != NULL); + assert(Py_TYPE(obj) == &PyLong_Type); + assert(PyObject_RichCompareBool(obj, expected, Py_EQ) == 1); + CLEAR(obj); + Py_DECREF(expected); + + // Py_CONSTANT_EMPTY_STR + obj = get_constant(Py_CONSTANT_EMPTY_STR); + assert(Py_TYPE(obj) == &PyUnicode_Type); +#if PY_VERSION_HEX >= 0x03030000 + assert(PyUnicode_GetLength(obj) == 0); +#else + assert(PyUnicode_GetSize(obj) == 0); +#endif + CLEAR(obj); + + // Py_CONSTANT_EMPTY_BYTES + obj = get_constant(Py_CONSTANT_EMPTY_BYTES); + assert(Py_TYPE(obj) == &PyBytes_Type); + assert(PyBytes_Size(obj) == 0); + CLEAR(obj); + + // Py_CONSTANT_EMPTY_TUPLE + obj = get_constant(Py_CONSTANT_EMPTY_TUPLE); + assert(Py_TYPE(obj) == &PyTuple_Type); + assert(PyTuple_Size(obj) == 0); + CLEAR(obj); + +#undef CLEAR +} + + +static PyObject * +test_get_constant(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + check_get_constant(Py_GetConstant, 0); + check_get_constant(Py_GetConstantBorrowed, 1); + Py_RETURN_NONE; +} + + +#if PY_VERSION_HEX < 0x030E0000 && PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION) +#define TEST_UNICODEWRITER 1 + +static PyObject * +test_unicodewriter(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) +{ + PyUnicodeWriter *writer = PyUnicodeWriter_Create(0); + if (writer == NULL) { + return NULL; } - if (PyModule_AddIntMacro(module, PY_VERSION_HEX)) { - return -1; + int ret; + + // test PyUnicodeWriter_WriteStr() + PyObject *str = PyUnicode_FromString("var"); + if (str == NULL) { + goto error; } -#ifdef PYPY_VERSION - if (PyModule_AddStringMacro(module, PYPY_VERSION)) { - return -1; + ret = PyUnicodeWriter_WriteStr(writer, str); + Py_CLEAR(str); + if (ret < 0) { + goto error; } -#endif -#ifdef PYPY_VERSION_NUM - if (PyModule_AddIntMacro(module, PYPY_VERSION_NUM)) { - return -1; + + // test PyUnicodeWriter_WriteChar() + if (PyUnicodeWriter_WriteChar(writer, '=') < 0) { + goto error; } -#endif - return 0; + + // test PyUnicodeWriter_WriteSubstring() + str = PyUnicode_FromString("[long]"); + if (str == NULL) { + goto error; + } + ret = PyUnicodeWriter_WriteSubstring(writer, str, 1, 5); + Py_CLEAR(str); + if (ret < 0) { + 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; + } + if (PyUnicodeWriter_WriteChar(writer, ' ') < 0) { + goto error; + } + + // test PyUnicodeWriter_WriteRepr() + str = PyUnicode_FromString("repr"); + if (str == NULL) { + goto error; + } + if (PyUnicodeWriter_WriteRepr(writer, str) < 0) { + goto error; + } + Py_CLEAR(str); + if (PyUnicodeWriter_WriteRepr(writer, NULL) < 0) { + goto error; + } + + { + PyObject *result = PyUnicodeWriter_Finish(writer); + if (result == NULL) { + return NULL; + } + const char *expected = "var=long non-ASCII valu\xC3\xA9 'repr'"; + assert(PyUnicode_EqualToUTF8(result, expected)); + Py_DECREF(result); + } + + Py_RETURN_NONE; + +error: + PyUnicodeWriter_Discard(writer); + return NULL; } -#if PY_VERSION_HEX >= 0x03050000 -static PyModuleDef_Slot module_slots[] = { - {Py_mod_exec, _Py_CAST(void*, module_exec)}, +static PyObject * +test_unicodewriter_widechar(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) +{ + PyUnicodeWriter *writer = PyUnicodeWriter_Create(0); + if (writer == NULL) { + return NULL; + } + + // test PyUnicodeWriter_WriteWideChar() + int ret = PyUnicodeWriter_WriteWideChar(writer, L"euro=\u20AC", -1); + if (ret < 0) { + goto error; + } + + { + PyObject *result = PyUnicodeWriter_Finish(writer); + if (result == NULL) { + return NULL; + } + assert(PyUnicode_EqualToUTF8(result, "euro=\xe2\x82\xac")); + Py_DECREF(result); + } + + Py_RETURN_NONE; + +error: + PyUnicodeWriter_Discard(writer); + return NULL; +} + + +static PyObject * +test_unicodewriter_format(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) +{ + PyUnicodeWriter *writer = PyUnicodeWriter_Create(0); + if (writer == NULL) { + return NULL; + } + + // test PyUnicodeWriter_Format() + if (PyUnicodeWriter_Format(writer, "%s %i", "Hello", 123) < 0) { + goto error; + } + + // test PyUnicodeWriter_WriteChar() + if (PyUnicodeWriter_WriteChar(writer, '.') < 0) { + goto error; + } + + { + PyObject *result = PyUnicodeWriter_Finish(writer); + if (result == NULL) { + return NULL; + } + assert(PyUnicode_EqualToUTF8(result, "Hello 123.")); + Py_DECREF(result); + } + + Py_RETURN_NONE; + +error: + PyUnicodeWriter_Discard(writer); + return NULL; +} +#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; +} + +#if 0x030D0000 <= PY_VERSION_HEX && !defined(PYPY_VERSION) +static PyObject * +test_set_immortal(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + PyObject object; + memset(&object, 0, sizeof(PyObject)); +#ifdef Py_GIL_DISABLED + object.ob_tid = _Py_ThreadId(); + object.ob_gc_bits = 0; + object.ob_ref_local = 1; + object.ob_ref_shared = 0; +#else + object.ob_refcnt = 1; +#endif + object.ob_type = &PyBaseObject_Type; + + int rc = PyUnstable_SetImmortal(&object); + assert(rc == 1); + Py_DECREF(&object); // should not dealloc + + // Check already immortal object + rc = PyUnstable_SetImmortal(&object); + assert(rc == 0); + + // Check unicode objects + PyObject *unicode = PyUnicode_FromString("test"); + rc = PyUnstable_SetImmortal(unicode); + assert(rc == 0); + Py_DECREF(unicode); + Py_RETURN_NONE; +} +#endif + + +static struct PyMethodDef methods[] = { + {"test_object", test_object, METH_NOARGS, _Py_NULL}, + {"test_py_is", test_py_is, METH_NOARGS, _Py_NULL}, +#ifndef PYPY_VERSION + {"test_frame", test_frame, METH_NOARGS, _Py_NULL}, +#endif + {"test_thread_state", test_thread_state, METH_NOARGS, _Py_NULL}, + {"test_interpreter", test_interpreter, METH_NOARGS, _Py_NULL}, + {"test_calls", test_calls, METH_NOARGS, _Py_NULL}, + {"test_gc", test_gc, METH_NOARGS, _Py_NULL}, + {"test_module", test_module, METH_NOARGS, _Py_NULL}, +#if (PY_VERSION_HEX <= 0x030B00A1 || 0x030B00A7 <= PY_VERSION_HEX) && !defined(PYPY_VERSION) + {"test_float_pack", test_float_pack, METH_NOARGS, _Py_NULL}, +#endif +#ifndef PYPY_VERSION + {"test_code", test_code, METH_NOARGS, _Py_NULL}, +#endif + {"test_api_casts", test_api_casts, METH_NOARGS, _Py_NULL}, + {"test_import", test_import, METH_NOARGS, _Py_NULL}, + {"test_weakref", test_weakref, METH_NOARGS, _Py_NULL}, + {"func_varargs", (PyCFunction)(void*)func_varargs, METH_VARARGS | METH_KEYWORDS, _Py_NULL}, + {"test_vectorcall", test_vectorcall, METH_NOARGS, _Py_NULL}, + {"test_getattr", test_getattr, METH_NOARGS, _Py_NULL}, + {"test_getitem", test_getitem, METH_NOARGS, _Py_NULL}, + {"test_dict_api", test_dict_api, METH_NOARGS, _Py_NULL}, + {"test_dict_pop", test_dict_pop, METH_NOARGS, _Py_NULL}, + {"test_dict_setdefault", test_dict_setdefault, METH_NOARGS, _Py_NULL}, + {"test_long_api", test_long_api, METH_NOARGS, _Py_NULL}, +#ifdef TEST_MANAGED_DICT + {"test_managed_dict", test_managed_dict, METH_NOARGS, _Py_NULL}, +#endif + {"test_unicode", test_unicode, METH_NOARGS, _Py_NULL}, + {"test_list", test_list, METH_NOARGS, _Py_NULL}, + {"test_hash", test_hash, METH_NOARGS, _Py_NULL}, +#ifdef TEST_PYTIME + {"test_time", test_time, METH_NOARGS, _Py_NULL}, +#endif + {"test_get_constant", test_get_constant, METH_NOARGS, _Py_NULL}, +#ifdef TEST_UNICODEWRITER + {"test_unicodewriter", test_unicodewriter, METH_NOARGS, _Py_NULL}, + {"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}, +#if 0x030D0000 <= PY_VERSION_HEX && !defined(PYPY_VERSION) + {"test_set_immortal", test_set_immortal, METH_NOARGS, _Py_NULL}, +#endif + {_Py_NULL, _Py_NULL, 0, _Py_NULL} +}; + + +static int +module_exec(PyObject *module) +{ +#ifdef __cplusplus + if (PyModule_AddIntMacro(module, __cplusplus)) { + return -1; + } +#endif + if (PyModule_AddStringMacro(module, PY_VERSION)) { + return -1; + } + if (PyModule_AddIntMacro(module, PY_VERSION_HEX)) { + return -1; + } +#ifdef PYPY_VERSION + if (PyModule_AddStringMacro(module, PYPY_VERSION)) { + return -1; + } +#endif +#ifdef PYPY_VERSION_NUM + if (PyModule_AddIntMacro(module, PYPY_VERSION_NUM)) { + 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; +} + + +#if PY_VERSION_HEX >= 0x03050000 +static PyModuleDef_Slot module_slots[] = { + {Py_mod_exec, _Py_CAST(void*, module_exec)}, +#if PY_VERSION_HEX >= 0x030D0000 + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, +#endif {0, _Py_NULL} }; #endif @@ -1435,7 +2651,7 @@ static struct PyModuleDef module_def = { PyModuleDef_HEAD_INIT, MODULE_NAME_STR, // m_name _Py_NULL, // m_doc - 0, // m_doc + 0, // m_size methods, // m_methods #if PY_VERSION_HEX >= 0x03050000 module_slots, // m_slots 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)