Skip to content

Commit 6845aae

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 ee25ff0 commit 6845aae

File tree

6 files changed

+432
-273
lines changed

6 files changed

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

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,5 +141,303 @@ inline PyTypeObject* make_default_metaclass() {
141141
return type;
142142
}
143143

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

include/pybind11/common.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@
122122
#define PYBIND11_SLICE_OBJECT PyObject
123123
#define PYBIND11_FROM_STRING PyUnicode_FromString
124124
#define PYBIND11_STR_TYPE ::pybind11::str
125-
#define PYBIND11_OB_TYPE(ht_type) (ht_type).ob_base.ob_base.ob_type
126125
#define PYBIND11_PLUGIN_IMPL(name) \
127126
extern "C" PYBIND11_EXPORT PyObject *PyInit_##name()
128127
#else
@@ -141,7 +140,6 @@
141140
#define PYBIND11_SLICE_OBJECT PySliceObject
142141
#define PYBIND11_FROM_STRING PyString_FromString
143142
#define PYBIND11_STR_TYPE ::pybind11::bytes
144-
#define PYBIND11_OB_TYPE(ht_type) (ht_type).ob_type
145143
#define PYBIND11_PLUGIN_IMPL(name) \
146144
static PyObject *pybind11_init_wrapper(); \
147145
extern "C" PYBIND11_EXPORT void init##name() { \
@@ -363,10 +361,14 @@ struct internals {
363361
std::unordered_map<std::string, void *> shared_data; // Custom data to be shared across extensions
364362
PyTypeObject *static_property_type;
365363
PyTypeObject *default_metaclass;
364+
std::unordered_map<size_t, PyObject *> bases; // one base type per `instance_size` (very few)
366365
#if defined(WITH_THREAD)
367366
decltype(PyThread_create_key()) tstate = 0; // Usually an int but a long on Cygwin64 with Python 3.x
368367
PyInterpreterState *istate = nullptr;
369368
#endif
369+
370+
/// Return the appropriate base type for the given instance size
371+
PyObject *get_base(size_t instance_size);
370372
};
371373

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

0 commit comments

Comments
 (0)