From 6b7b3d27e6c0c4438ed2ca4c7a3484d75cd96682 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Tue, 17 Mar 2026 00:49:55 -0400 Subject: [PATCH 1/2] Add tests that cause crash in def_readwrite - Occurs with non-smart-holder property of smart-holder class --- tests/test_class_sh_property.cpp | 31 +++++++++++++++++++++++++++++++ tests/test_class_sh_property.py | 17 +++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/tests/test_class_sh_property.cpp b/tests/test_class_sh_property.cpp index 8863ad7d7b..93981ff8d0 100644 --- a/tests/test_class_sh_property.cpp +++ b/tests/test_class_sh_property.cpp @@ -43,6 +43,23 @@ struct WithConstCharPtrMember { const char *const_char_ptr_member = "ConstChar*"; }; +enum class TinyLevel { + A = 0, + B = 1, +}; + +struct HolderWithEnum { + TinyLevel level = TinyLevel::A; +}; + +struct LegacyThing { + int value = 7; +}; + +struct HolderWithLegacyMember { + LegacyThing legacy; +}; + } // namespace test_class_sh_property TEST_SUBMODULE(class_sh_property, m) { @@ -91,4 +108,18 @@ TEST_SUBMODULE(class_sh_property, m) { py::classh(m, "WithConstCharPtrMember") .def(py::init<>()) .def_readonly("const_char_ptr_member", &WithConstCharPtrMember::const_char_ptr_member); + + py::enum_(m, "TinyLevel").value("A", TinyLevel::A).value("B", TinyLevel::B); + + py::classh(m, "HolderWithEnum") + .def(py::init<>()) + .def_readwrite("level", &HolderWithEnum::level); + + py::class_(m, "LegacyThing") + .def(py::init<>()) + .def_readwrite("value", &LegacyThing::value); + + py::classh(m, "HolderWithLegacyMember") + .def(py::init<>()) + .def_readwrite("legacy", &HolderWithLegacyMember::legacy); } diff --git a/tests/test_class_sh_property.py b/tests/test_class_sh_property.py index 0250a7f78e..2644de0a56 100644 --- a/tests/test_class_sh_property.py +++ b/tests/test_class_sh_property.py @@ -164,3 +164,20 @@ def test_readonly_char6_member(): def test_readonly_const_char_ptr_member(): obj = m.WithConstCharPtrMember() assert obj.const_char_ptr_member == "ConstChar*" + + +def test_enum_member_with_smart_holder_def_readwrite(): + obj = m.HolderWithEnum() + assert obj.level == m.TinyLevel.A + for _ in range(100): + v = obj.level + assert v == m.TinyLevel.A + del v + + +def test_non_smart_holder_member_type_with_smart_holder_owner(): + obj = m.HolderWithLegacyMember() + for _ in range(1000): + v = obj.legacy + assert v.value == 7 + del v From 3e963ce5ff17deff2cef4d5d90b82f73423e9050 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Tue, 17 Mar 2026 00:58:09 -0400 Subject: [PATCH 2/2] Fix crash in def_readwrite for non-smart-holder properties of smart-holder classes --- include/pybind11/pybind11.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 76d998a3ba..4349dffd34 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -2013,10 +2013,16 @@ struct property_cpp_function_sh_raw_ptr_member { // This prevents disowning of the Python object owning the member. template 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); + return tinfo->holder_enum_v == holder_enum_t::smart_holder; + } + template = 0> static cpp_function readonly(PM pm, const handle &hdl) { type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true); - if (tinfo->holder_enum_v == holder_enum_t::smart_holder) { + if (tinfo->holder_enum_v == holder_enum_t::smart_holder + && use_smart_holder_member_aliasing()) { return cpp_function( [pm](handle c_hdl) -> std::shared_ptr::type> { std::shared_ptr c_sp @@ -2033,7 +2039,8 @@ struct property_cpp_function_sh_member_held_by_value { template = 0> static cpp_function read(PM pm, const handle &hdl) { type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true); - if (tinfo->holder_enum_v == holder_enum_t::smart_holder) { + if (tinfo->holder_enum_v == holder_enum_t::smart_holder + && use_smart_holder_member_aliasing()) { return cpp_function( [pm](handle c_hdl) -> std::shared_ptr { std::shared_ptr c_sp