]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-91321: Fix compatibility with C++ older than C++11 (#93784)
authorVictor Stinner <vstinner@python.org>
Tue, 14 Jun 2022 09:43:08 +0000 (11:43 +0200)
committerGitHub <noreply@github.com>
Tue, 14 Jun 2022 09:43:08 +0000 (11:43 +0200)
Fix the compatibility of the Python C API with C++ older than C++11.

_Py_NULL is only defined as nullptr on C++11 and newer.

Include/pyport.h
Lib/test/_testcppext.cpp
Lib/test/setup_testcppext.py
Lib/test/test_cppext.py
Misc/NEWS.d/next/C API/2022-06-13-21-37-31.gh-issue-91321.DgJFvS.rst [new file with mode: 0644]

index faaeb8329181196c2b745f0aaa11b438be37c11e..313bc8d21c83fd352023fccd50e26c31faad3b1d 100644 (file)
@@ -36,10 +36,12 @@ extern "C++" {
         inline type _Py_CAST_impl(int ptr) {
             return reinterpret_cast<type>(ptr);
         }
+#if __cplusplus >= 201103
         template <typename type>
         inline type _Py_CAST_impl(std::nullptr_t) {
             return static_cast<type>(nullptr);
         }
+#endif
 
         template <typename type, typename expr_type>
             inline type _Py_CAST_impl(expr_type *expr) {
@@ -70,8 +72,9 @@ extern "C++" {
 #endif
 
 // Static inline functions should use _Py_NULL rather than using directly NULL
-// to prevent C++ compiler warnings. In C++, _Py_NULL uses nullptr.
-#ifdef __cplusplus
+// to prevent C++ compiler warnings. On C++11 and newer, _Py_NULL is defined as
+// nullptr.
+#if defined(__cplusplus) && __cplusplus >= 201103
 #  define _Py_NULL nullptr
 #else
 #  define _Py_NULL NULL
index 5e3d76b7b20723b5f210a240d744d60fded730aa..b6d35407a61edc85d2c1b4688558e55962b854e6 100644 (file)
@@ -6,6 +6,12 @@
 
 #include "Python.h"
 
+#if __cplusplus >= 201103
+#  define NAME _testcpp11ext
+#else
+#  define NAME _testcpp03ext
+#endif
+
 PyDoc_STRVAR(_testcppext_add_doc,
 "add(x, y)\n"
 "\n"
@@ -16,7 +22,7 @@ _testcppext_add(PyObject *Py_UNUSED(module), PyObject *args)
 {
     long i, j;
     if (!PyArg_ParseTuple(args, "ll:foo", &i, &j)) {
-        return nullptr;
+        return _Py_NULL;
     }
     long res = i + j;
     return PyLong_FromLong(res);
@@ -47,8 +53,8 @@ static PyObject *
 test_api_casts(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
 {
     PyObject *obj = Py_BuildValue("(ii)", 1, 2);
-    if (obj == nullptr) {
-        return nullptr;
+    if (obj == _Py_NULL) {
+        return _Py_NULL;
     }
     Py_ssize_t refcnt = Py_REFCNT(obj);
     assert(refcnt >= 1);
@@ -77,9 +83,11 @@ test_api_casts(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
     // gh-93442: Pass 0 as NULL for PyObject*
     Py_XINCREF(0);
     Py_XDECREF(0);
-    // ensure that nullptr works too
+#if _cplusplus >= 201103
+    // Test nullptr passed as PyObject*
     Py_XINCREF(nullptr);
     Py_XDECREF(nullptr);
+#endif
 
     Py_DECREF(obj);
     Py_RETURN_NONE;
@@ -90,8 +98,8 @@ static PyObject *
 test_unicode(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
 {
     PyObject *str = PyUnicode_FromString("abc");
-    if (str == nullptr) {
-        return nullptr;
+    if (str == _Py_NULL) {
+        return _Py_NULL;
     }
 
     assert(PyUnicode_Check(str));
@@ -99,7 +107,7 @@ test_unicode(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
 
     // gh-92800: test PyUnicode_READ()
     const void* data = PyUnicode_DATA(str);
-    assert(data != nullptr);
+    assert(data != _Py_NULL);
     int kind = PyUnicode_KIND(str);
     assert(kind == PyUnicode_1BYTE_KIND);
     assert(PyUnicode_READ(kind, data, 0) == 'a');
@@ -118,9 +126,9 @@ test_unicode(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
 
 static PyMethodDef _testcppext_methods[] = {
     {"add", _testcppext_add, METH_VARARGS, _testcppext_add_doc},
-    {"test_api_casts", test_api_casts, METH_NOARGS, nullptr},
-    {"test_unicode", test_unicode, METH_NOARGS, nullptr},
-    {nullptr, nullptr, 0, nullptr}  /* sentinel */
+    {"test_api_casts", test_api_casts, METH_NOARGS, _Py_NULL},
+    {"test_unicode", test_unicode, METH_NOARGS, _Py_NULL},
+    {_Py_NULL, _Py_NULL, 0, _Py_NULL}  /* sentinel */
 };
 
 
@@ -135,26 +143,32 @@ _testcppext_exec(PyObject *module)
 
 static PyModuleDef_Slot _testcppext_slots[] = {
     {Py_mod_exec, reinterpret_cast<void*>(_testcppext_exec)},
-    {0, nullptr}
+    {0, _Py_NULL}
 };
 
 
 PyDoc_STRVAR(_testcppext_doc, "C++ test extension.");
 
+#define _STR(NAME) #NAME
+#define STR(NAME) _STR(NAME)
+
 static struct PyModuleDef _testcppext_module = {
     PyModuleDef_HEAD_INIT,  // m_base
-    "_testcppext",  // m_name
+    STR(NAME),  // m_name
     _testcppext_doc,  // m_doc
     0,  // m_size
     _testcppext_methods,  // m_methods
     _testcppext_slots,  // m_slots
-    nullptr,  // m_traverse
-    nullptr,  // m_clear
-    nullptr,  // m_free
+    _Py_NULL,  // m_traverse
+    _Py_NULL,  // m_clear
+    _Py_NULL,  // m_free
 };
 
+#define _FUNC_NAME(NAME) PyInit_ ## NAME
+#define FUNC_NAME(NAME) _FUNC_NAME(NAME)
+
 PyMODINIT_FUNC
-PyInit__testcppext(void)
+FUNC_NAME(NAME)(void)
 {
     return PyModuleDef_Init(&_testcppext_module);
 }
index 780cb7b24a78c996575d51eb081dfb7f350af51b..a288dbdc57bdeda09189656ffba57d05364fe9a2 100644 (file)
@@ -13,8 +13,6 @@ SOURCE = support.findfile('_testcppext.cpp')
 if not MS_WINDOWS:
     # C++ compiler flags for GCC and clang
     CPPFLAGS = [
-        # Python currently targets C++11
-        '-std=c++11',
         # gh-91321: The purpose of _testcppext extension is to check that building
         # a C++ extension using the Python C API does not emit C++ compiler
         # warnings
@@ -30,12 +28,23 @@ else:
 
 
 def main():
+    cppflags = list(CPPFLAGS)
+    if '-std=c++03' in sys.argv:
+        sys.argv.remove('-std=c++03')
+        std = 'c++03'
+        name = '_testcpp03ext'
+    else:
+        # Python currently targets C++11
+        std = 'c++11'
+        name = '_testcpp11ext'
+
+    cppflags = [*CPPFLAGS, f'-std={std}']
     cpp_ext = Extension(
-        '_testcppext',
+        name,
         sources=[SOURCE],
         language='c++',
-        extra_compile_args=CPPFLAGS)
-    setup(name="_testcppext", ext_modules=[cpp_ext])
+        extra_compile_args=cppflags)
+    setup(name=name, ext_modules=[cpp_ext])
 
 
 if __name__ == "__main__":
index 9ed90616b0eab5ba12d3034575c07136ebb49883..8673911ecfae5d9656409135d0364003cf553a2c 100644 (file)
@@ -16,22 +16,29 @@ SETUP_TESTCPPEXT = support.findfile('setup_testcppext.py')
 
 @support.requires_subprocess()
 class TestCPPExt(unittest.TestCase):
+    def test_build_cpp11(self):
+        self.check_build(False)
+
+    def test_build_cpp03(self):
+        self.check_build(True)
+
     # With MSVC, the linker fails with: cannot open file 'python311.lib'
     # https://github.com/python/cpython/pull/32175#issuecomment-1111175897
     @unittest.skipIf(MS_WINDOWS, 'test fails on Windows')
     # the test uses venv+pip: skip if it's not available
     @support.requires_venv_with_pip()
-    def test_build(self):
+    def check_build(self, std_cpp03):
         # Build in a temporary directory
         with os_helper.temp_cwd():
-            self._test_build()
+            self._check_build(std_cpp03)
 
-    def _test_build(self):
+    def _check_build(self, std_cpp03):
         venv_dir = 'env'
+        verbose = support.verbose
 
         # Create virtual environment to get setuptools
         cmd = [sys.executable, '-X', 'dev', '-m', 'venv', venv_dir]
-        if support.verbose:
+        if verbose:
             print()
             print('Run:', ' '.join(cmd))
         subprocess.run(cmd, check=True)
@@ -46,16 +53,21 @@ class TestCPPExt(unittest.TestCase):
             python = os.path.join(venv_dir, 'bin', python_exe)
 
         # Build the C++ extension
-        cmd = [python, '-X', 'dev', SETUP_TESTCPPEXT, 'build_ext', '--verbose']
-        if support.verbose:
+        cmd = [python, '-X', 'dev',
+               SETUP_TESTCPPEXT, 'build_ext', '--verbose']
+        if std_cpp03:
+            cmd.append('-std=c++03')
+        if verbose:
             print('Run:', ' '.join(cmd))
-        proc = subprocess.run(cmd,
-                              stdout=subprocess.PIPE,
-                              stderr=subprocess.STDOUT,
-                              text=True)
-        if proc.returncode:
-            print(proc.stdout, end='')
-            self.fail(f"Build failed with exit code {proc.returncode}")
+            subprocess.run(cmd, check=True)
+        else:
+            proc = subprocess.run(cmd,
+                                  stdout=subprocess.PIPE,
+                                  stderr=subprocess.STDOUT,
+                                  text=True)
+            if proc.returncode:
+                print(proc.stdout, end='')
+                self.fail(f"Build failed with exit code {proc.returncode}")
 
 
 if __name__ == "__main__":
diff --git a/Misc/NEWS.d/next/C API/2022-06-13-21-37-31.gh-issue-91321.DgJFvS.rst b/Misc/NEWS.d/next/C API/2022-06-13-21-37-31.gh-issue-91321.DgJFvS.rst
new file mode 100644 (file)
index 0000000..57c39bc
--- /dev/null
@@ -0,0 +1,2 @@
+Fix the compatibility of the Python C API with C++ older than C++11. Patch by
+Victor Stinner.