-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Detect and fail if using mismatched holders #2644
base: master
Are you sure you want to change the base?
Changes from all commits
b498e52
44f23a2
0fe5697
6360006
e2aa4ec
502bf24
7852e7d
cde5c11
d5c4386
e57a3db
8115384
02d0b53
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1521,10 +1521,7 @@ struct copyable_holder_caster : public type_caster_base<type> { | |
|
||
protected: | ||
friend class type_caster_generic; | ||
void check_holder_compat() { | ||
if (typeinfo->default_holder) | ||
throw cast_error("Unable to load a custom holder type from a default-holder instance"); | ||
} | ||
void check_holder_compat() {} | ||
|
||
bool load_value(value_and_holder &&v_h) { | ||
if (v_h.holder_constructed()) { | ||
|
@@ -1607,6 +1604,39 @@ template <typename base, typename holder> struct is_holder_type : | |
template <typename base, typename deleter> struct is_holder_type<base, std::unique_ptr<base, deleter>> : | ||
std::true_type {}; | ||
|
||
template <typename holder> using is_holder = any_of< | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This already exists in
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I missed that. The existing definition should serve my purpose. |
||
is_template_base_of<move_only_holder_caster, make_caster<holder>>, | ||
is_template_base_of<copyable_holder_caster, make_caster<holder>>>; | ||
|
||
template <typename holder> | ||
void check_for_holder_mismatch(const char*, enable_if_t<!is_holder<holder>::value, int> = 0) {} | ||
template <typename holder> | ||
void check_for_holder_mismatch(const char* func_name, enable_if_t<is_holder<holder>::value, int> = 0) { | ||
using iholder = intrinsic_t<holder>; | ||
using base_type = decltype(*holder_helper<iholder>::get(std::declval<iholder>())); | ||
auto &holder_typeinfo = typeid(iholder); | ||
auto base_info = detail::get_type_info(typeid(base_type), false); | ||
if (!base_info) { | ||
#ifdef NDEBUG | ||
pybind11_fail("Cannot register function using not yet registered type"); | ||
#else | ||
pybind11_fail("Cannot register function using not yet registered type '" + type_id<base_type>() + "'"); | ||
#endif | ||
} | ||
|
||
if (!same_type(*base_info->holder_type, holder_typeinfo)) { | ||
#ifdef NDEBUG | ||
pybind11_fail("Detected mismatching holder types when declaring function '" + std::string(func_name) + "' (compile in debug mode for details)"); | ||
#else | ||
std::string holder_name(base_info->holder_type->name()); | ||
detail::clean_type_id(holder_name); | ||
pybind11_fail("Detected mismatching holder types when declaring function '" + std::string(func_name) + "':" | ||
" attempting to use holder type " + type_id<iholder>() + ", but " + type_id<base_type>() + | ||
" was declared using holder type " + holder_name); | ||
#endif | ||
} | ||
} | ||
|
||
template <typename T> struct handle_type_name { static constexpr auto name = _<T>(); }; | ||
template <> struct handle_type_name<bytes> { static constexpr auto name = _(PYBIND11_BYTES_NAME); }; | ||
template <> struct handle_type_name<int_> { static constexpr auto name = _("int"); }; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -128,6 +128,7 @@ struct internals { | |
struct type_info { | ||
PyTypeObject *type; | ||
const std::type_info *cpptype; | ||
const std::type_info *holder_type = nullptr; | ||
size_t type_size, type_align, holder_size_in_ptrs; | ||
void *(*operator_new)(size_t); | ||
void (*init_instance)(instance *, const void *); | ||
|
@@ -143,14 +144,12 @@ struct type_info { | |
bool simple_type : 1; | ||
/* True if there is no multiple inheritance in this type's inheritance tree */ | ||
bool simple_ancestors : 1; | ||
/* for base vs derived holder_type checks */ | ||
bool default_holder : 1; | ||
/* true if this is a type registered with py::module_local */ | ||
bool module_local : 1; | ||
}; | ||
|
||
/// Tracks the `internals` and `type_info` ABI version independent of the main library version | ||
#define PYBIND11_INTERNALS_VERSION 4 | ||
#define PYBIND11_INTERNALS_VERSION 5 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This breaks ABI, so should ideally be part of a batch of ABI-breaking PRs. |
||
|
||
/// On MSVC, debug and release builds are not ABI-compatible! | ||
#if defined(_MSC_VER) && defined(_DEBUG) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,11 +37,11 @@ PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>); | |
// holder size to trigger the non-simple-layout internal instance layout for single inheritance with | ||
// large holder type: | ||
template <typename T> class huge_unique_ptr { | ||
std::unique_ptr<T> ptr; | ||
uint64_t padding[10]; | ||
std::unique_ptr<T> ptr; | ||
public: | ||
huge_unique_ptr(T *p) : ptr(p) {}; | ||
T *get() { return ptr.get(); } | ||
T *get() const { return ptr.get(); } | ||
}; | ||
PYBIND11_DECLARE_HOLDER_TYPE(T, huge_unique_ptr<T>); | ||
|
||
|
@@ -330,12 +330,6 @@ TEST_SUBMODULE(smart_ptr, m) { | |
.def_readwrite("value", &TypeForMoveOnlyHolderWithAddressOf::value) | ||
.def("print_object", [](const TypeForMoveOnlyHolderWithAddressOf *obj) { py::print(obj->toString()); }); | ||
|
||
// test_smart_ptr_from_default | ||
struct HeldByDefaultHolder { }; | ||
py::class_<HeldByDefaultHolder>(m, "HeldByDefaultHolder") | ||
.def(py::init<>()) | ||
.def_static("load_shared_ptr", [](std::shared_ptr<HeldByDefaultHolder>) {}); | ||
|
||
// test_shared_ptr_gc | ||
// #187: issue involving std::shared_ptr<> return value policy & garbage collection | ||
struct ElementBase { | ||
|
@@ -367,4 +361,32 @@ TEST_SUBMODULE(smart_ptr, m) { | |
list.append(py::cast(e)); | ||
return list; | ||
}); | ||
|
||
// test_holder_mismatch | ||
// Tests the detection of trying to use mismatched holder types around the same instance type | ||
struct HeldByShared {}; | ||
struct HeldByUnique {}; | ||
// HeldByShared declared with shared_ptr holder, but used with unique_ptr later | ||
py::class_<HeldByShared, std::shared_ptr<HeldByShared>>(m, "HeldByShared"); | ||
m.def("register_mismatch_return", [](py::module m) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My brief reading of this PR leads me to believe that there's a sharp edge: a user could still hit a segfault if they call Possible resolutions:
@YannickJadoul or @rwgk Any chance y'all have a good (and mebbe easy?) timing performance benchmark for this, to see what the risk is in these terms? EDIT: Hm... for docs/benchmark.py, it's only for compilation time and size, and doesn't really dip into the more nuanced things (e.g. inheritance, custom type casters). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @EricCousineau-TRI wrote:
The TensorFlow core team has very sophisticated pybind11 benchmarks, but it's currently Google-internal only. The author already gave me permission to extract most of it for external view, including sources, but it may take me a few days (I want to show what I extract to the author for approval). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sweet!!! That'd be awesome! I'll see if I can gather some common "complex" patterns in Drake to see if I can get some patterns we have (but maintain feature-parity with upstream). Have you thought any about where your benchmarks might live? I think the ones I'd generate would be simple enough (i.e. just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I explicitly converted my previous code doing compatibility checks at cast time to this code, doing these tests at definition time only, because of efficiency concerns raised elsewhere. |
||
// Fails: the class was already registered with a shared_ptr holder | ||
m.def("bad1", []() { return std::unique_ptr<HeldByShared>(new HeldByShared()); }); | ||
}); | ||
m.def("register_mismatch_class", [](py::module m) { | ||
// Fails: the class was already registered with a shared_ptr holder | ||
py::class_<HeldByShared, std::unique_ptr<HeldByShared>>(m, "bad"); | ||
}); | ||
|
||
// HeldByUnique declared with unique_ptr holder, but used with shared_ptr before / later | ||
m.def("register_return_shared", [](py::module m) { | ||
// Fails if HeldByUnique is not yet registered or, if registered, due to mismatching holder | ||
m.def("bad2", []() { return std::make_shared<HeldByUnique>(); }); | ||
}); | ||
m.def("register_consume_shared", [](py::module m) { | ||
// Fails if HeldByUnique is not yet registered or, if registered, due to mismatching holder | ||
m.def("bad3", [](std::shared_ptr<HeldByUnique>) {}); | ||
}); | ||
m.def("register_HeldByUnique", [](py::module m) { | ||
py::class_<HeldByUnique>(m, "HeldByUnique"); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit To me, it looks like
holder
is reallyholder_caster
. Should this beis_holder_caster
?