Skip to content

Commit 8052783

Browse files
committed
Detect and fail if using mismatched holders
This adds a check when registering a class or a function with a holder return that the same wrapped type hasn't been previously seen using a different holder type. This fixes pybind#1138 by detecting the failure; currently attempting to use two different holder types (e.g. a unique_ptr<T> and shared_ptr<T>) in difference places can segfault because we don't have any type safety on the holder instances.
1 parent 5c7a290 commit 8052783

File tree

5 files changed

+62
-2
lines changed

5 files changed

+62
-2
lines changed

include/pybind11/cast.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1493,6 +1493,33 @@ template <typename base, typename holder> struct is_holder_type :
14931493
template <typename base, typename deleter> struct is_holder_type<base, std::unique_ptr<base, deleter>> :
14941494
std::true_type {};
14951495

1496+
template <typename holder> using is_holder = any_of<
1497+
is_template_base_of<move_only_holder_caster, make_caster<holder>>,
1498+
is_template_base_of<copyable_holder_caster, make_caster<holder>>>;
1499+
1500+
template <typename holder>
1501+
void check_for_holder_mismatch(enable_if_t<!is_holder<holder>::value, int> = 0) {}
1502+
template <typename holder>
1503+
void check_for_holder_mismatch(enable_if_t<is_holder<holder>::value, int> = 0) {
1504+
using iholder = intrinsic_t<holder>;
1505+
using base_type = decltype(*holder_helper<iholder>::get(std::declval<iholder>()));
1506+
auto &holder_typeinfo = typeid(iholder);
1507+
auto ins = get_internals().holders_seen.emplace(typeid(base_type), &holder_typeinfo);
1508+
1509+
auto debug = type_id<base_type>();
1510+
if (!ins.second && !same_type(*ins.first->second, holder_typeinfo)) {
1511+
#ifdef NDEBUG
1512+
pybind11_fail("Mismatched holders detected (compile in debug mode for details)");
1513+
#else
1514+
std::string seen_holder_name(ins.first->second->name());
1515+
detail::clean_type_id(seen_holder_name);
1516+
pybind11_fail("Mismatched holders detected: "
1517+
" attempting to use holder type " + type_id<iholder>() + ", but " + type_id<base_type>() +
1518+
" was already seen using holder type " + seen_holder_name);
1519+
#endif
1520+
}
1521+
}
1522+
14961523
template <typename T> struct handle_type_name { static constexpr auto name = _<T>(); };
14971524
template <> struct handle_type_name<bytes> { static constexpr auto name = _(PYBIND11_BYTES_NAME); };
14981525
template <> struct handle_type_name<args> { static constexpr auto name = _("*args"); };

include/pybind11/detail/internals.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ struct internals {
6868
type_map<type_info *> registered_types_cpp; // std::type_index -> pybind11's type information
6969
std::unordered_map<PyTypeObject *, std::vector<type_info *>> registered_types_py; // PyTypeObject* -> base type_info(s)
7070
std::unordered_multimap<const void *, instance*> registered_instances; // void * -> instance*
71+
type_map<const std::type_info *> holders_seen; // type -> seen holder type (to detect holder conflicts)
7172
std::unordered_set<std::pair<const PyObject *, const char *>, overload_hash> inactive_overload_cache;
7273
type_map<std::vector<bool (*)(PyObject *, void *&)>> direct_conversions;
7374
std::unordered_map<const PyObject *, std::vector<PyObject *>> patients;
@@ -111,7 +112,7 @@ struct type_info {
111112
};
112113

113114
/// Tracks the `internals` and `type_info` ABI version independent of the main library version
114-
#define PYBIND11_INTERNALS_VERSION 1
115+
#define PYBIND11_INTERNALS_VERSION 2
115116

116117
#if defined(WITH_THREAD)
117118
# define PYBIND11_INTERNALS_KIND ""

include/pybind11/pybind11.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ class cpp_function : public function {
127127
static_assert(detail::expected_num_args<Extra...>(sizeof...(Args), cast_in::has_args, cast_in::has_kwargs),
128128
"The number of argument annotations does not match the number of function arguments");
129129

130+
// Fail if we've previously seen a different holder around the held type
131+
detail::check_for_holder_mismatch<Return>();
132+
130133
/* Dispatch code which converts function arguments and performs the actual function call */
131134
rec->impl = [](detail::function_call &call) -> handle {
132135
cast_in args_converter;
@@ -1045,6 +1048,9 @@ class class_ : public detail::generic_type {
10451048
none_of<std::is_same<multiple_inheritance, Extra>...>::value), // no multiple_inheritance attr
10461049
"Error: multiple inheritance bases must be specified via class_ template options");
10471050

1051+
// Fail if we've previously seen a different holder around the type
1052+
detail::check_for_holder_mismatch<holder_type>();
1053+
10481054
type_record record;
10491055
record.scope = scope;
10501056
record.name = name;

tests/test_smart_ptr.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ template <typename T> class huge_unique_ptr {
4040
uint64_t padding[10];
4141
public:
4242
huge_unique_ptr(T *p) : ptr(p) {};
43-
T *get() { return ptr.get(); }
43+
T *get() const { return ptr.get(); }
4444
};
4545
PYBIND11_DECLARE_HOLDER_TYPE(T, huge_unique_ptr<T>);
4646

@@ -267,4 +267,19 @@ TEST_SUBMODULE(smart_ptr, m) {
267267
list.append(py::cast(e));
268268
return list;
269269
});
270+
271+
// test_holder_mismatch
272+
// Tests the detection of trying to use mismatched holder types around the same instance type
273+
struct HeldByShared {};
274+
struct HeldByUnique {};
275+
py::class_<HeldByShared, std::shared_ptr<HeldByShared>>(m, "HeldByShared");
276+
m.def("register_mismatch_return", [](py::module m) {
277+
// Fails: the class was already registered with a shared_ptr holder
278+
m.def("bad1", []() { return std::unique_ptr<HeldByShared>(new HeldByShared()); });
279+
});
280+
m.def("return_shared", []() { return std::make_shared<HeldByUnique>(); });
281+
m.def("register_mismatch_class", [](py::module m) {
282+
// Fails: `return_shared2' already returned this via shared_ptr holder
283+
py::class_<HeldByUnique>(m, "HeldByUnique");
284+
});
270285
}

tests/test_smart_ptr.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,3 +218,14 @@ def test_shared_ptr_gc():
218218
pytest.gc_collect()
219219
for i, v in enumerate(el.get()):
220220
assert i == v.value()
221+
222+
223+
def test_holder_mismatch():
224+
"""#1138: segfault if mixing holder types"""
225+
with pytest.raises(RuntimeError) as excinfo:
226+
m.register_mismatch_return(m)
227+
assert "Mismatched holders detected" in str(excinfo)
228+
229+
with pytest.raises(RuntimeError) as excinfo:
230+
m.register_mismatch_class(m)
231+
assert "Mismatched holders detected" in str(excinfo)

0 commit comments

Comments
 (0)