Skip to content

Commit cca20a7

Browse files
committed
Fix occassional segfault introduced by pybind#960
The fix for pybind#960 could result a type being registered multiple times if its `__init__` is called multiple times. This can happen perfectly ordinarily when python-side multiple inheritance is involved: for example, with a diamond inheritance pattern with each intermediate classes invoking the parent constructor. With the change in pybind#960, the multiple `__init__` calls meant `register_instance` was called multiple times, but the deletion only deleted it once. Thus, if a future instance of the same type was allocated at the same location, pybind would pick it up as a registered type. This fixes the issue by tracking whether a value pointer has been registered to avoid both double-registering it. (There's also a slight optimization of not needing to do a registered_instances lookup when the type is known not registered, but this is secondary).
1 parent 85d63c3 commit cca20a7

File tree

4 files changed

+39
-12
lines changed

4 files changed

+39
-12
lines changed

include/pybind11/cast.h

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -288,13 +288,24 @@ struct value_and_holder {
288288
bool holder_constructed() const {
289289
return inst->simple_layout
290290
? inst->simple_holder_constructed
291-
: inst->nonsimple.holder_constructed[index];
291+
: inst->nonsimple.status[index] & instance::status_holder_constructed;
292292
}
293293
void set_holder_constructed() {
294294
if (inst->simple_layout)
295295
inst->simple_holder_constructed = true;
296296
else
297-
inst->nonsimple.holder_constructed[index] = true;
297+
inst->nonsimple.status[index] |= instance::status_holder_constructed;
298+
}
299+
bool instance_registered() const {
300+
return inst->simple_layout
301+
? inst->simple_instance_registered
302+
: inst->nonsimple.status[index] & instance::status_instance_registered;
303+
}
304+
void set_instance_registered() {
305+
if (inst->simple_layout)
306+
inst->simple_instance_registered = true;
307+
else
308+
inst->nonsimple.status[index] |= instance::status_instance_registered;
298309
}
299310
};
300311

@@ -395,6 +406,7 @@ PYBIND11_NOINLINE inline void instance::allocate_layout() {
395406
if (simple_layout) {
396407
simple_value_holder[0] = nullptr;
397408
simple_holder_constructed = false;
409+
simple_instance_registered = false;
398410
}
399411
else { // multiple base types or a too-large holder
400412
// Allocate space to hold: [v1*][h1][v2*][h2]...[bb...] where [vN*] is a value pointer,
@@ -407,7 +419,7 @@ PYBIND11_NOINLINE inline void instance::allocate_layout() {
407419
space += t->holder_size_in_ptrs; // holder instance
408420
}
409421
size_t flags_at = space;
410-
space += size_in_ptrs(n_types * sizeof(bool)); // holder constructed flags
422+
space += size_in_ptrs(n_types); // status bytes (holder_constructed and instance_registered)
411423

412424
// Allocate space for flags, values, and holders, and initialize it to 0 (flags and values,
413425
// in particular, need to be 0). Use Python's memory allocation functions: in Python 3.6
@@ -422,7 +434,7 @@ PYBIND11_NOINLINE inline void instance::allocate_layout() {
422434
if (!nonsimple.values_and_holders) throw std::bad_alloc();
423435
std::memset(nonsimple.values_and_holders, 0, space * sizeof(void *));
424436
#endif
425-
nonsimple.holder_constructed = reinterpret_cast<bool *>(&nonsimple.values_and_holders[flags_at]);
437+
nonsimple.status = reinterpret_cast<uint8_t *>(&nonsimple.values_and_holders[flags_at]);
426438
}
427439
owned = true;
428440
}

include/pybind11/class_support.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,8 @@ inline bool deregister_instance(instance *self, void *valptr, const type_info *t
234234

235235
/// Instance creation function for all pybind11 types. It only allocates space for the C++ object
236236
/// (or multiple objects, for Python-side inheritance from multiple pybind11 types), but doesn't
237-
/// call the constructor -- an `__init__` function must do that. `register_instance` will need to
238-
/// be called after the object has been initialized.
237+
/// call the constructor -- an `__init__` function must do that (followed by an `init_instance`
238+
/// to set up the holder and register the instance).
239239
inline PyObject *make_new_instance(PyTypeObject *type, bool allocate_value /*= true (in cast.h)*/) {
240240
#if defined(PYPY_VERSION)
241241
// PyPy gets tp_basicsize wrong (issue 2482) under multiple inheritance when the first inherited
@@ -314,9 +314,11 @@ inline void clear_instance(PyObject *self) {
314314
// Deallocate any values/holders, if present:
315315
for (auto &v_h : values_and_holders(instance)) {
316316
if (v_h) {
317-
// This is allowed to fail (the type might have been allocated but not
318-
// initialized/registered):
319-
deregister_instance(instance, v_h.value_ptr(), v_h.type);
317+
318+
// We have to deregister before we call dealloc because, for virtual MI types, we still
319+
// need to be able to get the parent pointers.
320+
if (v_h.instance_registered() && !deregister_instance(instance, v_h.value_ptr(), v_h.type))
321+
pybind11_fail("pybind11_object_dealloc(): Tried to deallocate unregistered instance!");
320322

321323
if (instance->owned || v_h.holder_constructed())
322324
v_h.type->dealloc(v_h);

include/pybind11/common.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ struct instance {
375375
void *simple_value_holder[1 + instance_simple_holder_in_ptrs()];
376376
struct {
377377
void **values_and_holders;
378-
bool *holder_constructed;
378+
uint8_t *status;
379379
} nonsimple;
380380
};
381381
/// Weak references (needed for keep alive):
@@ -396,14 +396,20 @@ struct instance {
396396
* python side. Non-simple layout allocates the required amount of memory to have multiple
397397
* bound C++ classes as parents. Under this layout, `nonsimple.values_and_holders` is set to a
398398
* pointer to allocated space of the required space to hold a a sequence of value pointers and
399-
* holders followed by a set of holder-constructed flags (1 byte each), i.e.
399+
* holders followed `status`, a set of bit flags (1 byte each), i.e.
400400
* [val1*][holder1][val2*][holder2]...[bb...] where each [block] is rounded up to a multiple of
401401
* `sizeof(void *)`. `nonsimple.holder_constructed` is, for convenience, a pointer to the
402402
* beginning of the [bb...] block (but not independently allocated).
403+
*
404+
* Status bits indicate whether the associated holder is constructed (&
405+
* status_holder_constructed) and whether the value pointer is registered (&
406+
* status_instance_registered) in `registered_instances`.
403407
*/
404408
bool simple_layout : 1;
405409
/// For simple layout, tracks whether the holder has been constructed
406410
bool simple_holder_constructed : 1;
411+
/// For simple layout, tracks whether the instance is registered in `registered_instances`
412+
bool simple_instance_registered : 1;
407413
/// If true, get_internals().patients has an entry for this object
408414
bool has_patients : 1;
409415

@@ -416,6 +422,10 @@ struct instance {
416422
/// Returns the value_and_holder wrapper for the given type (or the first, if `find_type`
417423
/// omitted)
418424
value_and_holder get_value_and_holder(const type_info *find_type = nullptr);
425+
426+
/// Bit values for the non-simple status flags
427+
static constexpr uint8_t status_holder_constructed = 1;
428+
static constexpr uint8_t status_instance_registered = 2;
419429
};
420430

421431
static_assert(std::is_standard_layout<instance>::value, "Internal error: `pybind11::detail::instance` is not standard layout!");

include/pybind11/pybind11.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1215,7 +1215,10 @@ class class_ : public detail::generic_type {
12151215
/// `.owned`, a new holder will be constructed to manage the value pointer.
12161216
static void init_instance(detail::instance *inst, const void *holder_ptr) {
12171217
auto v_h = inst->get_value_and_holder(detail::get_type_info(typeid(type)));
1218-
register_instance(inst, v_h.value_ptr(), v_h.type);
1218+
if (!v_h.instance_registered()) {
1219+
register_instance(inst, v_h.value_ptr(), v_h.type);
1220+
v_h.set_instance_registered();
1221+
}
12191222
init_holder(inst, v_h, (const holder_type *) holder_ptr, v_h.value_ptr<type>());
12201223
}
12211224

0 commit comments

Comments
 (0)