gh-111506: Add _Py_OPAQUE_PYOBJECT to hide PyObject layout & related API (GH-136505)

Allow Py_LIMITED_API for (Py_GIL_DISABLED && _Py_OPAQUE_PYOBJECT)


API that's removed when _Py_OPAQUE_PYOBJECT is defined:

    - PyObject_HEAD
    - _PyObject_EXTRA_INIT
    - PyObject_HEAD_INIT
    - PyObject_VAR_HEAD
    - struct _object (i.e. PyObject) (opaque)
    - struct PyVarObject (opaque)
    - Py_SIZE
    - Py_SET_TYPE
    - Py_SET_SIZE
    - PyModuleDef_Base (opaque)
    - PyModuleDef_HEAD_INIT
    - PyModuleDef (opaque)
    - _Py_IsImmortal
    - _Py_IsStaticImmortal

Note that the `_Py_IsImmortal` removal (and a few other issues)
 means _Py_OPAQUE_PYOBJECT only works with limited
API 3.14+ now.


Co-authored-by: Victor Stinner <vstinner@python.org>
This commit is contained in:
Petr Viktorin 2025-07-12 09:55:12 +02:00 committed by GitHub
parent db47f4d844
commit c7d24b81c3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 123 additions and 25 deletions

View file

@ -12,7 +12,10 @@
from test import support
SOURCE = os.path.join(os.path.dirname(__file__), 'extension.c')
SOURCES = [
os.path.join(os.path.dirname(__file__), 'extension.c'),
os.path.join(os.path.dirname(__file__), 'create_moduledef.c'),
]
SETUP = os.path.join(os.path.dirname(__file__), 'setup.py')
@ -35,17 +38,22 @@ class BaseTests:
def test_build(self):
self.check_build('_test_cext')
def check_build(self, extension_name, std=None, limited=False):
def check_build(self, extension_name, std=None, limited=False,
opaque_pyobject=False):
venv_dir = 'env'
with support.setup_venv_with_pip_setuptools(venv_dir) as python_exe:
self._check_build(extension_name, python_exe,
std=std, limited=limited)
std=std, limited=limited,
opaque_pyobject=opaque_pyobject)
def _check_build(self, extension_name, python_exe, std, limited):
def _check_build(self, extension_name, python_exe, std, limited,
opaque_pyobject):
pkg_dir = 'pkg'
os.mkdir(pkg_dir)
shutil.copy(SETUP, os.path.join(pkg_dir, os.path.basename(SETUP)))
shutil.copy(SOURCE, os.path.join(pkg_dir, os.path.basename(SOURCE)))
for source in SOURCES:
dest = os.path.join(pkg_dir, os.path.basename(source))
shutil.copy(source, dest)
def run_cmd(operation, cmd):
env = os.environ.copy()
@ -53,6 +61,8 @@ def run_cmd(operation, cmd):
env['CPYTHON_TEST_STD'] = std
if limited:
env['CPYTHON_TEST_LIMITED'] = '1'
if opaque_pyobject:
env['CPYTHON_TEST_OPAQUE_PYOBJECT'] = '1'
env['CPYTHON_TEST_EXT_NAME'] = extension_name
env['TEST_INTERNAL_C_API'] = str(int(self.TEST_INTERNAL_C_API))
if support.verbose:
@ -107,6 +117,11 @@ def test_build_limited_c11(self):
def test_build_c11(self):
self.check_build('_test_c11_cext', std='c11')
def test_build_opaque_pyobject(self):
# Test with _Py_OPAQUE_PYOBJECT
self.check_build('_test_limited_opaque_cext', limited=True,
opaque_pyobject=True)
@unittest.skipIf(support.MS_WINDOWS, "MSVC doesn't support /std:c99")
def test_build_c99(self):
# In public docs, we say C API is compatible with C11. However,