[Python-checkins] gh-91321: Fix compatibility with C++ older than C++11 (#93784)

vstinner webhook-mailer at python.org
Tue Jun 14 05:43:12 EDT 2022


https://github.com/python/cpython/commit/4caf5c2753f1aa28d6f4bc1aa377975fd2a62331
commit: 4caf5c2753f1aa28d6f4bc1aa377975fd2a62331
branch: main
author: Victor Stinner <vstinner at python.org>
committer: vstinner <vstinner at python.org>
date: 2022-06-14T11:43:08+02:00
summary:

gh-91321: Fix compatibility with C++ older than C++11 (#93784)

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.

files:
A Misc/NEWS.d/next/C API/2022-06-13-21-37-31.gh-issue-91321.DgJFvS.rst
M Include/pyport.h
M Lib/test/_testcppext.cpp
M Lib/test/setup_testcppext.py
M Lib/test/test_cppext.py

diff --git a/Include/pyport.h b/Include/pyport.h
index faaeb83291811..313bc8d21c83f 100644
--- a/Include/pyport.h
+++ b/Include/pyport.h
@@ -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
diff --git a/Lib/test/_testcppext.cpp b/Lib/test/_testcppext.cpp
index 5e3d76b7b2072..b6d35407a61ed 100644
--- a/Lib/test/_testcppext.cpp
+++ b/Lib/test/_testcppext.cpp
@@ -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);
 }
diff --git a/Lib/test/setup_testcppext.py b/Lib/test/setup_testcppext.py
index 780cb7b24a78c..a288dbdc57bde 100644
--- a/Lib/test/setup_testcppext.py
+++ b/Lib/test/setup_testcppext.py
@@ -13,8 +13,6 @@
 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 @@
 
 
 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__":
diff --git a/Lib/test/test_cppext.py b/Lib/test/test_cppext.py
index 9ed90616b0eab..8673911ecfae5 100644
--- a/Lib/test/test_cppext.py
+++ b/Lib/test/test_cppext.py
@@ -16,22 +16,29 @@
 
 @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 @@ def _test_build(self):
             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
index 0000000000000..57c39bc8d83c8
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2022-06-13-21-37-31.gh-issue-91321.DgJFvS.rst	
@@ -0,0 +1,2 @@
+Fix the compatibility of the Python C API with C++ older than C++11. Patch by
+Victor Stinner.



More information about the Python-checkins mailing list