Track base class pointers of instances

This commits adds base class pointers of offset base classes (i.e. due
to multiple inheritance) to `registered_instances` so that if such a
pointer is returned we properly recognize it as an existing instance.

Without this, returning a base class pointer will cast to the existing
instance if the pointer happens to coincide with the instance pointer,
but constructs a new instance (quite possibly with a segfault, if
ownership is applied) for unequal base class pointers due to multiple
inheritance.
This commit is contained in:
Jason Rhinelander
2017-04-21 19:01:30 -04:00
parent 14e70650fe
commit 1f8a100d38
5 changed files with 102 additions and 21 deletions

View File

@@ -24,6 +24,7 @@ inline PyTypeObject *make_default_metaclass();
/// Additional type information which does not fit into the PyTypeObject
struct type_info {
PyTypeObject *type;
const std::type_info *cpptype;
size_t type_size;
void *(*operator_new)(size_t);
void (*init_holder)(PyObject *, const void *);
@@ -35,9 +36,11 @@ struct type_info {
void *get_buffer_data = nullptr;
/** A simple type never occurs as a (direct or indirect) parent
* of a class that makes use of multiple inheritance */
bool simple_type = true;
bool simple_type : 1;
/* True if there is no multiple inheritance in this type's inheritance tree */
bool simple_ancestors : 1;
/* for base vs derived holder_type checks */
bool default_holder = true;
bool default_holder : 1;
};
PYBIND11_NOINLINE inline internals &get_internals() {
@@ -197,7 +200,7 @@ inline PyThreadState *get_thread_state_unchecked() {
// Forward declarations
inline void keep_alive_impl(handle nurse, handle patient);
inline void register_instance(void *self);
inline void register_instance(void *self, const type_info *tinfo);
inline PyObject *make_new_instance(PyTypeObject *type, bool allocate_value = true);
class type_caster_generic {
@@ -338,7 +341,7 @@ public:
throw cast_error("unhandled return_value_policy: should not happen!");
}
register_instance(wrapper);
register_instance(wrapper, tinfo);
tinfo->init_holder(inst.ptr(), existing_holder);
return inst.release();

View File

@@ -185,18 +185,35 @@ inline PyTypeObject* make_default_metaclass() {
return type;
}
inline void register_instance(void *self) {
auto *inst = (instance_essentials<void> *) self;
get_internals().registered_instances.emplace(inst->value, self);
/// For multiple inheritance types we need to recursively register/deregister base pointers for any
/// base classes with pointers that are difference from the instance value pointer so that we can
/// correctly recognize an offset base class pointer. This calls a function with any offset base ptrs.
inline void traverse_offset_bases(void *valueptr, const detail::type_info *tinfo, void *self,
bool (*f)(void * /*parentptr*/, void * /*self*/)) {
for (handle h : reinterpret_borrow<tuple>(tinfo->type->tp_bases)) {
if (auto parent_tinfo = get_type_info((PyTypeObject *) h.ptr())) {
for (auto &c : parent_tinfo->implicit_casts) {
if (c.first == tinfo->cpptype) {
auto *parentptr = c.second(valueptr);
if (parentptr != valueptr)
f(parentptr, self);
traverse_offset_bases(parentptr, parent_tinfo, self, f);
break;
}
}
}
}
}
inline bool deregister_instance(void *self) {
auto *inst = (instance_essentials<void> *) self;
auto type = Py_TYPE(inst);
inline bool register_instance_impl(void *ptr, void *self) {
get_internals().registered_instances.emplace(ptr, self);
return true; // unused, but gives the same signature as the deregister func
}
inline bool deregister_instance_impl(void *ptr, void *self) {
auto &registered_instances = get_internals().registered_instances;
auto range = registered_instances.equal_range(inst->value);
auto range = registered_instances.equal_range(ptr);
for (auto it = range.first; it != range.second; ++it) {
if (type == Py_TYPE(it->second)) {
if (Py_TYPE(self) == Py_TYPE(it->second)) {
registered_instances.erase(it);
return true;
}
@@ -204,6 +221,21 @@ inline bool deregister_instance(void *self) {
return false;
}
inline void register_instance(void *self, const type_info *tinfo) {
auto *inst = (instance_essentials<void> *) self;
register_instance_impl(inst->value, self);
if (!tinfo->simple_ancestors)
traverse_offset_bases(inst->value, tinfo, self, register_instance_impl);
}
inline bool deregister_instance(void *self, const detail::type_info *tinfo) {
auto *inst = (instance_essentials<void> *) self;
bool ret = deregister_instance_impl(inst->value, self);
if (!tinfo->simple_ancestors)
traverse_offset_bases(inst->value, tinfo, self, deregister_instance_impl);
return ret;
}
/// Creates a new instance which, by default, includes allocation (but not construction of) the
/// wrapped C++ instance. If allocating value, the instance is registered; otherwise
/// register_instance will need to be called once the value has been assigned.
@@ -215,7 +247,7 @@ inline PyObject *make_new_instance(PyTypeObject *type, bool allocate_value /*= t
instance->holder_constructed = false;
if (allocate_value) {
instance->value = tinfo->operator_new(tinfo->type_size);
register_instance(self);
register_instance(self, tinfo);
} else {
instance->value = nullptr;
}
@@ -248,12 +280,15 @@ extern "C" inline int pybind11_object_init(PyObject *self, PyObject *, PyObject
inline void clear_instance(PyObject *self) {
auto instance = (instance_essentials<void> *) self;
bool has_value = instance->value;
type_info *tinfo = nullptr;
if (has_value || instance->holder_constructed) {
auto type = Py_TYPE(self);
get_type_info(type)->dealloc(self);
tinfo = get_type_info(type);
tinfo->dealloc(self);
}
if (has_value) {
if (!deregister_instance(self))
if (!tinfo) tinfo = get_type_info(Py_TYPE(self));
if (!deregister_instance(self, tinfo))
pybind11_fail("pybind11_object_dealloc(): Tried to deallocate unregistered instance!");
if (instance->weakrefs)

View File

@@ -813,10 +813,13 @@ protected:
/* Register supplemental type information in C++ dict */
auto *tinfo = new detail::type_info();
tinfo->type = (PyTypeObject *) m_ptr;
tinfo->cpptype = rec.type;
tinfo->type_size = rec.type_size;
tinfo->operator_new = rec.operator_new;
tinfo->init_holder = rec.init_holder;
tinfo->dealloc = rec.dealloc;
tinfo->simple_type = true;
tinfo->simple_ancestors = true;
auto &internals = get_internals();
auto tindex = std::type_index(*rec.type);
@@ -825,8 +828,14 @@ protected:
internals.registered_types_cpp[tindex] = tinfo;
internals.registered_types_py[m_ptr] = tinfo;
if (rec.bases.size() > 1 || rec.multiple_inheritance)
if (rec.bases.size() > 1 || rec.multiple_inheritance) {
mark_parents_nonsimple(tinfo->type);
tinfo->simple_ancestors = false;
}
else if (rec.bases.size() == 1) {
auto parent_tinfo = get_type_info((PyTypeObject *) rec.bases[0].ptr());
tinfo->simple_ancestors = parent_tinfo->simple_ancestors;
}
}
/// Helper function which tags all parents of a type using mult. inheritance