Skip to content

Commit 7852e7d

Browse files
committed
Generalize holder compatibility check for derived classes
A derived class needs to use the same holder type as its base class(es). So far, the check was constrained to the default holder vs. custom holder types. Thus, we replace the simple check (based on the default_holder flag) with a more elaborate one, comparing holder type names.
1 parent 502bf24 commit 7852e7d

File tree

6 files changed

+29
-25
lines changed

6 files changed

+29
-25
lines changed

include/pybind11/attr.h

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ struct function_record {
221221
struct type_record {
222222
PYBIND11_NOINLINE type_record()
223223
: multiple_inheritance(false), dynamic_attr(false), buffer_protocol(false),
224-
default_holder(true), module_local(false), is_final(false) { }
224+
module_local(false), is_final(false) { }
225225

226226
/// Handle to the parent scope
227227
handle scope;
@@ -271,9 +271,6 @@ struct type_record {
271271
/// Does the class implement the buffer protocol?
272272
bool buffer_protocol : 1;
273273

274-
/// Is the default (unique_ptr) holder type used?
275-
bool default_holder : 1;
276-
277274
/// Is the class definition local to the module shared object?
278275
bool module_local : 1;
279276

@@ -289,13 +286,29 @@ struct type_record {
289286
"\" referenced unknown base type \"" + tname + "\"");
290287
}
291288

292-
if (default_holder != base_info->default_holder) {
293-
std::string tname(base.name());
294-
detail::clean_type_id(tname);
295-
pybind11_fail("generic_type: type \"" + std::string(name) + "\" " +
296-
(default_holder ? "does not have" : "has") +
297-
" a non-default holder type while its base \"" + tname + "\" " +
298-
(base_info->default_holder ? "does not" : "does"));
289+
// Check for holder compatibility
290+
// We cannot simply check for same_type(*holder_type, *base_info->holder_type)
291+
// as the typeids naturally differ as the base type differs from this type
292+
auto clean_holder_name = [](const std::type_info* holder_type, const std::type_info* base_type) -> std::string {
293+
std::string base_name(base_type->name());
294+
detail::clean_type_id(base_name);
295+
std::string holder_name(holder_type->name());
296+
detail::clean_type_id(holder_name);
297+
// replace all occurences of base_name within holder_name with T
298+
size_t start_pos = 0;
299+
while((start_pos = holder_name.find(base_name, start_pos)) != std::string::npos) {
300+
holder_name.replace(start_pos, base_name.length(), "T");
301+
start_pos += 1;
302+
}
303+
return holder_name;
304+
};
305+
std::string holder_name = clean_holder_name(holder_type, this->type);
306+
std::string base_holder_name = clean_holder_name(base_info->holder_type, base_info->cpptype);
307+
if (holder_name != base_holder_name) {
308+
std::string base_name(base.name());
309+
detail::clean_type_id(base_name);
310+
pybind11_fail("generic_type: type \"" + std::string(name) +
311+
"\" uses different holder than its base \"" + base_name + "\" (" + base_holder_name + " vs " + holder_name + ")");
299312
}
300313

301314
bases.append((PyObject *) base_info->type);

include/pybind11/cast.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1546,8 +1546,6 @@ struct copyable_holder_caster : public type_caster_base<type> {
15461546
protected:
15471547
friend class type_caster_generic;
15481548
void check_holder_compat() {
1549-
if (typeinfo->default_holder)
1550-
throw cast_error("Unable to load a custom holder type from a default-holder instance");
15511549
check_for_holder_mismatch_impl<holder_type>();
15521550
}
15531551

include/pybind11/detail/internals.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,6 @@ struct type_info {
144144
bool simple_type : 1;
145145
/* True if there is no multiple inheritance in this type's inheritance tree */
146146
bool simple_ancestors : 1;
147-
/* for base vs derived holder_type checks */
148-
bool default_holder : 1;
149147
/* true if this is a type registered with py::module_local */
150148
bool module_local : 1;
151149
};

include/pybind11/pybind11.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,7 +1054,6 @@ class generic_type : public object {
10541054
tinfo->dealloc = rec.dealloc;
10551055
tinfo->simple_type = true;
10561056
tinfo->simple_ancestors = true;
1057-
tinfo->default_holder = rec.default_holder;
10581057
tinfo->module_local = rec.module_local;
10591058

10601059
auto &internals = get_internals();
@@ -1235,7 +1234,6 @@ class class_ : public detail::generic_type {
12351234
record.holder_size = sizeof(holder_type);
12361235
record.init_instance = init_instance;
12371236
record.dealloc = dealloc;
1238-
record.default_holder = detail::is_instantiation<std::unique_ptr, holder_type>::value;
12391237

12401238
set_operator_new<type>(&record);
12411239

tests/test_class.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,16 +219,16 @@ def test_mismatched_holder():
219219
with pytest.raises(RuntimeError) as excinfo:
220220
m.mismatched_holder_1()
221221
assert re.match(
222-
'generic_type: type ".*MismatchDerived1" does not have a non-default '
223-
'holder type while its base ".*MismatchBase1" does',
222+
'generic_type: type ".*MismatchDerived1" uses different holder '
223+
'than its base ".*MismatchBase1"',
224224
str(excinfo.value),
225225
)
226226

227227
with pytest.raises(RuntimeError) as excinfo:
228228
m.mismatched_holder_2()
229229
assert re.match(
230-
'generic_type: type ".*MismatchDerived2" has a non-default holder type '
231-
'while its base ".*MismatchBase2" does not',
230+
'generic_type: type ".*MismatchDerived2" uses different holder '
231+
'than its base ".*MismatchBase2"',
232232
str(excinfo.value),
233233
)
234234

tests/test_smart_ptr.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -294,10 +294,7 @@ def test_smart_ptr_from_default():
294294
instance = m.HeldByDefaultHolder()
295295
with pytest.raises(RuntimeError) as excinfo:
296296
m.HeldByDefaultHolder.load_shared_ptr(instance)
297-
assert (
298-
"Unable to load a custom holder type from a "
299-
"default-holder instance" in str(excinfo.value)
300-
)
297+
assert "Mismatched holders detected" in str(excinfo.value)
301298

302299

303300
def test_shared_ptr_gc():

0 commit comments

Comments
 (0)