Skip to content

Commit c6e71cf

Browse files
committed
Merge branch 'master' into smart_holder
2 parents b9c17cf + e250155 commit c6e71cf

File tree

6 files changed

+140
-12
lines changed

6 files changed

+140
-12
lines changed

include/pybind11/detail/class.h

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -189,12 +189,10 @@ extern "C" inline PyObject *pybind11_meta_call(PyObject *type, PyObject *args, P
189189
return nullptr;
190190
}
191191

192-
// This must be a pybind11 instance
193-
auto *instance = reinterpret_cast<detail::instance *>(self);
194-
195192
// Ensure that the base __init__ function(s) were called
196-
for (const auto &vh : values_and_holders(instance)) {
197-
if (!vh.holder_constructed()) {
193+
values_and_holders vhs(self);
194+
for (const auto &vh : vhs) {
195+
if (!vh.holder_constructed() && !vhs.is_redundant_value_and_holder(vh)) {
198196
PyErr_Format(PyExc_TypeError,
199197
"%.200s.__init__() must be called when overriding __init__",
200198
get_fully_qualified_tp_name(vh.type->type).c_str());

include/pybind11/detail/type_caster_base.h

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,22 @@ class loader_life_support {
102102
inline std::pair<decltype(internals::registered_types_py)::iterator, bool>
103103
all_type_info_get_cache(PyTypeObject *type);
104104

105+
// Band-aid workaround to fix a subtle but serious bug in a minimalistic fashion. See PR #4762.
106+
inline void all_type_info_add_base_most_derived_first(std::vector<type_info *> &bases,
107+
type_info *addl_base) {
108+
for (auto it = bases.begin(); it != bases.end(); it++) {
109+
type_info *existing_base = *it;
110+
if (PyType_IsSubtype(addl_base->type, existing_base->type) != 0) {
111+
bases.insert(it, addl_base);
112+
return;
113+
}
114+
}
115+
bases.push_back(addl_base);
116+
}
117+
105118
// Populates a just-created cache entry.
106119
PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector<type_info *> &bases) {
120+
assert(bases.empty());
107121
std::vector<PyTypeObject *> check;
108122
for (handle parent : reinterpret_borrow<tuple>(t->tp_bases)) {
109123
check.push_back((PyTypeObject *) parent.ptr());
@@ -136,7 +150,7 @@ PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector<type_
136150
}
137151
}
138152
if (!found) {
139-
bases.push_back(tinfo);
153+
all_type_info_add_base_most_derived_first(bases, tinfo);
140154
}
141155
}
142156
} else if (type->tp_bases) {
@@ -322,18 +336,29 @@ struct values_and_holders {
322336
explicit values_and_holders(instance *inst)
323337
: inst{inst}, tinfo(all_type_info(Py_TYPE(inst))) {}
324338

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+
325346
struct iterator {
326347
private:
327348
instance *inst = nullptr;
328349
const type_vec *types = nullptr;
329350
value_and_holder curr;
330351
friend struct values_and_holders;
331-
iterator(instance *inst, const type_vec *tinfo)
332-
: inst{inst}, types{tinfo},
333-
curr(inst /* instance */,
334-
types->empty() ? nullptr : (*types)[0] /* type info */,
335-
0, /* vpos: (non-simple types only): the first vptr comes first */
336-
0 /* index */) {}
352+
iterator(instance *inst, const type_vec *tinfo) : inst{inst}, types{tinfo} {
353+
if (inst != nullptr) {
354+
assert(!types->empty());
355+
curr = value_and_holder(
356+
inst /* instance */,
357+
(*types)[0] /* type info */,
358+
0, /* vpos: (non-simple types only): the first vptr comes first */
359+
0 /* index */);
360+
}
361+
}
337362
// Past-the-end iterator:
338363
explicit iterator(size_t end) : curr(end) {}
339364

@@ -364,6 +389,16 @@ struct values_and_holders {
364389
}
365390

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

369404
/**

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ set(PYBIND11_TEST_FILES
165165
test_opaque_types
166166
test_operator_overloading
167167
test_pickling
168+
test_python_multiple_inheritance
168169
test_pytypes
169170
test_return_value_policy_override
170171
test_sequences_and_iterators

tests/test_class.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from unittest import mock
2+
13
import pytest
24

35
import env
@@ -203,6 +205,18 @@ def __init__(self):
203205
assert msg(exc_info.value) == expected
204206

205207

208+
@pytest.mark.parametrize(
209+
"mock_return_value", [None, (1, 2, 3), m.Pet("Polly", "parrot"), m.Dog("Molly")]
210+
)
211+
def test_mock_new(mock_return_value):
212+
with mock.patch.object(
213+
m.Pet, "__new__", return_value=mock_return_value
214+
) as mock_new:
215+
obj = m.Pet("Noname", "Nospecies")
216+
assert obj is mock_return_value
217+
mock_new.assert_called_once_with(m.Pet, "Noname", "Nospecies")
218+
219+
206220
def test_automatic_upcasting():
207221
assert type(m.return_class_1()).__name__ == "DerivedClass1"
208222
assert type(m.return_class_2()).__name__ == "DerivedClass2"
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#include "pybind11_tests.h"
2+
3+
namespace test_python_multiple_inheritance {
4+
5+
// Copied from:
6+
// https://github.com/google/clif/blob/5718e4d0807fd3b6a8187dde140069120b81ecef/clif/testing/python_multiple_inheritance.h
7+
8+
struct CppBase {
9+
explicit CppBase(int value) : base_value(value) {}
10+
int get_base_value() const { return base_value; }
11+
void reset_base_value(int new_value) { base_value = new_value; }
12+
13+
private:
14+
int base_value;
15+
};
16+
17+
struct CppDrvd : CppBase {
18+
explicit CppDrvd(int value) : CppBase(value), drvd_value(value * 3) {}
19+
int get_drvd_value() const { return drvd_value; }
20+
void reset_drvd_value(int new_value) { drvd_value = new_value; }
21+
22+
int get_base_value_from_drvd() const { return get_base_value(); }
23+
void reset_base_value_from_drvd(int new_value) { reset_base_value(new_value); }
24+
25+
private:
26+
int drvd_value;
27+
};
28+
29+
} // namespace test_python_multiple_inheritance
30+
31+
TEST_SUBMODULE(python_multiple_inheritance, m) {
32+
using namespace test_python_multiple_inheritance;
33+
34+
py::class_<CppBase>(m, "CppBase")
35+
.def(py::init<int>())
36+
.def("get_base_value", &CppBase::get_base_value)
37+
.def("reset_base_value", &CppBase::reset_base_value);
38+
39+
py::class_<CppDrvd, CppBase>(m, "CppDrvd")
40+
.def(py::init<int>())
41+
.def("get_drvd_value", &CppDrvd::get_drvd_value)
42+
.def("reset_drvd_value", &CppDrvd::reset_drvd_value)
43+
.def("get_base_value_from_drvd", &CppDrvd::get_base_value_from_drvd)
44+
.def("reset_base_value_from_drvd", &CppDrvd::reset_base_value_from_drvd);
45+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Adapted from:
2+
# https://github.com/google/clif/blob/5718e4d0807fd3b6a8187dde140069120b81ecef/clif/testing/python/python_multiple_inheritance_test.py
3+
4+
from pybind11_tests import python_multiple_inheritance as m
5+
6+
7+
class PC(m.CppBase):
8+
pass
9+
10+
11+
class PPCC(PC, m.CppDrvd):
12+
pass
13+
14+
15+
def test_PC():
16+
d = PC(11)
17+
assert d.get_base_value() == 11
18+
d.reset_base_value(13)
19+
assert d.get_base_value() == 13
20+
21+
22+
def test_PPCC():
23+
d = PPCC(11)
24+
assert d.get_drvd_value() == 33
25+
d.reset_drvd_value(55)
26+
assert d.get_drvd_value() == 55
27+
28+
assert d.get_base_value() == 11
29+
assert d.get_base_value_from_drvd() == 11
30+
d.reset_base_value(20)
31+
assert d.get_base_value() == 20
32+
assert d.get_base_value_from_drvd() == 20
33+
d.reset_base_value_from_drvd(30)
34+
assert d.get_base_value() == 30
35+
assert d.get_base_value_from_drvd() == 30

0 commit comments

Comments
 (0)