mirror of
https://github.com/python/cpython
synced 2026-11-06 09:02:40 +03:00
[3.15] gh-161185: Fix strict aliasing issue in Py_SETREF() macros family (GH-161186) (GH-161360) gh-161185: Fix strict aliasing issue in Py_SETREF() macros family (GH-161186) Py_SETREF(), Py_XSETREF() and Py_CLEAR() now use memcpy() to set _tmp_old_dst/_tmp_old_op to fix a strict aliasing issue. Py_SETREF(), Py_XSETREF() and Py_CLEAR() now also cast _tmp_old_dst/_tmp_old_op to PyObject* when calling Py_DECREF()/Py_XDECREF() to avoid compiler warnings with the limited C API. Add the _Py_TESTCEXT_NO_TYPEOF macro to undefine the _Py_TYPEOF macro. (cherry picked from commitf8e65ee35d) (cherry picked from commit0dc1a96026) Co-authored-by: Victor Stinner <vstinner@python.org> Co-authored-by: Nathan Goldbaum <nathan.goldbaum@gmail.com>
217 lines
7.7 KiB
Python
217 lines
7.7 KiB
Python
# gh-116869: Build a C/C++ test extension to check that the Python C API does
|
|
# not emit compiler warnings.
|
|
#
|
|
# The Python C API must be compatible with building
|
|
# with the -Werror=declaration-after-statement compiler flag.
|
|
|
|
import os.path
|
|
import platform
|
|
import shlex
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import sysconfig
|
|
import unittest
|
|
from test import support
|
|
from test.support import os_helper
|
|
|
|
|
|
if not support.has_subprocess_support:
|
|
raise unittest.SkipTest("requires subprocess support")
|
|
|
|
|
|
SOURCE_DIR = os.path.dirname(__file__)
|
|
SOURCES = [
|
|
os.path.join(SOURCE_DIR, 'extension.c'),
|
|
os.path.join(SOURCE_DIR, 'extension.cpp'),
|
|
os.path.join(SOURCE_DIR, 'setup.py'),
|
|
]
|
|
RUNTESTS_PY = os.path.join(SOURCE_DIR, 'runtests.py')
|
|
MSVC = support.MS_WINDOWS
|
|
|
|
|
|
# With MSVC on a debug build, the linker fails with: cannot open file
|
|
# 'python311.lib', it should look 'python311_d.lib'.
|
|
@unittest.skipIf(MSVC and support.Py_DEBUG,
|
|
'test fails on Windows debug build')
|
|
# Building and running an extension in clang sanitizing mode is not
|
|
# straightforward
|
|
@support.skip_if_sanitizer('test does not work with analyzing builds',
|
|
address=True, memory=True, ub=True, thread=True)
|
|
# the test uses venv+pip: skip if it's not available
|
|
@support.requires_venv_with_pip()
|
|
@support.requires_subprocess()
|
|
@support.requires_resource('cpu')
|
|
class BaseTests:
|
|
TEST_INTERNAL_C_API = False
|
|
LANGUAGE = None
|
|
|
|
def check_build(self, extension_name, std=None, limited=False,
|
|
extra_cflags=None):
|
|
if self.LANGUAGE == 'C++' and not std and sys.platform == 'darwin':
|
|
# Old Apple clang++ default C++ std is gnu++98, use C++11 instead
|
|
std = 'c++11'
|
|
|
|
if limited and support.Py_GIL_DISABLED:
|
|
raise unittest.SkipTest("Free Threading doesn't support "
|
|
"the limited C API")
|
|
|
|
pkg_dir = 'pkg'
|
|
os.mkdir(pkg_dir)
|
|
self.addCleanup(os_helper.rmtree, pkg_dir)
|
|
|
|
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()
|
|
env['CPYTHON_TEST_EXT_NAME'] = extension_name
|
|
env['CPYTHON_TEST_LANG'] = self.LANGUAGE
|
|
if std:
|
|
env['CPYTHON_TEST_STD'] = std
|
|
if limited:
|
|
env['CPYTHON_TEST_LIMITED'] = '1'
|
|
if MSVC and sysconfig.is_python_build():
|
|
env['CPYTHON_TEST_EXTRA_INCDIRS'] = os.path.split(sysconfig.get_config_h_filename())[0]
|
|
env['CPYTHON_TEST_EXTRA_LIBDIRS'] = os.path.split(sys.executable)[0]
|
|
env['CPYTHON_TEST_INTERNAL_C_API'] = str(int(self.TEST_INTERNAL_C_API))
|
|
if extra_cflags:
|
|
env['CPYTHON_TEST_EXTRA_CFLAGS'] = extra_cflags
|
|
if support.verbose:
|
|
print('Run:', ' '.join(map(shlex.quote, cmd)))
|
|
subprocess.run(cmd, check=True, env=env)
|
|
else:
|
|
proc = subprocess.run(cmd,
|
|
env=env,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
text=True)
|
|
if proc.returncode:
|
|
print('Run:', ' '.join(map(shlex.quote, cmd)))
|
|
print(proc.stdout, end='')
|
|
self.fail(
|
|
f"{operation} failed with exit code {proc.returncode}")
|
|
|
|
# Build and install the C extension
|
|
python_exe = PYTHON_EXE
|
|
cmd = [python_exe, '-X', 'dev',
|
|
'-m', 'pip', 'install', '--no-build-isolation',
|
|
os.path.abspath(pkg_dir)]
|
|
if support.verbose:
|
|
cmd.append('-v')
|
|
run_cmd('Install', cmd)
|
|
|
|
# Import the extension module and run tests.
|
|
# On a debug build, check also for reference leaks.
|
|
cmd = [python_exe, '-X', 'dev', RUNTESTS_PY, extension_name]
|
|
run_cmd('Tests', cmd)
|
|
|
|
|
|
class TestPublicC(BaseTests, unittest.TestCase):
|
|
LANGUAGE = 'C'
|
|
|
|
# Default build with no options
|
|
def test_build(self):
|
|
self.check_build('_test_cext')
|
|
|
|
def test_build_no_typeof(self):
|
|
# gh-161185: Exercise the Py_CLEAR/Py_SETREF() memcpy implementation
|
|
# with strict aliasing enabled: undefine the _Py_TYPEOF macro.
|
|
self.check_build(
|
|
'_test_cext_no_typeof',
|
|
extra_cflags='-D_Py_TESTCEXT_NO_TYPEOF -O2 -fstrict-aliasing')
|
|
|
|
@unittest.skipIf(MSVC, "MSVC doesn't support /std:c99")
|
|
def test_build_c99(self):
|
|
# In public docs, we say C API is compatible with C11. However,
|
|
# in practice we do maintain C99 compatibility in public headers.
|
|
# Please ask the C API WG before adding a new C11-only feature.
|
|
self.check_build('_test_cext_c99', std='c99')
|
|
|
|
def test_build_c11(self):
|
|
self.check_build('_test_cext_c11', std='c11')
|
|
|
|
def test_build_limited(self):
|
|
self.check_build('_test_cext_limited', limited=True)
|
|
|
|
def test_build_limited_c11(self):
|
|
self.check_build('_test_cext_limited_c11', limited=True, std='c11')
|
|
|
|
|
|
class TestPublicCpp(BaseTests, unittest.TestCase):
|
|
LANGUAGE = 'C++'
|
|
|
|
def test_build(self):
|
|
self.check_build('_test_cppext')
|
|
|
|
def test_build_cpp03(self):
|
|
# In public docs, we say C API is compatible with C++11. However,
|
|
# in practice we do maintain C++03 compatibility in public headers.
|
|
# Please ask the C API WG before adding a new C++11-only feature.
|
|
self.check_build('_test_cppext_cpp03', std='c++03')
|
|
|
|
@unittest.skipIf(MSVC, "MSVC doesn't support /std:c++03")
|
|
def test_build_cpp03_no_typeof(self):
|
|
# gh-161185: Exercise the Py_CLEAR/Py_SETREF() memcpy implementation
|
|
# with strict aliasing enabled: undefine the _Py_TYPEOF macro.
|
|
self.check_build(
|
|
'_test_cppext_cpp03_no_typeof',
|
|
std='c++03',
|
|
extra_cflags='-D_Py_TESTCEXT_NO_TYPEOF -O2 -fstrict-aliasing')
|
|
|
|
@unittest.skipIf(MSVC, "MSVC doesn't support /std:c++11")
|
|
def test_build_cpp11(self):
|
|
self.check_build('_test_cppext_cpp11', std='c++11')
|
|
|
|
# Only test C++14 on MSVC.
|
|
# On s390x RHEL7, GCC 4.8.5 doesn't support C++14.
|
|
@unittest.skipIf(not MSVC, "need MSVC")
|
|
def test_build_cpp14(self):
|
|
self.check_build('_test_cppext_cpp14', std='c++14')
|
|
|
|
# Test that headers compile with Intel asm syntax, which may conflict
|
|
# with inline assembly in free-threading headers that use AT&T syntax.
|
|
@unittest.skipIf(MSVC, "MSVC doesn't support -masm=intel")
|
|
@unittest.skipUnless(platform.machine() in ('x86_64', 'i686', 'AMD64'),
|
|
"x86-specific flag")
|
|
def test_build_intel_asm(self):
|
|
self.check_build('_test_cppext_intel_asm', extra_cflags='-masm=intel')
|
|
|
|
def test_build_limited(self):
|
|
self.check_build('_test_cppext_limited', limited=True)
|
|
|
|
def test_build_limited_cpp03(self):
|
|
self.check_build('_test_cppext_limited_cpp03', std='c++03', limited=True)
|
|
|
|
|
|
class TestInternalC(BaseTests, unittest.TestCase):
|
|
LANGUAGE = 'C'
|
|
TEST_INTERNAL_C_API = True
|
|
|
|
# Default build with no options
|
|
def test_build(self):
|
|
self.check_build('_test_cext_internal')
|
|
|
|
|
|
class TestInternalCpp(BaseTests, unittest.TestCase):
|
|
LANGUAGE = 'C++'
|
|
TEST_INTERNAL_C_API = True
|
|
|
|
def test_build(self):
|
|
self.check_build('_test_cppext_internal')
|
|
|
|
|
|
@support.requires_venv_with_pip()
|
|
def setUpModule():
|
|
global VENV_CONTEXT, PYTHON_EXE
|
|
VENV_CONTEXT = support.setup_venv_with_pip_setuptools('env')
|
|
PYTHON_EXE = VENV_CONTEXT.__enter__()
|
|
|
|
|
|
def tearDownModule():
|
|
VENV_CONTEXT.__exit__(None, None, None)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|