Skip to content

Commit 99cf27a

Browse files
authored
[smart_holder] Keep parent alive when returning raw pointers (#4609)
* Avoid dangling pointers. * Add test for const ptr * Fix test failures. * Fix ClangTidy * fix emplace_back
1 parent b37a1cd commit 99cf27a

File tree

3 files changed

+66
-1
lines changed

3 files changed

+66
-1
lines changed

include/pybind11/detail/smart_holder_type_casters.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,11 @@ struct smart_holder_type_caster : smart_holder_type_caster_load<T>,
715715

716716
static handle cast(T *src, return_value_policy policy, handle parent) {
717717
if (policy == return_value_policy::_clif_automatic) {
718-
policy = return_value_policy::reference;
718+
if (parent) {
719+
policy = return_value_policy::reference_internal;
720+
} else {
721+
policy = return_value_policy::reference;
722+
}
719723
}
720724
return cast(const_cast<T const *>(src), policy, parent); // Mutbl2Const
721725
}

tests/test_return_value_policy_override.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,37 @@
22

33
#include "pybind11_tests.h"
44

5+
#include <vector>
6+
57
namespace test_return_value_policy_override {
68

79
struct some_type {};
810

11+
struct data_field {
12+
int value = -99;
13+
14+
explicit data_field(int v) : value(v) {}
15+
};
16+
17+
struct data_fields_holder {
18+
std::vector<data_field> vec;
19+
20+
explicit data_fields_holder(std::size_t vec_size) {
21+
for (std::size_t i = 0; i < vec_size; i++) {
22+
vec.emplace_back(13 + static_cast<int>(i) * 11);
23+
}
24+
}
25+
26+
data_field *vec_at(std::size_t index) {
27+
if (index >= vec.size()) {
28+
return nullptr;
29+
}
30+
return &vec[index];
31+
}
32+
33+
const data_field *vec_at_const_ptr(std::size_t index) { return vec_at(index); }
34+
};
35+
936
// cp = copyable, mv = movable, 1 = yes, 0 = no
1037
struct type_cp1_mv1 {
1138
std::string mtxt;
@@ -156,6 +183,8 @@ std::unique_ptr<type_cp0_mv0> return_unique_pointer_nocopy_nomove() {
156183

157184
} // namespace test_return_value_policy_override
158185

186+
using test_return_value_policy_override::data_field;
187+
using test_return_value_policy_override::data_fields_holder;
159188
using test_return_value_policy_override::some_type;
160189
using test_return_value_policy_override::type_cp0_mv0;
161190
using test_return_value_policy_override::type_cp0_mv1;
@@ -205,6 +234,8 @@ struct type_caster<some_type> : type_caster_base<some_type> {
205234
} // namespace detail
206235
} // namespace pybind11
207236

237+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(data_field)
238+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(data_fields_holder)
208239
PYBIND11_SMART_HOLDER_TYPE_CASTERS(type_cp1_mv1)
209240
PYBIND11_SMART_HOLDER_TYPE_CASTERS(type_cp0_mv1)
210241
PYBIND11_SMART_HOLDER_TYPE_CASTERS(type_cp1_mv0)
@@ -239,6 +270,21 @@ TEST_SUBMODULE(return_value_policy_override, m) {
239270
},
240271
py::return_value_policy::_clif_automatic);
241272

273+
py::classh<data_field>(m, "data_field").def_readwrite("value", &data_field::value);
274+
py::classh<data_fields_holder>(m, "data_fields_holder")
275+
.def(py::init<std::size_t>())
276+
.def("vec_at",
277+
[](const py::object &self_py, std::size_t index) {
278+
auto *self = py::cast<data_fields_holder *>(self_py);
279+
return py::cast(
280+
self->vec_at(index), py::return_value_policy::_clif_automatic, self_py);
281+
})
282+
.def("vec_at_const_ptr", [](const py::object &self_py, std::size_t index) {
283+
auto *self = py::cast<data_fields_holder *>(self_py);
284+
return py::cast(
285+
self->vec_at_const_ptr(index), py::return_value_policy::_clif_automatic, self_py);
286+
});
287+
242288
py::classh<type_cp1_mv1>(m, "type_cp1_mv1")
243289
.def(py::init<std::string>())
244290
.def_readonly("mtxt", &type_cp1_mv1::mtxt);

tests/test_return_value_policy_override.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ def test_return_pointer():
1717
assert m.return_pointer_with_policy_clif_automatic() == "_clif_automatic"
1818

1919

20+
def test_persistent_holder():
21+
h = m.data_fields_holder(2)
22+
assert h.vec_at(0).value == 13
23+
assert h.vec_at(1).value == 24
24+
assert h.vec_at_const_ptr(0).value == 13
25+
assert h.vec_at_const_ptr(1).value == 24
26+
27+
28+
def test_temporary_holder():
29+
data_field = m.data_fields_holder(2).vec_at(1)
30+
assert data_field.value == 24
31+
data_field = m.data_fields_holder(2).vec_at_const_ptr(1)
32+
assert data_field.value == 24
33+
34+
2035
@pytest.mark.parametrize(
2136
("func", "expected"),
2237
[

0 commit comments

Comments
 (0)