1 // gh-91321: Very basic C++ test extension to check that the Python C API is
2 // compatible with C++ and does not emit C++ compiler warnings.
3 
4 // Always enable assertions
5 #undef NDEBUG
6 
7 #include "Python.h"
8 
9 #if __cplusplus >= 201103
10 #  define NAME _testcpp11ext
11 #else
12 #  define NAME _testcpp03ext
13 #endif
14 
15 #define _STR(NAME) #NAME
16 #define STR(NAME) _STR(NAME)
17 
18 PyDoc_STRVAR(_testcppext_add_doc,
19 "add(x, y)\n"
20 "\n"
21 "Return the sum of two integers: x + y.");
22 
23 static PyObject *
_testcppext_add(PyObject * Py_UNUSED (module),PyObject * args)24 _testcppext_add(PyObject *Py_UNUSED(module), PyObject *args)
25 {
26     long i, j;
27     if (!PyArg_ParseTuple(args, "ll:foo", &i, &j)) {
28         return _Py_NULL;
29     }
30     long res = i + j;
31     return PyLong_FromLong(res);
32 }
33 
34 
35 // Class to test operator casting an object to PyObject*
36 class StrongRef
37 {
38 public:
StrongRef(PyObject * obj)39     StrongRef(PyObject *obj) : m_obj(obj) {
40         Py_INCREF(this->m_obj);
41     }
42 
~StrongRef()43     ~StrongRef() {
44         Py_DECREF(this->m_obj);
45     }
46 
47     // Cast to PyObject*: get a borrowed reference
operator PyObject*() const48     inline operator PyObject*() const { return this->m_obj; }
49 
50 private:
51     PyObject *m_obj;  // Strong reference
52 };
53 
54 
55 static PyObject *
test_api_casts(PyObject * Py_UNUSED (module),PyObject * Py_UNUSED (args))56 test_api_casts(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
57 {
58     PyObject *obj = Py_BuildValue("(ii)", 1, 2);
59     if (obj == _Py_NULL) {
60         return _Py_NULL;
61     }
62     Py_ssize_t refcnt = Py_REFCNT(obj);
63     assert(refcnt >= 1);
64 
65     // gh-92138: For backward compatibility, functions of Python C API accepts
66     // "const PyObject*". Check that using it does not emit C++ compiler
67     // warnings.
68     const PyObject *const_obj = obj;
69     Py_INCREF(const_obj);
70     Py_DECREF(const_obj);
71     PyTypeObject *type = Py_TYPE(const_obj);
72     assert(Py_REFCNT(const_obj) == refcnt);
73     assert(type == &PyTuple_Type);
74     assert(PyTuple_GET_SIZE(const_obj) == 2);
75     PyObject *one = PyTuple_GET_ITEM(const_obj, 0);
76     assert(PyLong_AsLong(one) == 1);
77 
78     // gh-92898: StrongRef doesn't inherit from PyObject but has an operator to
79     // cast to PyObject*.
80     StrongRef strong_ref(obj);
81     assert(Py_TYPE(strong_ref) == &PyTuple_Type);
82     assert(Py_REFCNT(strong_ref) == (refcnt + 1));
83     Py_INCREF(strong_ref);
84     Py_DECREF(strong_ref);
85 
86     // gh-93442: Pass 0 as NULL for PyObject*
87     Py_XINCREF(0);
88     Py_XDECREF(0);
89 #if _cplusplus >= 201103
90     // Test nullptr passed as PyObject*
91     Py_XINCREF(nullptr);
92     Py_XDECREF(nullptr);
93 #endif
94 
95     Py_DECREF(obj);
96     Py_RETURN_NONE;
97 }
98 
99 
100 static PyObject *
test_unicode(PyObject * Py_UNUSED (module),PyObject * Py_UNUSED (args))101 test_unicode(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
102 {
103     PyObject *str = PyUnicode_FromString("abc");
104     if (str == _Py_NULL) {
105         return _Py_NULL;
106     }
107 
108     assert(PyUnicode_Check(str));
109     assert(PyUnicode_GET_LENGTH(str) == 3);
110 
111     // gh-92800: test PyUnicode_READ()
112     const void* data = PyUnicode_DATA(str);
113     assert(data != _Py_NULL);
114     int kind = PyUnicode_KIND(str);
115     assert(kind == PyUnicode_1BYTE_KIND);
116     assert(PyUnicode_READ(kind, data, 0) == 'a');
117 
118     // gh-92800: test PyUnicode_READ() casts
119     const void* const_data = PyUnicode_DATA(str);
120     unsigned int ukind = static_cast<unsigned int>(kind);
121     assert(PyUnicode_READ(ukind, const_data, 2) == 'c');
122 
123     assert(PyUnicode_READ_CHAR(str, 1) == 'b');
124 
125     Py_DECREF(str);
126     Py_RETURN_NONE;
127 }
128 
129 /* Test a `new`-allocated object with a virtual method.
130  * (https://github.com/python/cpython/issues/94731) */
131 
132 class VirtualPyObject : public PyObject {
133 public:
134     VirtualPyObject();
~VirtualPyObject()135     virtual ~VirtualPyObject() {
136         delete [] internal_data;
137         --instance_count;
138     }
set_internal_data()139     virtual void set_internal_data() {
140         internal_data[0] = 1;
141     }
dealloc(PyObject * o)142     static void dealloc(PyObject* o) {
143         delete static_cast<VirtualPyObject*>(o);
144     }
145 
146     // Number of "living" instances
147     static int instance_count;
148 private:
149     // buffer that can get corrupted
150     int* internal_data;
151 };
152 
153 int VirtualPyObject::instance_count = 0;
154 
155 PyType_Slot VirtualPyObject_Slots[] = {
156     {Py_tp_free, (void*)VirtualPyObject::dealloc},
157     {0, _Py_NULL},
158 };
159 
160 PyType_Spec VirtualPyObject_Spec = {
161     /* .name */ STR(NAME) ".VirtualPyObject",
162     /* .basicsize */ sizeof(VirtualPyObject),
163     /* .itemsize */ 0,
164     /* .flags */ Py_TPFLAGS_DEFAULT,
165     /* .slots */ VirtualPyObject_Slots,
166 };
167 
VirtualPyObject()168 VirtualPyObject::VirtualPyObject() {
169     // Create a temporary type (just so we don't need to store it)
170     PyObject *type = PyType_FromSpec(&VirtualPyObject_Spec);
171     // no good way to signal failure from a C++ constructor, so use assert
172     // for error handling
173     assert(type);
174     assert(PyObject_Init(this, (PyTypeObject *)type));
175     Py_DECREF(type);
176     internal_data = new int[50];
177     ++instance_count;
178 }
179 
180 static PyObject *
test_virtual_object(PyObject * Py_UNUSED (module),PyObject * Py_UNUSED (args))181 test_virtual_object(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
182 {
183     VirtualPyObject* obj = new VirtualPyObject();
184     obj->set_internal_data();
185     Py_DECREF(obj);
186     if (VirtualPyObject::instance_count != 0) {
187         return PyErr_Format(
188             PyExc_AssertionError,
189             "instance_count should be 0, got %d",
190             VirtualPyObject::instance_count);
191     }
192     Py_RETURN_NONE;
193 }
194 
195 static PyMethodDef _testcppext_methods[] = {
196     {"add", _testcppext_add, METH_VARARGS, _testcppext_add_doc},
197     {"test_api_casts", test_api_casts, METH_NOARGS, _Py_NULL},
198     {"test_unicode", test_unicode, METH_NOARGS, _Py_NULL},
199     {"test_virtual_object", test_virtual_object, METH_NOARGS, _Py_NULL},
200     // Note: _testcppext_exec currently runs all test functions directly.
201     // When adding a new one, add a call there.
202 
203     {_Py_NULL, _Py_NULL, 0, _Py_NULL}  /* sentinel */
204 };
205 
206 
207 static int
_testcppext_exec(PyObject * module)208 _testcppext_exec(PyObject *module)
209 {
210     if (PyModule_AddIntMacro(module, __cplusplus) < 0) {
211         return -1;
212     }
213 
214     PyObject *result;
215 
216     result = PyObject_CallMethod(module, "test_api_casts", "");
217     if (!result) return -1;
218     Py_DECREF(result);
219 
220     result = PyObject_CallMethod(module, "test_unicode", "");
221     if (!result) return -1;
222     Py_DECREF(result);
223 
224     result = PyObject_CallMethod(module, "test_virtual_object", "");
225     if (!result) return -1;
226     Py_DECREF(result);
227 
228     return 0;
229 }
230 
231 static PyModuleDef_Slot _testcppext_slots[] = {
232     {Py_mod_exec, reinterpret_cast<void*>(_testcppext_exec)},
233     {0, _Py_NULL}
234 };
235 
236 
237 PyDoc_STRVAR(_testcppext_doc, "C++ test extension.");
238 
239 static struct PyModuleDef _testcppext_module = {
240     PyModuleDef_HEAD_INIT,  // m_base
241     STR(NAME),  // m_name
242     _testcppext_doc,  // m_doc
243     0,  // m_size
244     _testcppext_methods,  // m_methods
245     _testcppext_slots,  // m_slots
246     _Py_NULL,  // m_traverse
247     _Py_NULL,  // m_clear
248     _Py_NULL,  // m_free
249 };
250 
251 #define _FUNC_NAME(NAME) PyInit_ ## NAME
252 #define FUNC_NAME(NAME) _FUNC_NAME(NAME)
253 
254 PyMODINIT_FUNC
FUNC_NAME(NAME)255 FUNC_NAME(NAME)(void)
256 {
257     return PyModuleDef_Init(&_testcppext_module);
258 }
259