mirror of
https://github.com/python/cpython.git
synced 2025-11-24 12:20:42 +00:00
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:
parent
c1a9c23195
commit
0c74fc8af0
24 changed files with 654 additions and 8 deletions
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
3
Doc/data/stable_abi.dat
generated
3
Doc/data/stable_abi.dat
generated
|
|
@ -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,,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
----------------------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
154
Lib/test/test_capi/test_modsupport.py
Normal file
154
Lib/test/test_capi/test_modsupport.py
Normal 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)
|
||||
1
Lib/test/test_stable_abi_ctypes.py
generated
1
Lib/test/test_stable_abi_ctypes.py
generated
|
|
@ -45,6 +45,7 @@ class TestStableABIAvailability(unittest.TestCase):
|
|||
|
||||
SYMBOL_NAMES = (
|
||||
|
||||
"PyABIInfo_Check",
|
||||
"PyAIter_Check",
|
||||
"PyArg_Parse",
|
||||
"PyArg_ParseTuple",
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
|
|
|
|||
55
Modules/_testcapi/modsupport.c
Normal file
55
Modules/_testcapi/modsupport.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
1
PC/python3dll.c
generated
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue