Fix crash in def_readwrite for non-smart-holder properties of smart-holder classes#6003
Fix crash in def_readwrite for non-smart-holder properties of smart-holder classes#6003virtuald wants to merge 2 commits intopybind:masterfrom
Conversation
| template <typename T, typename D> | ||
| struct property_cpp_function_sh_member_held_by_value { | ||
| static bool use_smart_holder_member_aliasing() { | ||
| type_info *tinfo = get_type_info(typeid(D), /*throw_if_missing=*/true); |
There was a problem hiding this comment.
This adds an implicit requirement that you must bind the type of a field before you bind a property that returns something of that type. That has never been required before, and is certainly going to trip people up.
Can we instead modify the shared_ptr to-Python cast path so that it doesn't blindly assume it's casting to a type with a compatible holder? It used to not be able to know better, but now type_info has a holder-type field. And it would save the wrong-holder UB in all cases rather than just this one.
There was a problem hiding this comment.
I'll take a look this weekend.
There was a problem hiding this comment.
Well, if you want stubgen to work, there's already an implicit requirement that you must bind a type before you use it as a parameter or other, so this doesn't seem that different to me.
I don't quite understand how your suggested fix would avoid that problem, you'd still need to call get_type_info(typeid(D)) at bind time (for efficiency) to determine the holder type of the member.
... with that said, clearly I don't understand how this works. If you remove the non-test changes from this PR (I split the tests and the fix into two separate commits for convenience), GPT decided the fix that you suggested looks like this:
diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h
index 1c4973b7..05aef0e9 100644
--- a/include/pybind11/cast.h
+++ b/include/pybind11/cast.h
@@ -1021,7 +1021,19 @@ public:
return smart_holder_type_caster_support::smart_holder_from_shared_ptr(
src, policy, parent, srcs.result);
}
- return type_caster_base<type>::cast_holder(srcs, &src);
+
+ auto *tinfo = srcs.result.tinfo;
+ if (tinfo != nullptr && tinfo->holder_enum_v == holder_enum_t::std_shared_ptr) {
+ return type_caster_base<type>::cast_holder(srcs, &src);
+ }
+
+ if (parent) {
+ return type_caster_base<type>::cast(
+ srcs, return_value_policy::reference_internal, parent);
+ }
+
+ throw cast_error("Unable to convert std::shared_ptr<T> to Python when the bound type "
+ "does not use std::shared_ptr or py::smart_holder as its holder type");
}
// This function will succeed even if the `responsible_parent` does not own theand the tests pass, but it doesn't look right to me for some reason.
There was a problem hiding this comment.
I added #6008 with that fix to see if tests passed or not.
- Occurs with non-smart-holder property of smart-holder class
Description
Ran into an issue where my extension was failing with pybind11 calling free on an enum (and on some platforms this ends up being a double-free?). Asked AI to dig into it and it got it wrong several times, but it finally did hit on a really subtle bug (described below).
Honestly, the fix and tests are probably slop -- I'm not sure why it decided to run the test in a subprocess, but the fix looks right and the tests pass. Open to suggestions.I fixed the slop, looks better now.
gpt-5.3-high's analysis of the bug
This comes from a subtle interaction of generated bindings + smart_holder:
target types it eventually treats holder memory as if it were unique_ptr.
So pybind is not “randomly” deleting Level; it’s getting put into an invalid ownership state via holder-type mismatch.
Summarizing code locations with excerpts
Absolutely — here are the exact places.
1) def_readwrite routes through property_cpp_function<type, D>::read
2) For smart-holder classes with by-value members, read returns std::shared_ptr
That is exactly the std::shared_ptr(c_sp, &(c_sp.get()->*pm)) aliasing getter.
3) This specialization is selected for by-value non-pointer/non-smart-pointer members
Suggested changelog entry: