Skip to content

Commit 2c67767

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 031a4ad commit 2c67767

8 files changed

+509
-274
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/class_support.h

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,34 @@ inline PyTypeObject *make_static_property_type() {
8585

8686
#endif // PYPY
8787

88+
/** Inheriting from multiple C++ types in Python is not supported -- set an error instead.
89+
A Python definition (`class C(A, B): pass`) will call `tp_new` so we check for multiple
90+
C++ bases here. On the other hand, C++ type definitions (`py::class_<C, A, B>(m, "C")`)
91+
don't not use `tp_new` and will not trigger this error. */
92+
extern "C" inline PyObject *pybind11_meta_new(PyTypeObject *metaclass, PyObject *args,
93+
PyObject *kwargs) {
94+
PyObject *bases = PyTuple_GetItem(args, 1); // arguments: (name, bases, dict)
95+
if (!bases)
96+
return nullptr;
97+
98+
auto &internals = get_internals();
99+
auto num_cpp_bases = 0;
100+
for (auto base : reinterpret_borrow<tuple>(bases)) {
101+
auto base_type = (PyTypeObject *) base.ptr();
102+
auto instance_size = static_cast<size_t>(base_type->tp_basicsize);
103+
if (PyObject_IsSubclass(base.ptr(), internals.get_base(instance_size)))
104+
++num_cpp_bases;
105+
}
106+
107+
if (num_cpp_bases > 1) {
108+
PyErr_SetString(PyExc_TypeError, "Can't inherit from multiple C++ classes in Python."
109+
"Use py::class_ to define the class in C++ instead.");
110+
return nullptr;
111+
} else {
112+
return PyType_Type.tp_new(metaclass, args, kwargs);
113+
}
114+
}
115+
88116
/** Types with static properties need to handle `Type.static_prop = x` in a specific way.
89117
By default, Python replaces the `static_property` itself, but for wrapped C++ types
90118
we need to call `static_property.__set__()` in order to propagate the new value to
@@ -135,6 +163,8 @@ inline PyTypeObject* make_default_metaclass() {
135163
type->tp_name = name;
136164
type->tp_base = &PyType_Type;
137165
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
166+
167+
type->tp_new = pybind11_meta_new;
138168
type->tp_setattro = pybind11_meta_setattro;
139169

140170
if (PyType_Ready(type) < 0)
@@ -143,5 +173,328 @@ inline PyTypeObject* make_default_metaclass() {
143173
return type;
144174
}
145175

176+
/// Instance creation function for all pybind11 types. It only allocates space for the
177+
/// C++ object, but doesn't call the constructor -- an `__init__` function must do that.
178+
extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, PyObject *) {
179+
PyObject *self = type->tp_alloc(type, 0);
180+
auto instance = (instance_essentials<void> *) self;
181+
auto tinfo = get_type_info(type);
182+
instance->value = ::operator new(tinfo->type_size);
183+
instance->owned = true;
184+
instance->holder_constructed = false;
185+
get_internals().registered_instances.emplace(instance->value, self);
186+
return self;
187+
}
188+
189+
/// An `__init__` function constructs the C++ object. Users should provide at least one
190+
/// of these using `py::init` or directly with `.def(__init__, ...)`. Otherwise, the
191+
/// following default function will be used which simply throws an exception.
192+
extern "C" inline int pybind11_object_init(PyObject *self, PyObject *, PyObject *) {
193+
PyTypeObject *type = Py_TYPE(self);
194+
std::string msg;
195+
#if defined(PYPY_VERSION)
196+
msg += handle((PyObject *) type).attr("__module__").cast<std::string>() + ".";
197+
#endif
198+
msg += type->tp_name;
199+
msg += ": No constructor defined!";
200+
PyErr_SetString(PyExc_TypeError, msg.c_str());
201+
return -1;
202+
}
203+
204+
/// Instance destructor function for all pybind11 types. It calls `type_info.dealloc`
205+
/// to destroy the C++ object itself, while the rest is Python bookkeeping.
206+
extern "C" inline void pybind11_object_dealloc(PyObject *self) {
207+
auto instance = (instance_essentials<void> *) self;
208+
if (instance->value) {
209+
auto type = Py_TYPE(self);
210+
get_type_info(type)->dealloc(self);
211+
212+
auto &registered_instances = get_internals().registered_instances;
213+
auto range = registered_instances.equal_range(instance->value);
214+
bool found = false;
215+
for (auto it = range.first; it != range.second; ++it) {
216+
if (type == Py_TYPE(it->second)) {
217+
registered_instances.erase(it);
218+
found = true;
219+
break;
220+
}
221+
}
222+
if (!found)
223+
pybind11_fail("pybind11_object_dealloc(): Tried to deallocate unregistered instance!");
224+
225+
if (instance->weakrefs)
226+
PyObject_ClearWeakRefs(self);
227+
228+
PyObject **dict_ptr = _PyObject_GetDictPtr(self);
229+
if (dict_ptr)
230+
Py_CLEAR(*dict_ptr);
231+
}
232+
Py_TYPE(self)->tp_free(self);
233+
}
234+
235+
/** Create a type which can be used as a common base for all classes with the same
236+
instance size, i.e. all classes with the same `sizeof(holder_type)`. This is
237+
needed in order to satisfy Python's requirements for multiple inheritance.
238+
Return value: New reference. */
239+
inline PyObject *make_object_base_type(size_t instance_size) {
240+
auto name = "pybind11_object_" + std::to_string(instance_size);
241+
auto name_obj = reinterpret_steal<object>(PYBIND11_FROM_STRING(name.c_str()));
242+
243+
/* Danger zone: from now (and until PyType_Ready), make sure to
244+
issue no Python C API calls which could potentially invoke the
245+
garbage collector (the GC will call type_traverse(), which will in
246+
turn find the newly constructed type in an invalid state) */
247+
auto heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
248+
if (!heap_type)
249+
pybind11_fail("make_object_base_type(): error allocating type!");
250+
251+
heap_type->ht_name = name_obj.inc_ref().ptr();
252+
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
253+
heap_type->ht_qualname = name_obj.inc_ref().ptr();
254+
#endif
255+
256+
auto type = &heap_type->ht_type;
257+
type->tp_name = strdup(name.c_str());
258+
type->tp_base = &PyBaseObject_Type;
259+
type->tp_basicsize = static_cast<ssize_t>(instance_size);
260+
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
261+
262+
type->tp_new = pybind11_object_new;
263+
type->tp_init = pybind11_object_init;
264+
type->tp_dealloc = pybind11_object_dealloc;
265+
266+
/* Support weak references (needed for the keep_alive feature) */
267+
type->tp_weaklistoffset = offsetof(instance_essentials<void>, weakrefs);
268+
269+
if (PyType_Ready(type) < 0)
270+
pybind11_fail("PyType_Ready failed in make_object_base_type():" + error_string());
271+
272+
assert(!PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC));
273+
return (PyObject *) heap_type;
274+
}
275+
276+
/** Return the appropriate base type for the given instance size. The results are cached
277+
in `internals.bases` so that only a single base is ever created for any size value.
278+
Return value: Borrowed reference. */
279+
inline PyObject *internals::get_base(size_t instance_size) {
280+
auto it = bases.find(instance_size);
281+
if (it != bases.end()) {
282+
return it->second;
283+
} else {
284+
auto base = make_object_base_type(instance_size);
285+
bases[instance_size] = base;
286+
return base;
287+
}
288+
}
289+
290+
/// dynamic_attr: Support for `d = instance.__dict__`.
291+
extern "C" inline PyObject *pybind11_get_dict(PyObject *self, void *) {
292+
PyObject *&dict = *_PyObject_GetDictPtr(self);
293+
if (!dict)
294+
dict = PyDict_New();
295+
Py_XINCREF(dict);
296+
return dict;
297+
}
298+
299+
/// dynamic_attr: Support for `instance.__dict__ = dict()`.
300+
extern "C" inline int pybind11_set_dict(PyObject *self, PyObject *new_dict, void *) {
301+
if (!PyDict_Check(new_dict)) {
302+
PyErr_Format(PyExc_TypeError, "__dict__ must be set to a dictionary, not a '%.200s'",
303+
Py_TYPE(new_dict)->tp_name);
304+
return -1;
305+
}
306+
PyObject *&dict = *_PyObject_GetDictPtr(self);
307+
Py_INCREF(new_dict);
308+
Py_CLEAR(dict);
309+
dict = new_dict;
310+
return 0;
311+
}
312+
313+
/// dynamic_attr: Allow the garbage collector to traverse the internal instance `__dict__`.
314+
extern "C" inline int pybind11_traverse(PyObject *self, visitproc visit, void *arg) {
315+
PyObject *&dict = *_PyObject_GetDictPtr(self);
316+
Py_VISIT(dict);
317+
return 0;
318+
}
319+
320+
/// dynamic_attr: Allow the GC to clear the dictionary.
321+
extern "C" inline int pybind11_clear(PyObject *self) {
322+
PyObject *&dict = *_PyObject_GetDictPtr(self);
323+
Py_CLEAR(dict);
324+
return 0;
325+
}
326+
327+
/// Give instances of this type a `__dict__` and opt into garbage collection.
328+
inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type) {
329+
auto type = &heap_type->ht_type;
330+
#if defined(PYPY_VERSION)
331+
pybind11_fail(std::string(type->tp_name) + ": dynamic attributes are "
332+
"currently not supported in "
333+
"conjunction with PyPy!");
334+
#endif
335+
type->tp_flags |= Py_TPFLAGS_HAVE_GC;
336+
type->tp_dictoffset = type->tp_basicsize; // place dict at the end
337+
type->tp_basicsize += sizeof(PyObject *); // and allocate enough space for it
338+
type->tp_traverse = pybind11_traverse;
339+
type->tp_clear = pybind11_clear;
340+
341+
static PyGetSetDef getset[] = {
342+
{const_cast<char*>("__dict__"), pybind11_get_dict, pybind11_set_dict, nullptr, nullptr},
343+
{nullptr, nullptr, nullptr, nullptr, nullptr}
344+
};
345+
type->tp_getset = getset;
346+
}
347+
348+
/// buffer_protocol: Fill in the view as specified by flags.
349+
extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int flags) {
350+
auto tinfo = get_type_info(Py_TYPE(obj));
351+
if (view == nullptr || obj == nullptr || !tinfo || !tinfo->get_buffer) {
352+
if (view)
353+
view->obj = nullptr;
354+
PyErr_SetString(PyExc_BufferError, "generic_type::getbuffer(): Internal error");
355+
return -1;
356+
}
357+
memset(view, 0, sizeof(Py_buffer));
358+
buffer_info *info = tinfo->get_buffer(obj, tinfo->get_buffer_data);
359+
view->obj = obj;
360+
view->ndim = 1;
361+
view->internal = info;
362+
view->buf = info->ptr;
363+
view->itemsize = (ssize_t) info->itemsize;
364+
view->len = view->itemsize;
365+
for (auto s : info->shape)
366+
view->len *= s;
367+
if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT)
368+
view->format = const_cast<char *>(info->format.c_str());
369+
if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) {
370+
view->ndim = (int) info->ndim;
371+
view->strides = (ssize_t *) &info->strides[0];
372+
view->shape = (ssize_t *) &info->shape[0];
373+
}
374+
Py_INCREF(view->obj);
375+
return 0;
376+
}
377+
378+
/// buffer_protocol: Release the resources of the buffer.
379+
extern "C" inline void pybind11_releasebuffer(PyObject *, Py_buffer *view) {
380+
delete (buffer_info *) view->internal;
381+
}
382+
383+
/// Give this type a buffer interface.
384+
inline void enable_buffer_protocol(PyHeapTypeObject *heap_type) {
385+
heap_type->ht_type.tp_as_buffer = &heap_type->as_buffer;
386+
#if PY_MAJOR_VERSION < 3
387+
heap_type->ht_type.tp_flags |= Py_TPFLAGS_HAVE_NEWBUFFER;
388+
#endif
389+
390+
heap_type->as_buffer.bf_getbuffer = pybind11_getbuffer;
391+
heap_type->as_buffer.bf_releasebuffer = pybind11_releasebuffer;
392+
}
393+
394+
/** Create a brand new Python type according to the `type_record` specification.
395+
Return value: New reference. */
396+
inline PyObject* make_new_python_type(const type_record &rec) {
397+
auto name = reinterpret_steal<object>(PYBIND11_FROM_STRING(rec.name));
398+
399+
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
400+
auto ht_qualname = name;
401+
if (rec.scope && hasattr(rec.scope, "__qualname__")) {
402+
ht_qualname = reinterpret_steal<object>(
403+
PyUnicode_FromFormat("%U.%U", rec.scope.attr("__qualname__").ptr(), name.ptr()));
404+
}
405+
#endif
406+
407+
object module;
408+
if (rec.scope) {
409+
if (hasattr(rec.scope, "__module__"))
410+
module = rec.scope.attr("__module__");
411+
else if (hasattr(rec.scope, "__name__"))
412+
module = rec.scope.attr("__name__");
413+
}
414+
415+
#if !defined(PYPY_VERSION)
416+
const auto full_name = module ? str(module).cast<std::string>() + "." + rec.name
417+
: std::string(rec.name);
418+
#else
419+
const auto full_name = std::string(rec.name);
420+
#endif
421+
422+
char *tp_doc = nullptr;
423+
if (rec.doc && options::show_user_defined_docstrings()) {
424+
/* Allocate memory for docstring (using PyObject_MALLOC, since
425+
Python will free this later on) */
426+
size_t size = strlen(rec.doc) + 1;
427+
tp_doc = (char *) PyObject_MALLOC(size);
428+
memcpy((void *) tp_doc, rec.doc, size);
429+
}
430+
431+
auto &internals = get_internals();
432+
auto bases = tuple(rec.bases);
433+
auto base = (bases.size() == 0) ? internals.get_base(rec.instance_size)
434+
: bases[0].ptr();
435+
436+
/* Danger zone: from now (and until PyType_Ready), make sure to
437+
issue no Python C API calls which could potentially invoke the
438+
garbage collector (the GC will call type_traverse(), which will in
439+
turn find the newly constructed type in an invalid state) */
440+
auto heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
441+
if (!heap_type)
442+
pybind11_fail(std::string(rec.name) + ": Unable to create type object!");
443+
444+
heap_type->ht_name = name.release().ptr();
445+
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
446+
heap_type->ht_qualname = ht_qualname.release().ptr();
447+
#endif
448+
449+
auto type = &heap_type->ht_type;
450+
type->tp_name = strdup(full_name.c_str());
451+
type->tp_doc = tp_doc;
452+
type->tp_base = (PyTypeObject *) handle(base).inc_ref().ptr();
453+
type->tp_basicsize = static_cast<ssize_t>(rec.instance_size);
454+
if (bases.size() > 0)
455+
type->tp_bases = bases.release().ptr();
456+
457+
/* Don't inherit base __init__ */
458+
type->tp_init = pybind11_object_init;
459+
460+
/* Custom metaclass if requested (used for static properties) */
461+
if (rec.metaclass) {
462+
Py_INCREF(internals.default_metaclass);
463+
Py_TYPE(type) = (PyTypeObject *) internals.default_metaclass;
464+
}
465+
466+
/* Supported protocols */
467+
type->tp_as_number = &heap_type->as_number;
468+
type->tp_as_sequence = &heap_type->as_sequence;
469+
type->tp_as_mapping = &heap_type->as_mapping;
470+
471+
/* Flags */
472+
type->tp_flags |= Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
473+
#if PY_MAJOR_VERSION < 3
474+
type->tp_flags |= Py_TPFLAGS_CHECKTYPES;
475+
#endif
476+
477+
if (rec.dynamic_attr)
478+
enable_dynamic_attributes(heap_type);
479+
480+
if (rec.buffer_protocol)
481+
enable_buffer_protocol(heap_type);
482+
483+
if (PyType_Ready(type) < 0)
484+
pybind11_fail(std::string(rec.name) + ": PyType_Ready failed (" + error_string() + ")!");
485+
486+
assert(rec.dynamic_attr ? PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC)
487+
: !PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC));
488+
489+
/* Register type with the parent scope */
490+
if (rec.scope)
491+
setattr(rec.scope, rec.name, (PyObject *) type);
492+
493+
if (module) // Needed by pydoc
494+
setattr((PyObject *) type, "__module__", module);
495+
496+
return (PyObject *) type;
497+
}
498+
146499
NAMESPACE_END(detail)
147500
NAMESPACE_END(pybind11)

0 commit comments

Comments
 (0)