Skip to content

Commit cf5958d

Browse files
committed
Streamline implementation and avoid unsafe reinterpret_cast<instance *>() introduced with PR #2152
The `reinterpret_cast<instance *>(self)` is unsafe if `__new__` is mocked, which was actually found in the wild: the mock returned `None` for `self`. This was inconsequential because `inst` is currently cast straight back to `PyObject *` to compute `all_type_info()`, which is empty if `self` is not a pybind11 `instance`, and then `inst` is never dereferenced. However, the unsafe detour through `instance *` is easily avoided and the updated implementation is less prone to accidents while debugging or refactoring.
1 parent d708836 commit cf5958d

File tree

2 files changed

+19
-17
lines changed

2 files changed

+19
-17
lines changed

include/pybind11/detail/class.h

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -180,17 +180,6 @@ extern "C" inline PyObject *pybind11_meta_getattro(PyObject *obj, PyObject *name
180180
return PyType_Type.tp_getattro(obj, name);
181181
}
182182

183-
// Band-aid workaround to fix a subtle but serious bug in a minimalistic fashion. See PR #4762.
184-
inline bool is_redundant_value_and_holder(const std::vector<type_info *> &bases,
185-
std::size_t ix_base) {
186-
for (std::size_t i = 0; i < ix_base; i++) {
187-
if (PyType_IsSubtype(bases[i]->type, bases[ix_base]->type) != 0) {
188-
return true;
189-
}
190-
}
191-
return false;
192-
}
193-
194183
/// metaclass `__call__` function that is used to create all pybind11 objects.
195184
extern "C" inline PyObject *pybind11_meta_call(PyObject *type, PyObject *args, PyObject *kwargs) {
196185

@@ -201,19 +190,15 @@ extern "C" inline PyObject *pybind11_meta_call(PyObject *type, PyObject *args, P
201190
}
202191

203192
// Ensure that the base __init__ function(s) were called
204-
const auto &bases = all_type_info((PyTypeObject *) type);
205-
values_and_holders vhs(reinterpret_cast<detail::instance *>(self));
206-
assert(bases.size() == vhs.size());
207-
std::size_t ix_base = 0;
193+
values_and_holders vhs(self);
208194
for (const auto &vh : vhs) {
209-
if (!vh.holder_constructed() && !is_redundant_value_and_holder(bases, ix_base)) {
195+
if (!vh.holder_constructed() && !vhs.is_redundant_value_and_holder(vh)) {
210196
PyErr_Format(PyExc_TypeError,
211197
"%.200s.__init__() must be called when overriding __init__",
212198
get_fully_qualified_tp_name(vh.type->type).c_str());
213199
Py_DECREF(self);
214200
return nullptr;
215201
}
216-
ix_base++;
217202
}
218203

219204
return self;

include/pybind11/detail/type_caster_base.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,13 @@ struct values_and_holders {
336336
explicit values_and_holders(instance *inst)
337337
: inst{inst}, tinfo(all_type_info(Py_TYPE(inst))) {}
338338

339+
explicit values_and_holders(PyObject *obj)
340+
: inst{nullptr}, tinfo(all_type_info(Py_TYPE(obj))) {
341+
if (!tinfo.empty()) {
342+
inst = reinterpret_cast<instance *>(obj);
343+
}
344+
}
345+
339346
struct iterator {
340347
private:
341348
instance *inst = nullptr;
@@ -378,6 +385,16 @@ struct values_and_holders {
378385
}
379386

380387
size_t size() { return tinfo.size(); }
388+
389+
// Band-aid workaround to fix a subtle but serious bug in a minimalistic fashion. See PR #4762.
390+
bool is_redundant_value_and_holder(const value_and_holder &vh) {
391+
for (size_t i = 0; i < vh.index; i++) {
392+
if (PyType_IsSubtype(tinfo[i]->type, tinfo[vh.index]->type) != 0) {
393+
return true;
394+
}
395+
}
396+
return false;
397+
}
381398
};
382399

383400
/**

0 commit comments

Comments
 (0)