Skip to content

Commit f09b8c6

Browse files
encukouda-woods
andcommitted
gh-94731: Add a failing test
Co-Authored-By: da-woods <[email protected]>
1 parent ec5db53 commit f09b8c6

File tree

1 file changed

+72
-3
lines changed

1 file changed

+72
-3
lines changed

Lib/test/_testcppext.cpp

+72-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
# define NAME _testcpp03ext
1313
#endif
1414

15+
#define _STR(NAME) #NAME
16+
#define STR(NAME) _STR(NAME)
17+
1518
PyDoc_STRVAR(_testcppext_add_doc,
1619
"add(x, y)\n"
1720
"\n"
@@ -123,11 +126,76 @@ 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+
.flags = Py_TPFLAGS_DEFAULT,
164+
.slots = VirtualPyObject_Slots,
165+
};
166+
167+
VirtualPyObject::VirtualPyObject() {
168+
// Create a temporary type (just so we don't need to store it)
169+
PyObject *ipotype = PyType_FromSpec(&VirtualPyObject_Spec);
170+
// no good way to signal failure from a C++ constructor, so use assert
171+
// for error handling
172+
assert(ipotype);
173+
assert(PyObject_Init(this, (PyTypeObject *)ipotype));
174+
Py_DECREF(ipotype);
175+
internal_data = new int[50];
176+
++instance_count;
177+
}
178+
179+
static PyObject *
180+
test_virtual_object(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
181+
{
182+
VirtualPyObject* obj = new VirtualPyObject();
183+
obj->set_internal_data();
184+
Py_DECREF(obj);
185+
if (VirtualPyObject::instance_count != 0) {
186+
return PyErr_Format(
187+
PyExc_AssertionError,
188+
"instance_count should be 0, got %d",
189+
VirtualPyObject::instance_count);
190+
}
191+
Py_RETURN_NONE;
192+
}
126193

127194
static PyMethodDef _testcppext_methods[] = {
128195
{"add", _testcppext_add, METH_VARARGS, _testcppext_add_doc},
129196
{"test_api_casts", test_api_casts, METH_NOARGS, _Py_NULL},
130197
{"test_unicode", test_unicode, METH_NOARGS, _Py_NULL},
198+
{"test_virtual_object", test_virtual_object, METH_NOARGS, _Py_NULL},
131199
// Note: _testcppext_exec currently runs all test functions directly.
132200
// When adding a new one, add a call there.
133201

@@ -152,6 +220,10 @@ _testcppext_exec(PyObject *module)
152220
if (!result) return -1;
153221
Py_DECREF(result);
154222

223+
result = PyObject_CallMethod(module, "test_virtual_object", "");
224+
if (!result) return -1;
225+
Py_DECREF(result);
226+
155227
return 0;
156228
}
157229

@@ -163,9 +235,6 @@ static PyModuleDef_Slot _testcppext_slots[] = {
163235

164236
PyDoc_STRVAR(_testcppext_doc, "C++ test extension.");
165237

166-
#define _STR(NAME) #NAME
167-
#define STR(NAME) _STR(NAME)
168-
169238
static struct PyModuleDef _testcppext_module = {
170239
PyModuleDef_HEAD_INIT, // m_base
171240
STR(NAME), // m_name

0 commit comments

Comments
 (0)