Skip to content

Commit 44f23a2

Browse files
jagermanrhaschke
authored andcommitted
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 b498e52 commit 44f23a2

File tree

4 files changed

+36
-2
lines changed

4 files changed

+36
-2
lines changed

include/pybind11/cast.h

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

1610+
template <typename holder> using is_holder = any_of<
1611+
is_template_base_of<move_only_holder_caster, make_caster<holder>>,
1612+
is_template_base_of<copyable_holder_caster, make_caster<holder>>>;
1613+
1614+
template <typename holder>
1615+
void check_for_holder_mismatch(enable_if_t<!is_holder<holder>::value, int> = 0) {}
1616+
template <typename holder>
1617+
void check_for_holder_mismatch(enable_if_t<is_holder<holder>::value, int> = 0) {
1618+
using iholder = intrinsic_t<holder>;
1619+
using base_type = decltype(*holder_helper<iholder>::get(std::declval<iholder>()));
1620+
auto &holder_typeinfo = typeid(iholder);
1621+
auto ins = get_internals().holders_seen.emplace(typeid(base_type), &holder_typeinfo);
1622+
1623+
auto debug = type_id<base_type>();
1624+
if (!ins.second && !same_type(*ins.first->second, holder_typeinfo)) {
1625+
#ifdef NDEBUG
1626+
pybind11_fail("Mismatched holders detected (compile in debug mode for details)");
1627+
#else
1628+
std::string seen_holder_name(ins.first->second->name());
1629+
detail::clean_type_id(seen_holder_name);
1630+
pybind11_fail("Mismatched holders detected: "
1631+
" attempting to use holder type " + type_id<iholder>() + ", but " + type_id<base_type>() +
1632+
" was already seen using holder type " + seen_holder_name);
1633+
#endif
1634+
}
1635+
}
1636+
16101637
template <typename T> struct handle_type_name { static constexpr auto name = _<T>(); };
16111638
template <> struct handle_type_name<bytes> { static constexpr auto name = _(PYBIND11_BYTES_NAME); };
16121639
template <> struct handle_type_name<int_> { static constexpr auto name = _("int"); };

include/pybind11/detail/internals.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ struct internals {
9797
type_map<type_info *> registered_types_cpp; // std::type_index -> pybind11's type information
9898
std::unordered_map<PyTypeObject *, std::vector<type_info *>> registered_types_py; // PyTypeObject* -> base type_info(s)
9999
std::unordered_multimap<const void *, instance*> registered_instances; // void * -> instance*
100+
type_map<const std::type_info *> holders_seen; // type -> seen holder type (to detect holder conflicts)
100101
std::unordered_set<std::pair<const PyObject *, const char *>, override_hash> inactive_override_cache;
101102
type_map<std::vector<bool (*)(PyObject *, void *&)>> direct_conversions;
102103
std::unordered_map<const PyObject *, std::vector<PyObject *>> patients;
@@ -150,7 +151,7 @@ struct type_info {
150151
};
151152

152153
/// Tracks the `internals` and `type_info` ABI version independent of the main library version
153-
#define PYBIND11_INTERNALS_VERSION 4
154+
#define PYBIND11_INTERNALS_VERSION 5
154155

155156
/// On MSVC, debug and release builds are not ABI-compatible!
156157
#if defined(_MSC_VER) && defined(_DEBUG)

include/pybind11/pybind11.h

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

160+
// Fail if we've previously seen a different holder around the held type
161+
detail::check_for_holder_mismatch<Return>();
162+
160163
/* Dispatch code which converts function arguments and performs the actual function call */
161164
rec->impl = [](function_call &call) -> handle {
162165
cast_in args_converter;
@@ -1221,6 +1224,9 @@ class class_ : public detail::generic_type {
12211224
none_of<std::is_same<multiple_inheritance, Extra>...>::value), // no multiple_inheritance attr
12221225
"Error: multiple inheritance bases must be specified via class_ template options");
12231226

1227+
// Fail if we've previously seen a different holder around the type
1228+
detail::check_for_holder_mismatch<holder_type>();
1229+
12241230
type_record record;
12251231
record.scope = scope;
12261232
record.name = name;

tests/test_smart_ptr.cpp

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

0 commit comments

Comments
 (0)