1212# define NAME _testcpp03ext
1313#endif
1414
15+ #define _STR (NAME ) #NAME
16+ #define STR (NAME ) _STR(NAME)
17+
1518PyDoc_STRVAR (_testcppext_add_doc,
1619" add(x, y)\n "
1720" \n "
@@ -123,11 +126,77 @@ test_unicode(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
123126 Py_RETURN_NONE;
124127}
125128
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 ();
135+ virtual ~VirtualPyObject () {
136+ delete [] internal_data;
137+ --instance_count;
138+ }
139+ virtual void set_internal_data () {
140+ internal_data[0 ] = 1 ;
141+ }
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+
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 *
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+ }
126194
127195static PyMethodDef _testcppext_methods[] = {
128196 {" add" , _testcppext_add, METH_VARARGS, _testcppext_add_doc},
129197 {" test_api_casts" , test_api_casts, METH_NOARGS, _Py_NULL},
130198 {" test_unicode" , test_unicode, METH_NOARGS, _Py_NULL},
199+ {" test_virtual_object" , test_virtual_object, METH_NOARGS, _Py_NULL},
131200 // Note: _testcppext_exec currently runs all test functions directly.
132201 // When adding a new one, add a call there.
133202
@@ -152,6 +221,10 @@ _testcppext_exec(PyObject *module)
152221 if (!result) return -1 ;
153222 Py_DECREF (result);
154223
224+ result = PyObject_CallMethod (module , " test_virtual_object" , " " );
225+ if (!result) return -1 ;
226+ Py_DECREF (result);
227+
155228 return 0 ;
156229}
157230
@@ -163,9 +236,6 @@ static PyModuleDef_Slot _testcppext_slots[] = {
163236
164237PyDoc_STRVAR (_testcppext_doc, " C++ test extension." );
165238
166- #define _STR (NAME ) #NAME
167- #define STR (NAME ) _STR(NAME)
168-
169239static struct PyModuleDef _testcppext_module = {
170240 PyModuleDef_HEAD_INIT, // m_base
171241 STR (NAME), // m_name
0 commit comments