Skip to content

Commit ce9f83c

Browse files
virtualdpre-commit-ci[bot]rwgk
authored
Fix crash in def_readwrite for non-smart-holder properties of smart-holder classes (v2) (#6008)
* Add tests that cause crash in def_readwrite - Occurs with non-smart-holder property of smart-holder class * Fix crash in def_readwrite for non-smart-holder properties of smart-holder classes * Use default policy * Address PR comments * Add test for cast error path * style: pre-commit fixes * Revert "Use default policy" This reverts commit b299f32. * Disable test_shared_ptr_return_for_unique_ptr_holder when PYBIND11_TEST_SMART_HOLDER=ON * Add counterexample --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Ralf W. Grosse-Kunstleve <rgrossekunst@nvidia.com>
1 parent 2d1723c commit ce9f83c

4 files changed

Lines changed: 94 additions & 1 deletion

File tree

include/pybind11/cast.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1020,7 +1020,19 @@ struct copyable_holder_caster<
10201020
return smart_holder_type_caster_support::smart_holder_from_shared_ptr(
10211021
src, policy, parent, srcs.result);
10221022
}
1023-
return type_caster_base<type>::cast_holder(srcs, &src);
1023+
1024+
auto *tinfo = srcs.result.tinfo;
1025+
if (tinfo != nullptr && tinfo->holder_enum_v == holder_enum_t::std_shared_ptr) {
1026+
return type_caster_base<type>::cast_holder(srcs, &src);
1027+
}
1028+
1029+
if (parent) {
1030+
return type_caster_base<type>::cast(
1031+
srcs, return_value_policy::reference_internal, parent);
1032+
}
1033+
1034+
throw cast_error("Unable to convert std::shared_ptr<T> to Python when the bound type "
1035+
"does not use std::shared_ptr or py::smart_holder as its holder type");
10241036
}
10251037

10261038
// This function will succeed even if the `responsible_parent` does not own the

tests/pybind11_tests.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ PYBIND11_MODULE(pybind11_tests, m, py::mod_gil_not_used()) {
9696
#else
9797
false;
9898
#endif
99+
m.attr("PYBIND11_TEST_SMART_HOLDER") =
100+
#if defined(PYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE)
101+
true;
102+
#else
103+
false;
104+
#endif
99105

100106
bind_ConstructorStats(m);
101107

tests/test_class_sh_property.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,24 @@ struct WithConstCharPtrMember {
4343
const char *const_char_ptr_member = "ConstChar*";
4444
};
4545

46+
// See PR #6008
47+
enum class EnumAB {
48+
A = 0,
49+
B = 1,
50+
};
51+
52+
struct ShWithEnumABMember {
53+
EnumAB level = EnumAB::A;
54+
};
55+
56+
struct SimpleStruct {
57+
int value = 7;
58+
};
59+
60+
struct ShWithSimpleStructMember {
61+
SimpleStruct legacy;
62+
};
63+
4664
} // namespace test_class_sh_property
4765

4866
TEST_SUBMODULE(class_sh_property, m) {
@@ -91,4 +109,21 @@ TEST_SUBMODULE(class_sh_property, m) {
91109
py::classh<WithConstCharPtrMember>(m, "WithConstCharPtrMember")
92110
.def(py::init<>())
93111
.def_readonly("const_char_ptr_member", &WithConstCharPtrMember::const_char_ptr_member);
112+
113+
// See PR #6008
114+
py::enum_<EnumAB>(m, "EnumAB").value("A", EnumAB::A).value("B", EnumAB::B);
115+
116+
py::classh<ShWithEnumABMember>(m, "ShWithEnumABMember")
117+
.def(py::init<>())
118+
.def_readwrite("level", &ShWithEnumABMember::level);
119+
120+
py::class_<SimpleStruct>(m, "SimpleStruct")
121+
.def(py::init<>())
122+
.def_readwrite("value", &SimpleStruct::value);
123+
124+
py::classh<ShWithSimpleStructMember>(m, "ShWithSimpleStructMember")
125+
.def(py::init<>())
126+
.def_readwrite("legacy", &ShWithSimpleStructMember::legacy);
127+
128+
m.def("getSimpleStructAsShared", []() { return std::make_shared<SimpleStruct>(); });
94129
}

tests/test_class_sh_property.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import pytest
66

77
import env # noqa: F401
8+
import pybind11_tests
89
from pybind11_tests import class_sh_property as m
910

1011

@@ -164,3 +165,42 @@ def test_readonly_char6_member():
164165
def test_readonly_const_char_ptr_member():
165166
obj = m.WithConstCharPtrMember()
166167
assert obj.const_char_ptr_member == "ConstChar*"
168+
169+
170+
# See PR #6008
171+
def test_enum_member_with_smart_holder_def_readwrite():
172+
obj = m.ShWithEnumABMember()
173+
assert obj.level == m.EnumAB.A
174+
for _ in range(100):
175+
v = obj.level
176+
assert v == m.EnumAB.A
177+
del v
178+
179+
180+
# See PR #6008
181+
def test_non_smart_holder_member_type_with_smart_holder_owner():
182+
obj = m.ShWithSimpleStructMember()
183+
for _ in range(1000):
184+
v = obj.legacy
185+
assert v.value == 7
186+
del v
187+
188+
189+
# See PR #6008, previously this was UB
190+
@pytest.mark.skipif(
191+
pybind11_tests.PYBIND11_TEST_SMART_HOLDER,
192+
reason="PYBIND11_TEST_SMART_HOLDER changes the default holder",
193+
)
194+
def test_shared_ptr_return_for_unique_ptr_holder():
195+
with pytest.raises(
196+
RuntimeError,
197+
match="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",
198+
):
199+
m.getSimpleStructAsShared()
200+
201+
202+
def test_non_smart_holder_member_type_with_smart_holder_owner_aliases_member():
203+
obj = m.ShWithSimpleStructMember()
204+
legacy = obj.legacy
205+
legacy.value = 13
206+
assert obj.legacy.value == 13

0 commit comments

Comments
 (0)