Skip to content

Commit 7f164c7

Browse files
committed
Make all classes with the same instance size derive from a common base
In order to fully satisfy Python's inheritance type layout requirements, all types should have a common 'solid' base. A solid base is one which has the same instance size as the derived type (not counting the space required for the optional `dict_ptr` and `weakrefs_ptr`). Thus, `object` does not qualify as a solid base for pybind11 types and this can lead to issues with multiple inheritance. To get around this, new base types are created: one per unique instance size. There is going to be very few of these bases. They ensure Python's MRO checks will pass when multiple bases are involved.
1 parent 13a47d4 commit 7f164c7

File tree

6 files changed

+426
-273
lines changed

6 files changed

+426
-273
lines changed

include/pybind11/cast.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ struct type_info {
2626
PyTypeObject *type;
2727
size_t type_size;
2828
void (*init_holder)(PyObject *, const void *);
29+
void (*dealloc)(PyObject *);
2930
std::vector<PyObject *(*)(PyObject *, PyTypeObject *)> implicit_conversions;
3031
std::vector<std::pair<const std::type_info *, void *(*)(void *)>> implicit_casts;
3132
std::vector<bool (*)(PyObject *, void *&)> *direct_conversions;

include/pybind11/common.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@
120120
#define PYBIND11_SLICE_OBJECT PyObject
121121
#define PYBIND11_FROM_STRING PyUnicode_FromString
122122
#define PYBIND11_STR_TYPE ::pybind11::str
123-
#define PYBIND11_OB_TYPE(ht_type) (ht_type).ob_base.ob_base.ob_type
124123
#define PYBIND11_PLUGIN_IMPL(name) \
125124
extern "C" PYBIND11_EXPORT PyObject *PyInit_##name()
126125
#else
@@ -139,7 +138,6 @@
139138
#define PYBIND11_SLICE_OBJECT PySliceObject
140139
#define PYBIND11_FROM_STRING PyString_FromString
141140
#define PYBIND11_STR_TYPE ::pybind11::bytes
142-
#define PYBIND11_OB_TYPE(ht_type) (ht_type).ob_type
143141
#define PYBIND11_PLUGIN_IMPL(name) \
144142
static PyObject *pybind11_init_wrapper(); \
145143
extern "C" PYBIND11_EXPORT void init##name() { \
@@ -372,10 +370,14 @@ struct internals {
372370
std::unordered_map<std::string, void *> shared_data; // Custom data to be shared across extensions
373371
PyTypeObject *static_property_type;
374372
PyTypeObject *metatype;
373+
std::unordered_map<size_t, PyObject *> bases; // one base type per `instance_size` (very few)
375374
#if defined(WITH_THREAD)
376375
decltype(PyThread_create_key()) tstate = 0; // Usually an int but a long on Cygwin64 with Python 3.x
377376
PyInterpreterState *istate = nullptr;
378377
#endif
378+
379+
/// Return the appropriate base type for the given instance size
380+
PyObject *get_base(size_t instance_size);
379381
};
380382

381383
/// Return a reference to the current 'internals' information

include/pybind11/detail/class_.h

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,5 +136,297 @@ inline PyTypeObject* make_metatype() {
136136
return type;
137137
}
138138

139+
extern "C" inline PyObject *object_new(PyTypeObject *type, PyObject *, PyObject *) {
140+
PyObject *self = type->tp_alloc(type, 0);
141+
auto instance = (instance_essentials<void> *) self;
142+
auto tinfo = get_type_info(type);
143+
instance->value = ::operator new(tinfo->type_size);
144+
instance->owned = true;
145+
instance->holder_constructed = false;
146+
get_internals().registered_instances.emplace(instance->value, self);
147+
return self;
148+
}
149+
150+
extern "C" inline int object_init(PyObject *self, PyObject *, PyObject *) {
151+
PyTypeObject *type = Py_TYPE(self);
152+
std::string msg;
153+
#if defined(PYPY_VERSION)
154+
msg += handle((PyObject *) type).attr("__module__").cast<std::string>() + ".";
155+
#endif
156+
msg += type->tp_name;
157+
msg += ": No constructor defined!";
158+
PyErr_SetString(PyExc_TypeError, msg.c_str());
159+
return -1;
160+
}
161+
162+
extern "C" inline void object_dealloc(PyObject *self) {
163+
auto instance = (instance_essentials<void> *) self;
164+
if (instance->value) {
165+
auto type = Py_TYPE(self);
166+
get_type_info(type)->dealloc(self);
167+
168+
auto &registered_instances = get_internals().registered_instances;
169+
auto range = registered_instances.equal_range(instance->value);
170+
bool found = false;
171+
for (auto it = range.first; it != range.second; ++it) {
172+
if (type == Py_TYPE(it->second)) {
173+
registered_instances.erase(it);
174+
found = true;
175+
break;
176+
}
177+
}
178+
if (!found)
179+
pybind11_fail("object_dealloc(): Tried to deallocate unregistered instance!");
180+
181+
if (instance->weakrefs)
182+
PyObject_ClearWeakRefs(self);
183+
184+
PyObject **dict_ptr = _PyObject_GetDictPtr(self);
185+
if (dict_ptr)
186+
Py_CLEAR(*dict_ptr);
187+
}
188+
Py_TYPE(self)->tp_free(self);
189+
}
190+
191+
extern "C" inline PyObject *object_get_dict(PyObject *self, void *) {
192+
PyObject *&dict = *_PyObject_GetDictPtr(self);
193+
if (!dict)
194+
dict = PyDict_New();
195+
Py_XINCREF(dict);
196+
return dict;
197+
}
198+
199+
extern "C" inline int object_set_dict(PyObject *self, PyObject *new_dict, void *) {
200+
if (!PyDict_Check(new_dict)) {
201+
PyErr_Format(PyExc_TypeError, "__dict__ must be set to a dictionary, not a '%.200s'",
202+
Py_TYPE(new_dict)->tp_name);
203+
return -1;
204+
}
205+
PyObject *&dict = *_PyObject_GetDictPtr(self);
206+
Py_INCREF(new_dict);
207+
Py_CLEAR(dict);
208+
dict = new_dict;
209+
return 0;
210+
}
211+
212+
static PyGetSetDef object_getset[] = {
213+
{const_cast<char*>("__dict__"), object_get_dict, object_set_dict, nullptr, nullptr},
214+
{nullptr, nullptr, nullptr, nullptr, nullptr}
215+
};
216+
217+
extern "C" inline int object_traverse(PyObject *self, visitproc visit, void *arg) {
218+
PyObject *&dict = *_PyObject_GetDictPtr(self);
219+
Py_VISIT(dict);
220+
return 0;
221+
}
222+
223+
extern "C" inline int object_clear(PyObject *self) {
224+
PyObject *&dict = *_PyObject_GetDictPtr(self);
225+
Py_CLEAR(dict);
226+
return 0;
227+
}
228+
229+
extern "C" inline int object_getbuffer(PyObject *obj, Py_buffer *view, int flags) {
230+
auto tinfo = get_type_info(Py_TYPE(obj));
231+
if (view == nullptr || obj == nullptr || !tinfo || !tinfo->get_buffer) {
232+
if (view)
233+
view->obj = nullptr;
234+
PyErr_SetString(PyExc_BufferError, "generic_type::getbuffer(): Internal error");
235+
return -1;
236+
}
237+
memset(view, 0, sizeof(Py_buffer));
238+
buffer_info *info = tinfo->get_buffer(obj, tinfo->get_buffer_data);
239+
view->obj = obj;
240+
view->ndim = 1;
241+
view->internal = info;
242+
view->buf = info->ptr;
243+
view->itemsize = (ssize_t) info->itemsize;
244+
view->len = view->itemsize;
245+
for (auto s : info->shape)
246+
view->len *= s;
247+
if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT)
248+
view->format = const_cast<char *>(info->format.c_str());
249+
if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) {
250+
view->ndim = (int) info->ndim;
251+
view->strides = (ssize_t *) &info->strides[0];
252+
view->shape = (ssize_t *) &info->shape[0];
253+
}
254+
Py_INCREF(view->obj);
255+
return 0;
256+
}
257+
258+
extern "C" inline void object_releasebuffer(PyObject *, Py_buffer *view) {
259+
delete (buffer_info *) view->internal;
260+
}
261+
262+
/// Creates a type which can be used as a common base for all classes with the same
263+
/// instance size, i.e. all classes with the same `sizeof(holder_type)`. This is
264+
/// needed in order to satisfy Python's requirements for multiple inheritance.
265+
inline PyObject *make_object_base_type(size_t instance_size) {
266+
auto name = "pybind11_object_" + std::to_string(instance_size);
267+
auto name_obj = reinterpret_steal<object>(PYBIND11_FROM_STRING(name.c_str()));
268+
269+
/* Danger zone: from now (and until PyType_Ready), make sure to
270+
issue no Python C API calls which could potentially invoke the
271+
garbage collector (the GC will call type_traverse(), which will in
272+
turn find the newly constructed type in an invalid state) */
273+
auto heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
274+
if (!heap_type)
275+
pybind11_fail("make_object_base_type(): error allocating type!");
276+
277+
heap_type->ht_name = name_obj.inc_ref().ptr();
278+
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
279+
heap_type->ht_qualname = name_obj.inc_ref().ptr();
280+
#endif
281+
282+
auto type = &heap_type->ht_type;
283+
type->tp_name = strdup(name.c_str());
284+
type->tp_base = &PyBaseObject_Type;
285+
type->tp_basicsize = static_cast<ssize_t>(instance_size);
286+
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
287+
288+
type->tp_new = object_new;
289+
type->tp_init = object_init;
290+
type->tp_dealloc = object_dealloc;
291+
292+
/* Support weak references (needed for the keep_alive feature) */
293+
type->tp_weaklistoffset = offsetof(instance_essentials<void>, weakrefs);
294+
295+
if (PyType_Ready(type) < 0)
296+
pybind11_fail("PyType_Ready failed in make_object_base_type():" + error_string());
297+
298+
assert(!PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC));
299+
return (PyObject *) heap_type;
300+
}
301+
302+
inline PyObject *internals::get_base(size_t instance_size) {
303+
auto it = bases.find(instance_size);
304+
if (it != bases.end()) {
305+
return it->second;
306+
} else {
307+
auto base = make_object_base_type(instance_size);
308+
bases[instance_size] = base;
309+
return base;
310+
}
311+
}
312+
313+
inline PyObject* make_new_python_type(const type_record &rec) {
314+
auto name = reinterpret_steal<object>(PYBIND11_FROM_STRING(rec.name));
315+
316+
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
317+
auto ht_qualname = name;
318+
if (rec.scope && hasattr(rec.scope, "__qualname__")) {
319+
ht_qualname = reinterpret_steal<object>(
320+
PyUnicode_FromFormat("%U.%U", rec.scope.attr("__qualname__").ptr(), name.ptr()));
321+
}
322+
#endif
323+
324+
object module;
325+
if (rec.scope) {
326+
if (hasattr(rec.scope, "__module__"))
327+
module = rec.scope.attr("__module__");
328+
else if (hasattr(rec.scope, "__name__"))
329+
module = rec.scope.attr("__name__");
330+
}
331+
332+
#if !defined(PYPY_VERSION)
333+
const auto full_name = module ? str(module).cast<std::string>() + "." + rec.name
334+
: std::string(rec.name);
335+
#else
336+
const auto full_name = std::string(rec.name);
337+
#endif
338+
339+
char *tp_doc = nullptr;
340+
if (rec.doc && options::show_user_defined_docstrings()) {
341+
/* Allocate memory for docstring (using PyObject_MALLOC, since
342+
Python will free this later on) */
343+
size_t size = strlen(rec.doc) + 1;
344+
tp_doc = (char *) PyObject_MALLOC(size);
345+
memcpy((void *) tp_doc, rec.doc, size);
346+
}
347+
348+
auto &internals = get_internals();
349+
auto bases = tuple(rec.bases);
350+
auto base = (bases.size() == 0) ? internals.get_base(rec.instance_size)
351+
: bases[0].ptr();
352+
353+
/* Danger zone: from now (and until PyType_Ready), make sure to
354+
issue no Python C API calls which could potentially invoke the
355+
garbage collector (the GC will call type_traverse(), which will in
356+
turn find the newly constructed type in an invalid state) */
357+
auto heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
358+
if (!heap_type)
359+
pybind11_fail(std::string(rec.name) + ": Unable to create type object!");
360+
361+
heap_type->ht_name = name.release().ptr();
362+
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
363+
heap_type->ht_qualname = ht_qualname.release().ptr();
364+
#endif
365+
366+
auto type = &heap_type->ht_type;
367+
type->tp_name = strdup(full_name.c_str());
368+
type->tp_doc = tp_doc;
369+
type->tp_base = (PyTypeObject *) handle(base).inc_ref().ptr();
370+
type->tp_basicsize = static_cast<ssize_t>(rec.instance_size);
371+
if (bases.size() > 0)
372+
type->tp_bases = bases.release().ptr();
373+
374+
/* Custom metaclass if requested (used for static properties) */
375+
if (rec.metaclass) {
376+
Py_INCREF(internals.metatype);
377+
Py_TYPE(type) = (PyTypeObject *) internals.metatype;
378+
}
379+
380+
/* Supported protocols */
381+
type->tp_as_number = &heap_type->as_number;
382+
type->tp_as_sequence = &heap_type->as_sequence;
383+
type->tp_as_mapping = &heap_type->as_mapping;
384+
385+
/* Flags */
386+
type->tp_flags |= Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
387+
#if PY_MAJOR_VERSION < 3
388+
type->tp_flags |= Py_TPFLAGS_CHECKTYPES;
389+
#endif
390+
391+
/* Support dynamic attributes */
392+
if (rec.dynamic_attr) {
393+
#if defined(PYPY_VERSION)
394+
pybind11_fail(std::string(rec.name) + ": dynamic attributes are "
395+
"currently not supported in "
396+
"conjunction with PyPy!");
397+
#endif
398+
type->tp_dictoffset = type->tp_basicsize; // place dict at the end
399+
type->tp_basicsize += sizeof(PyObject *); // and allocate enough space for it
400+
type->tp_getset = object_getset;
401+
type->tp_traverse = object_traverse;
402+
type->tp_clear = object_clear;
403+
type->tp_flags |= Py_TPFLAGS_HAVE_GC;
404+
}
405+
406+
if (rec.buffer_protocol) {
407+
type->tp_as_buffer = &heap_type->as_buffer;
408+
#if PY_MAJOR_VERSION < 3
409+
type->tp_flags |= Py_TPFLAGS_HAVE_NEWBUFFER;
410+
#endif
411+
heap_type->as_buffer.bf_getbuffer = object_getbuffer;
412+
heap_type->as_buffer.bf_releasebuffer = object_releasebuffer;
413+
}
414+
415+
if (PyType_Ready(type) < 0)
416+
pybind11_fail(std::string(rec.name) + ": PyType_Ready failed (" + error_string() + ")!");
417+
418+
assert(rec.dynamic_attr ? PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC)
419+
: !PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC));
420+
421+
/* Register type with the parent scope */
422+
if (rec.scope)
423+
setattr(rec.scope, rec.name, (PyObject *) type);
424+
425+
if (module) // Needed by pydoc
426+
setattr((PyObject *) type, "__module__", module);
427+
428+
return (PyObject *) type;
429+
}
430+
139431
NAMESPACE_END(detail)
140432
NAMESPACE_END(pybind11)

0 commit comments

Comments
 (0)