gh-137210: Add a struct, slot & function for checking an extension's ABI (GH-137212)

Co-authored-by: Steve Dower <steve.dower@microsoft.com>
This commit is contained in:
Petr Viktorin 2025-09-05 16:23:18 +02:00 committed by GitHub
parent c1a9c23195
commit 0c74fc8af0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 654 additions and 8 deletions

View file

@ -388,6 +388,28 @@ The available slot types are:
.. 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:

View file

@ -2,9 +2,9 @@
.. _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`.
@ -199,6 +199,162 @@ This is the case with Windows and macOS releases from ``python.org`` and many
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

View file

@ -1,5 +1,8 @@
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,,

View file

@ -293,6 +293,9 @@ General Options
.. 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.

View file

@ -670,6 +670,11 @@ New features
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
----------------------

View file

@ -24,3 +24,15 @@ typedef struct _PyArg_Parser {
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

View file

@ -82,6 +82,12 @@ extern int _PyUnicode_FormatAdvancedWriter(
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(

View file

@ -134,6 +134,72 @@ PyAPI_FUNC(PyObject *) PyModule_FromDefAndSpec2(PyModuleDef *def,
#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"

View file

@ -81,10 +81,13 @@ struct PyModuleDef_Slot {
#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 */

View file

@ -0,0 +1,154 @@
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)

View file

@ -45,6 +45,7 @@ class TestStableABIAvailability(unittest.TestCase):
SYMBOL_NAMES = (
"PyABIInfo_Check",
"PyAIter_Check",
"PyArg_Parse",
"PyArg_ParseTuple",

View file

@ -0,0 +1,3 @@
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`.

View file

@ -2575,6 +2575,7 @@
added = '3.14'
[function.Py_PACK_VERSION]
added = '3.14'
[function.PySys_GetAttr]
added = '3.15'
[function.PySys_GetAttrString]
@ -2583,3 +2584,24 @@
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'

View file

@ -174,7 +174,7 @@
@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

View file

@ -7616,6 +7616,7 @@ finally:
}
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},

View file

@ -0,0 +1,55 @@
#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;
}

View file

@ -58,6 +58,7 @@ int _PyTestCapi_Init_Immortal(PyObject *module);
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);

View file

@ -3465,6 +3465,9 @@ PyInit__testcapi(void)
if (_PyTestCapi_Init_Time(m) < 0) {
return NULL;
}
if (_PyTestCapi_Init_Modsupport(m) < 0) {
return NULL;
}
if (_PyTestCapi_Init_Monitoring(m) < 0) {
return NULL;
}

View file

@ -340,6 +340,11 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
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(
@ -514,6 +519,7 @@ PyModule_ExecDef(PyObject *module, PyModuleDef *def)
break;
case Py_mod_multiple_interpreters:
case Py_mod_gil:
case Py_mod_abi:
/* handled in PyModule_FromDefAndSpec2 */
break;
default:

View file

@ -3263,14 +3263,22 @@ PyUnicode_FromFormat(const char *format, ...)
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;

1
PC/python3dll.c generated
View file

@ -93,6 +93,7 @@ EXPORT_FUNC(Py_SetRecursionLimit)
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)

View file

@ -125,6 +125,7 @@
<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" />

View file

@ -108,6 +108,9 @@
<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>

View file

@ -4,6 +4,7 @@
#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;
@ -668,6 +669,116 @@ PyModule_AddType(PyObject *module, PyTypeObject *type)
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 */