.. versionadded:: 3.13
+.. c:macro:: Py_mod_abi
+
+ A pointer to a :c:struct:`PyABIInfo` structure that describes the ABI that
+ the extension is using.
+
+ When the module is loaded, the :c:struct:`!PyABIInfo` in this slot is checked
+ using :c:func:`PyABIInfo_Check`.
+
+ A suitable :c:struct:`!PyABIInfo` variable can be defined using the
+ :c:macro:`PyABIInfo_VAR` macro, as in:
+
+ .. code-block:: c
+
+ PyABIInfo_VAR(abi_info);
+
+ static PyModuleDef_Slot mymodule_slots[] = {
+ {Py_mod_abi, &abi_info},
+ ...
+ };
+
+ .. versionadded:: 3.15
+
.. _moduledef-dynamic:
.. _stable:
-***************
-C API Stability
-***************
+***********************
+C API and ABI Stability
+***********************
Unless documented otherwise, Python's C API is covered by the Backwards
Compatibility Policy, :pep:`387`.
third-party distributors.
+ABI Checking
+============
+
+.. versionadded:: next
+
+Python includes a rudimentary check for ABI compatibility.
+
+This check is not comprehensive.
+It only guards against common cases of incompatible modules being
+installed for the wrong interpreter.
+It also does not take :ref:`platform incompatibilities <stable-abi-platform>`
+into account.
+It can only be done after an extension is successfully loaded.
+
+Despite these limitations, it is recommended that extension modules use this
+mechanism, so that detectable incompatibilities raise exceptions rather than
+crash.
+
+Most modules can use this check via the :c:data:`Py_mod_abi`
+slot and the :c:macro:`PyABIInfo_VAR` macro, for example like this:
+
+.. code-block:: c
+
+ PyABIInfo_VAR(abi_info);
+
+ static PyModuleDef_Slot mymodule_slots[] = {
+ {Py_mod_abi, &abi_info},
+ ...
+ };
+
+
+The full API is described below for advanced use cases.
+
+.. c:function:: int PyABIInfo_Check(PyABIInfo *info, const char *module_name)
+
+ Verify that the given *info* is compatible with the currently running
+ interpreter.
+
+ Return 0 on success. On failure, raise an exception and return -1.
+
+ If the ABI is incompatible, the raised exception will be :py:exc:`ImportError`.
+
+ The *module_name* argument can be ``NULL``, or point to a NUL-terminated
+ UTF-8-encoded string used for error messages.
+
+ Note that if *info* describes the ABI that the current code uses (as defined
+ by :c:macro:`PyABIInfo_VAR`, for example), using any other Python C API
+ may lead to crashes.
+ In particular, it is not safe to examine the raised exception.
+
+ .. versionadded:: next
+
+.. c:macro:: PyABIInfo_VAR(NAME)
+
+ Define a static :c:struct:`PyABIInfo` variable with the given *NAME* that
+ describes the ABI that the current code will use.
+ This macro expands to:
+
+ .. code-block:: c
+
+ static PyABIInfo NAME = {
+ 1, 0,
+ PyABIInfo_DEFAULT_FLAGS,
+ PY_VERSION_HEX,
+ PyABIInfo_DEFAULT_ABI_VERSION
+ }
+
+ .. versionadded:: next
+
+.. c:type:: PyABIInfo
+
+ .. c:member:: uint8_t abiinfo_major_version
+
+ The major version of :c:struct:`PyABIInfo`. Can be set to:
+
+ * ``0`` to skip all checking, or
+ * ``1`` to specify this version of :c:struct:`!PyABIInfo`.
+
+ .. c:member:: uint8_t abiinfo_minor_version
+
+ The major version of :c:struct:`PyABIInfo`.
+ Must be set to ``0``; larger values are reserved for backwards-compatible
+ future versions of :c:struct:`!PyABIInfo`.
+
+ .. c:member:: uint16_t flags
+
+ .. c:namespace:: NULL
+
+ This field is usually set to the following macro:
+
+ .. c:macro:: PyABIInfo_DEFAULT_FLAGS
+
+ Default flags, based on current values of macros such as
+ :c:macro:`Py_LIMITED_API` and :c:macro:`Py_GIL_DISABLED`.
+
+ Alternately, the field can be set to to the following flags, combined
+ by bitwise OR.
+ Unused bits must be set to zero.
+
+ ABI variant -- one of:
+
+ .. c:macro:: PyABIInfo_STABLE
+
+ Specifies that the stable ABI is used.
+
+ .. c:macro:: PyABIInfo_INTERNAL
+
+ Specifies ABI specific to a particular build of CPython.
+ Internal use only.
+
+ Free-threading compatibility -- one of:
+
+ .. c:macro:: PyABIInfo_FREETHREADED
+
+ Specifies ABI compatible with free-threading builds of CPython.
+ (That is, ones compiled with :option:`--disable-gil`; with ``t``
+ in :py:data:`sys.abiflags`)
+
+ .. c:macro:: PyABIInfo_GIL
+
+ Specifies ABI compatible with non-free-threading builds of CPython
+ (ones compiled *without* :option:`--disable-gil`).
+
+ .. c:member:: uint32_t build_version
+
+ The version of the Python headers used to build the code, in the format
+ used by :c:macro:`PY_VERSION_HEX`.
+
+ This can be set to ``0`` to skip any checks related to this field.
+ This option is meant mainly for projects that do not use the CPython
+ headers directly, and do not emulate a specific version of them.
+
+ .. c:member:: uint32_t abi_version
+
+ The ABI version.
+
+ For the Stable ABI, this field should be the value of
+ :c:macro:`Py_LIMITED_API`
+ (except if :c:macro:`Py_LIMITED_API` is ``3``; use
+ :c:expr:`Py_PACK_VERSION(3, 2)` in that case).
+
+ Otherwise, it should be set to :c:macro:`PY_VERSION_HEX`.
+
+ It can also be set to ``0`` to skip any checks related to this field.
+
+ .. c:namespace:: NULL
+
+ .. c:macro:: PyABIInfo_DEFAULT_ABI_VERSION
+
+ The value that should be used for this field, based on current
+ values of macros such as :c:macro:`Py_LIMITED_API`,
+ :c:macro:`PY_VERSION_HEX` and :c:macro:`Py_GIL_DISABLED`.
+
+ .. versionadded:: next
+
+
.. _limited-api-list:
Contents of Limited API
role,name,added,ifdef_note,struct_abi_kind
macro,PY_VECTORCALL_ARGUMENTS_OFFSET,3.12,,
+type,PyABIInfo,3.15,,full-abi
+func,PyABIInfo_Check,3.15,,
+macro,PyABIInfo_VAR,3.15,,
func,PyAIter_Check,3.10,,
func,PyArg_Parse,3.2,,
func,PyArg_ParseTuple,3.2,,
.. option:: --disable-gil
+ .. c:macro:: Py_GIL_DISABLED
+ :no-typesetting:
+
Enables support for running Python without the :term:`global interpreter
lock` (GIL): free threading build.
a string. See the documentation for caveats.
(Contributed by Petr Viktorin in :gh:`131510`)
+* Add API for checking an extension module's ABI compatibility:
+ :c:data:`Py_mod_abi`, :c:func:`PyABIInfo_Check`, :c:macro:`PyABIInfo_VAR`
+ and :c:data:`Py_mod_abi`.
+ (Contributed by Petr Viktorin in :gh:`137210`)
+
Porting to Python 3.15
----------------------
PyAPI_FUNC(int) _PyArg_ParseTupleAndKeywordsFast(PyObject *, PyObject *,
struct _PyArg_Parser *, ...);
+
+#ifdef Py_BUILD_CORE
+// Internal; defined here to avoid explicitly including pycore_modsupport.h
+#define _Py_INTERNAL_ABI_SLOT \
+ {Py_mod_abi, (void*) &(PyABIInfo) { \
+ .abiinfo_major_version = 1, \
+ .abiinfo_minor_version = 0, \
+ .flags = PyABIInfo_INTERNAL, \
+ .build_version = PY_VERSION_HEX, \
+ .abi_version = PY_VERSION_HEX }} \
+ ///////////////////////////////////////////////////////
+#endif
Py_ssize_t start,
Py_ssize_t end);
+/* PyUnicodeWriter_Format, with va_list instead of `...` */
+extern int _PyUnicodeWriter_FormatV(
+ PyUnicodeWriter *writer,
+ const char *format,
+ va_list vargs);
+
/* --- UTF-7 Codecs ------------------------------------------------------- */
extern PyObject* _PyUnicode_EncodeUTF7(
#endif /* New in 3.5 */
+/* ABI info & checking (new in 3.15) */
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030f0000
+typedef struct PyABIInfo {
+ uint8_t abiinfo_major_version;
+ uint8_t abiinfo_minor_version;
+ uint16_t flags;
+ uint32_t build_version;
+ uint32_t abi_version;
+} PyABIInfo;
+#define PyABIInfo_STABLE 0x0001
+#define PyABIInfo_GIL 0x0002
+#define PyABIInfo_FREETHREADED 0x0004
+#define PyABIInfo_INTERNAL 0x0008
+
+#define PyABIInfo_FREETHREADING_AGNOSTIC (PyABIInfo_GIL|PyABIInfo_FREETHREADED)
+
+PyAPI_FUNC(int) PyABIInfo_Check(PyABIInfo *info, const char *module_name);
+
+// Define the defaults
+#ifdef Py_LIMITED_API
+ #define _PyABIInfo_DEFAULT_FLAG_STABLE PyABIInfo_STABLE
+ #if Py_LIMITED_API == 3
+ #define PyABIInfo_DEFAULT_ABI_VERSION _Py_PACK_VERSION(3, 2)
+ #else
+ #define PyABIInfo_DEFAULT_ABI_VERSION Py_LIMITED_API
+ #endif
+#else
+ #define _PyABIInfo_DEFAULT_FLAG_STABLE 0
+ #define PyABIInfo_DEFAULT_ABI_VERSION PY_VERSION_HEX
+#endif
+#if defined(Py_LIMITED_API) && defined(_Py_OPAQUE_PYOBJECT)
+ #define _PyABIInfo_DEFAULT_FLAG_FT PyABIInfo_FREETHREADING_AGNOSTIC
+#elif defined(Py_GIL_DISABLED)
+ #define _PyABIInfo_DEFAULT_FLAG_FT PyABIInfo_FREETHREADED
+#else
+ #define _PyABIInfo_DEFAULT_FLAG_FT PyABIInfo_GIL
+#endif
+#if defined(Py_BUILD_CORE)
+ #define _PyABIInfo_DEFAULT_FLAG_INTERNAL PyABIInfo_INTERNAL
+#else
+ #define _PyABIInfo_DEFAULT_FLAG_INTERNAL 0
+#endif
+
+#define PyABIInfo_DEFAULT_FLAGS ( \
+ _PyABIInfo_DEFAULT_FLAG_STABLE \
+ | _PyABIInfo_DEFAULT_FLAG_FT \
+ | _PyABIInfo_DEFAULT_FLAG_INTERNAL \
+ ) \
+ /////////////////////////////////////////////////////////
+
+#define _PyABIInfo_DEFAULT() { \
+ 1, 0, \
+ PyABIInfo_DEFAULT_FLAGS, \
+ PY_VERSION_HEX, \
+ PyABIInfo_DEFAULT_ABI_VERSION } \
+ /////////////////////////////////////////////////////////
+
+#define PyABIInfo_VAR(NAME) \
+ static PyABIInfo NAME = _PyABIInfo_DEFAULT;
+
+#undef _PyABIInfo_DEFAULT_STABLE
+#undef _PyABIInfo_DEFAULT_FT
+#undef _PyABIInfo_DEFAULT_INTERNAL
+
+#endif /* ABI info (new in 3.15) */
+
#ifndef Py_LIMITED_API
# define Py_CPYTHON_MODSUPPORT_H
# include "cpython/modsupport.h"
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030d0000
# define Py_mod_gil 4
#endif
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= _Py_PACK_VERSION(3, 15)
+# define Py_mod_abi 5
+#endif
#ifndef Py_LIMITED_API
-#define _Py_mod_LAST_SLOT 4
+#define _Py_mod_LAST_SLOT 5
#endif
#endif /* New in 3.5 */
--- /dev/null
+import sys
+import unittest
+import sysconfig
+
+from test.support import subTests
+from test.support import import_helper
+
+_testcapi = import_helper.import_module('_testcapi')
+
+
+class Test_ABIInfo_Check(unittest.TestCase):
+ @subTests('modname', (None, 'test_mod'))
+ def test_zero(self, modname):
+ _testcapi.pyabiinfo_check(modname, 0, 0, 0, 0, 0)
+ _testcapi.pyabiinfo_check(modname, 1, 0, 0, 0, 0)
+
+ def test_large_major_version(self):
+ with self.assertRaisesRegex(ImportError,
+ '^PyABIInfo version too high$'):
+ _testcapi.pyabiinfo_check(None, 2, 0, 0, 0, 0)
+ with self.assertRaisesRegex(ImportError,
+ '^test_mod: PyABIInfo version too high$'):
+ _testcapi.pyabiinfo_check("test_mod", 2, 0, 0, 0, 0)
+
+ @subTests('modname', (None, 'test_mod'))
+ def test_large_minor_version(self, modname):
+ _testcapi.pyabiinfo_check(modname, 1, 2, 0, 0, 0)
+
+ @subTests('modname', (None, 'test_mod'))
+ @subTests('major', (0, 1))
+ @subTests('minor', (0, 1, 9))
+ @subTests('build', (0, sys.hexversion))
+ def test_positive_regular(self, modname, major, minor, build):
+ ver = sys.hexversion
+ truncated = ver & 0xffff0000
+ filled = truncated | 0x12b8
+ maxed = truncated | 0xffff
+ for abi_version in (0, ver, truncated, filled, maxed):
+ with self.subTest(abi_version=abi_version):
+ _testcapi.pyabiinfo_check(modname, major, minor, 0,
+ build, abi_version)
+
+ @subTests('modname', (None, 'test_mod'))
+ @subTests('minor', (0, 1, 9))
+ @subTests('build', (0, sys.hexversion))
+ @subTests('offset', (+0x00010000, -0x00010000))
+ def test_negative_regular(self, modname, minor, build, offset):
+ ver = sys.hexversion + offset
+ truncated = ver & 0xffff0000
+ filled = truncated | 0x12b8
+ maxed = truncated | 0xffff
+ for abi_version in (ver, truncated, filled, maxed):
+ with self.subTest(abi_version=abi_version):
+ with self.assertRaisesRegex(
+ ImportError,
+ r'incompatible ABI version \(3\.\d+\)$'):
+ _testcapi.pyabiinfo_check(modname, 1, minor, 0,
+ build,
+ abi_version)
+
+ @subTests('modname', (None, 'test_mod'))
+ @subTests('major', (0, 1))
+ @subTests('minor', (0, 1, 9))
+ @subTests('build', (0, sys.hexversion))
+ @subTests('abi_version', (
+ 0,
+ 0x03020000,
+ sys.hexversion,
+ sys.hexversion & 0xffff0000,
+ sys.hexversion - 0x00010000,
+ ))
+ def test_positive_stable(self, modname, major, minor, build, abi_version):
+ _testcapi.pyabiinfo_check(modname, major, minor,
+ _testcapi.PyABIInfo_STABLE,
+ build,
+ abi_version)
+
+ @subTests('modname', (None, 'test_mod'))
+ @subTests('minor', (0, 1, 9))
+ @subTests('build', (0, sys.hexversion))
+ @subTests('abi_version_and_msg', (
+ (1, 'invalid'),
+ (3, 'invalid'),
+ (0x0301ffff, 'invalid'),
+ ((sys.hexversion & 0xffff0000) + 0x00010000, 'incompatible future'),
+ (sys.hexversion + 0x00010000, 'incompatible future'),
+ (0x04000000, 'incompatible future'),
+ ))
+ def test_negative_stable(self, modname, minor, build, abi_version_and_msg):
+ abi_version, msg = abi_version_and_msg
+ with self.assertRaisesRegex(
+ ImportError,
+ rf'{msg} stable ABI version \(\d+\.\d+\)$'):
+ _testcapi.pyabiinfo_check(modname, 1, minor,
+ _testcapi.PyABIInfo_STABLE,
+ build,
+ abi_version)
+
+ @subTests('modname', (None, 'test_mod'))
+ @subTests('major', (0, 1))
+ @subTests('minor', (0, 1, 9))
+ @subTests('build', (0, sys.hexversion))
+ @subTests('abi_version', (0, sys.hexversion))
+ def test_positive_internal(self, modname, major, minor, build, abi_version):
+ _testcapi.pyabiinfo_check(modname, major, minor,
+ _testcapi.PyABIInfo_INTERNAL,
+ build,
+ abi_version)
+
+ @subTests('modname', (None, 'test_mod'))
+ @subTests('minor', (0, 1, 9))
+ @subTests('build', (0, sys.hexversion))
+ @subTests('abi_version', (
+ sys.hexversion - 0x00010000,
+ sys.hexversion - 1,
+ sys.hexversion + 1,
+ sys.hexversion + 0x00010000,
+ ))
+ def test_negative_internal(self, modname, minor, build, abi_version):
+ with self.assertRaisesRegex(
+ ImportError,
+ r'incompatible internal ABI \(0x[\da-f]+ != 0x[\da-f]+\)$'):
+ _testcapi.pyabiinfo_check(modname, 1, minor,
+ _testcapi.PyABIInfo_INTERNAL,
+ build,
+ abi_version)
+
+ @subTests('modname', (None, 'test_mod'))
+ @subTests('minor', (0, 1, 9))
+ @subTests('build', (0, sys.hexversion))
+ @subTests('ft_flag', (
+ 0,
+ (_testcapi.PyABIInfo_FREETHREADED
+ if sysconfig.get_config_var("Py_GIL_DISABLED")
+ else _testcapi.PyABIInfo_GIL),
+ _testcapi.PyABIInfo_FREETHREADING_AGNOSTIC,
+ ))
+ def test_positive_freethreading(self, modname, minor, build, ft_flag):
+ self.assertEqual(ft_flag & _testcapi.PyABIInfo_FREETHREADING_AGNOSTIC,
+ ft_flag)
+ _testcapi.pyabiinfo_check(modname, 1, minor, ft_flag, build, 0)
+
+ @subTests('modname', (None, 'test_mod'))
+ @subTests('minor', (0, 1, 9))
+ @subTests('build', (0, sys.hexversion))
+ def test_negative_freethreading(self, modname, minor, build):
+ if sysconfig.get_config_var("Py_GIL_DISABLED"):
+ ft_flag = _testcapi.PyABIInfo_GIL
+ msg = "incompatible with free-threaded CPython"
+ else:
+ ft_flag = _testcapi.PyABIInfo_FREETHREADED
+ msg = "only compatible with free-threaded CPython"
+ with self.assertRaisesRegex(ImportError, msg):
+ _testcapi.pyabiinfo_check(modname, 1, minor, ft_flag, build, 0)
SYMBOL_NAMES = (
+ "PyABIInfo_Check",
"PyAIter_Check",
"PyArg_Parse",
"PyArg_ParseTuple",
--- /dev/null
+Add API for checking an extension module's ABI compatibility:
+:c:data:`Py_mod_abi`, :c:func:`PyABIInfo_Check`, :c:macro:`PyABIInfo_VAR`
+and :c:data:`Py_mod_abi`.
added = '3.14'
[function.Py_PACK_VERSION]
added = '3.14'
+
[function.PySys_GetAttr]
added = '3.15'
[function.PySys_GetAttrString]
added = '3.15'
[function.PySys_GetOptionalAttrString]
added = '3.15'
+[function.PyABIInfo_Check]
+ added = '3.15'
+[macro.PyABIInfo_VAR]
+ added = '3.15'
+[struct.PyABIInfo]
+ added = '3.15'
+ struct_abi_kind = 'full-abi'
+[const.Py_mod_abi]
+ added = '3.15'
+[const.PyABIInfo_DEFAULT_ABI_VERSION]
+ added = '3.15'
+[const.PyABIInfo_DEFAULT_FLAGS]
+ added = '3.15'
+[const.PyABIInfo_STABLE]
+ added = '3.15'
+[const.PyABIInfo_FREETHREADED]
+ added = '3.15'
+[const.PyABIInfo_GIL]
+ added = '3.15'
+[const.PyABIInfo_FREETHREADING_AGNOSTIC]
+ added = '3.15'
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c
-@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c
+@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
}
static PyModuleDef_Slot module_slots[] = {
+ _Py_INTERNAL_ABI_SLOT,
{Py_mod_exec, _datetime_exec},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
--- /dev/null
+#include "parts.h"
+
+
+
+static PyObject *
+pyabiinfo_check(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ const char *modname;
+ unsigned long maj, min, flags, buildver, abiver;
+
+ if (!PyArg_ParseTuple(args, "zkkkkk",
+ &modname, &maj, &min, &flags, &buildver, &abiver))
+ {
+ return NULL;
+ }
+ PyABIInfo info = {
+ .abiinfo_major_version = (uint8_t)maj,
+ .abiinfo_minor_version = (uint8_t)min,
+ .flags = (uint16_t)flags,
+ .build_version = (uint32_t)buildver,
+ .abi_version = (uint32_t)abiver};
+ if (PyABIInfo_Check(&info, modname) < 0) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef TestMethods[] = {
+ {"pyabiinfo_check", pyabiinfo_check, METH_VARARGS},
+ {NULL},
+};
+
+int
+_PyTestCapi_Init_Modsupport(PyObject *m)
+{
+ if (PyModule_AddIntMacro(m, PyABIInfo_STABLE) < 0) {
+ return -1;
+ }
+ if (PyModule_AddIntMacro(m, PyABIInfo_INTERNAL) < 0) {
+ return -1;
+ }
+ if (PyModule_AddIntMacro(m, PyABIInfo_GIL) < 0) {
+ return -1;
+ }
+ if (PyModule_AddIntMacro(m, PyABIInfo_FREETHREADED) < 0) {
+ return -1;
+ }
+ if (PyModule_AddIntMacro(m, PyABIInfo_FREETHREADING_AGNOSTIC) < 0) {
+ return -1;
+ }
+ if (PyModule_AddFunctions(m, TestMethods) < 0) {
+ return -1;
+ }
+ return 0;
+}
int _PyTestCapi_Init_GC(PyObject *module);
int _PyTestCapi_Init_Hash(PyObject *module);
int _PyTestCapi_Init_Time(PyObject *module);
+int _PyTestCapi_Init_Modsupport(PyObject *module);
int _PyTestCapi_Init_Monitoring(PyObject *module);
int _PyTestCapi_Init_Object(PyObject *module);
int _PyTestCapi_Init_Config(PyObject *mod);
if (_PyTestCapi_Init_Time(m) < 0) {
return NULL;
}
+ if (_PyTestCapi_Init_Modsupport(m) < 0) {
+ return NULL;
+ }
if (_PyTestCapi_Init_Monitoring(m) < 0) {
return NULL;
}
gil_slot = cur_slot->value;
has_gil_slot = 1;
break;
+ case Py_mod_abi:
+ if (PyABIInfo_Check((PyABIInfo *)cur_slot->value, name) < 0) {
+ goto error;
+ }
+ break;
default:
assert(cur_slot->slot < 0 || cur_slot->slot > _Py_mod_LAST_SLOT);
PyErr_Format(
break;
case Py_mod_multiple_interpreters:
case Py_mod_gil:
+ case Py_mod_abi:
/* handled in PyModule_FromDefAndSpec2 */
break;
default:
int
PyUnicodeWriter_Format(PyUnicodeWriter *writer, const char *format, ...)
+{
+ va_list vargs;
+ va_start(vargs, format);
+ int res = _PyUnicodeWriter_FormatV(writer, format, vargs);
+ va_end(vargs);
+ return res;
+}
+
+int
+_PyUnicodeWriter_FormatV(PyUnicodeWriter *writer, const char *format,
+ va_list vargs)
{
_PyUnicodeWriter *_writer = (_PyUnicodeWriter*)writer;
Py_ssize_t old_pos = _writer->pos;
- va_list vargs;
- va_start(vargs, format);
int res = unicode_from_format(_writer, format, vargs);
- va_end(vargs);
if (res < 0) {
_writer->pos = old_pos;
EXPORT_FUNC(Py_TYPE)
EXPORT_FUNC(Py_VaBuildValue)
EXPORT_FUNC(Py_XNewRef)
+EXPORT_FUNC(PyABIInfo_Check)
EXPORT_FUNC(PyAIter_Check)
EXPORT_FUNC(PyArg_Parse)
EXPORT_FUNC(PyArg_ParseTuple)
<ClCompile Include="..\Modules\_testcapi\immortal.c" />
<ClCompile Include="..\Modules\_testcapi\gc.c" />
<ClCompile Include="..\Modules\_testcapi\run.c" />
+ <ClCompile Include="..\Modules\_testcapi\modsupport.c" />
<ClCompile Include="..\Modules\_testcapi\monitoring.c" />
<ClCompile Include="..\Modules\_testcapi\config.c" />
<ClCompile Include="..\Modules\_testcapi\import.c" />
<ClCompile Include="..\Modules\_testcapi\run.c">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\Modules\_testcapi\modsupport.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="..\Modules\_testcapi\monitoring.c">
<Filter>Source Files</Filter>
</ClCompile>
#include "Python.h"
#include "pycore_abstract.h" // _PyIndex_Check()
#include "pycore_object.h" // _PyType_IsReady()
+#include "pycore_unicodeobject.h" // _PyUnicodeWriter_FormatV()
typedef double va_double;
return PyModule_AddObjectRef(module, name, (PyObject *)type);
}
+static int _abiinfo_raise(const char *module_name, const char *format, ...)
+{
+ PyUnicodeWriter *writer = PyUnicodeWriter_Create(0);
+ if (!writer) {
+ return -1;
+ }
+ if (module_name) {
+ if (PyUnicodeWriter_WriteUTF8(writer, module_name, -1) < 0) {
+ PyUnicodeWriter_Discard(writer);
+ return -1;
+ }
+ if (PyUnicodeWriter_WriteASCII(writer, ": ", 2) < 0) {
+ PyUnicodeWriter_Discard(writer);
+ return -1;
+ }
+ }
+ va_list vargs;
+ va_start(vargs, format);
+ if (_PyUnicodeWriter_FormatV(writer, format, vargs) < 0) {
+ PyUnicodeWriter_Discard(writer);
+ return -1;
+ }
+ PyObject *message = PyUnicodeWriter_Finish(writer);
+ if (!message) {
+ return -1;
+ }
+ PyErr_SetObject(PyExc_ImportError, message);
+ Py_DECREF(message);
+ return -1;
+}
+
+int PyABIInfo_Check(PyABIInfo *info, const char *module_name)
+{
+ if (!info) {
+ return _abiinfo_raise(module_name, "NULL PyABIInfo");
+ }
+
+ /* abiinfo_major_version */
+ if (info->abiinfo_major_version == 0) {
+ return 0;
+ }
+ if (info->abiinfo_major_version > 1) {
+ return _abiinfo_raise(module_name, "PyABIInfo version too high");
+ }
+
+ /* Internal ABI */
+ if (info->flags & PyABIInfo_INTERNAL) {
+ if (info->abi_version && (info->abi_version != PY_VERSION_HEX)) {
+ return _abiinfo_raise(
+ module_name,
+ "incompatible internal ABI (0x%x != 0x%x)",
+ info->abi_version, PY_VERSION_HEX);
+ }
+ }
+
+#define XY_MASK 0xffff0000
+ if (info->flags & PyABIInfo_STABLE) {
+ /* Greater-than major.minor version check */
+ if (info->abi_version) {
+ if ((info->abi_version & XY_MASK) > (PY_VERSION_HEX & XY_MASK)) {
+ return _abiinfo_raise(
+ module_name,
+ "incompatible future stable ABI version (%d.%d)",
+ ((info->abi_version) >> 24) % 0xff,
+ ((info->abi_version) >> 16) % 0xff);
+ }
+ if (info->abi_version < Py_PACK_VERSION(3, 2)) {
+ return _abiinfo_raise(
+ module_name,
+ "invalid stable ABI version (%d.%d)",
+ ((info->abi_version) >> 24) % 0xff,
+ ((info->abi_version) >> 16) % 0xff);
+ }
+ }
+ if (info->flags & PyABIInfo_INTERNAL) {
+ return _abiinfo_raise(module_name,
+ "cannot use both internal and stable ABI");
+ }
+ }
+ else {
+ /* Exact major.minor version check */
+ if (info->abi_version) {
+ if ((info->abi_version & XY_MASK) != (PY_VERSION_HEX & XY_MASK)) {
+ return _abiinfo_raise(
+ module_name,
+ "incompatible ABI version (%d.%d)",
+ ((info->abi_version) >> 24) % 0xff,
+ ((info->abi_version) >> 16) % 0xff);
+ }
+ }
+ }
+#undef XY_MASK
+
+ /* Free-threading/GIL */
+ uint16_t gilflags = info->flags & (PyABIInfo_GIL | PyABIInfo_FREETHREADED);
+#if Py_GIL_DISABLED
+ if (gilflags == PyABIInfo_GIL) {
+ return _abiinfo_raise(module_name,
+ "incompatible with free-threaded CPython");
+ }
+#else
+ if (gilflags == PyABIInfo_FREETHREADED) {
+ return _abiinfo_raise(module_name,
+ "only compatible with free-threaded CPython");
+ }
+#endif
+
+ return 0;
+}
+
/* Exported functions for version helper macros */