Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from
Jun 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Include/pyport.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
46 changes: 30 additions & 16 deletions Lib/test/_testcppext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -90,16 +98,16 @@ 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));
assert(PyUnicode_GET_LENGTH(str) == 3);

// 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');
Expand All @@ -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 */
};


Expand All @@ -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);
}
19 changes: 14 additions & 5 deletions Lib/test/setup_testcppext.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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__":
Expand Down
38 changes: 25 additions & 13 deletions Lib/test/test_cppext.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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__":
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix the compatibility of the Python C API with C++ older than C++11. Patch by
Victor Stinner.