Skip to content

Commit 28bbda9

Browse files
authored
[skip ci] test: reject shared_ptr casts for custom holders (#6069)
Lock down the current behavior discussed in issue #6064: py::cast() from std::shared_ptr<T> remains rejected for custom holder bindings that are not std::shared_ptr or py::smart_holder. This extracts the incompatible-holder coverage from PR #6068 and adds a test shaped like issue #6064, while PR #6065 and PR #6066 explore alternative support paths.
1 parent 3160c82 commit 28bbda9

2 files changed

Lines changed: 59 additions & 0 deletions

File tree

tests/test_smart_ptr.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,24 @@ class const_only_shared_ptr {
8383
// for demonstration purpose only, this imitates smart pointer with a const-only pointer
8484
};
8585

86+
template <typename T>
87+
class shared_ptr_as_custom_holder {
88+
std::shared_ptr<T> ptr_;
89+
90+
public:
91+
using element_type = T;
92+
93+
shared_ptr_as_custom_holder() = default;
94+
95+
explicit shared_ptr_as_custom_holder(T *) {
96+
throw std::runtime_error("invalid shared_ptr_as_custom_holder constructor call");
97+
}
98+
99+
explicit shared_ptr_as_custom_holder(std::shared_ptr<T> ptr) : ptr_(std::move(ptr)) {}
100+
101+
T *get() const { return ptr_.get(); }
102+
};
103+
86104
// Custom object with builtin reference counting (see 'object.h' for the implementation)
87105
class MyObject1 : public Object {
88106
public:
@@ -239,6 +257,17 @@ struct SharedFromThisRef {
239257
std::shared_ptr<B> shared = std::make_shared<B>();
240258
};
241259

260+
class Issue6064UnsafePath {
261+
public:
262+
static std::shared_ptr<Issue6064UnsafePath> create() {
263+
return {new Issue6064UnsafePath(), [](Issue6064UnsafePath *ptr) { delete ptr; }};
264+
}
265+
266+
private:
267+
Issue6064UnsafePath() = default;
268+
~Issue6064UnsafePath() = default;
269+
};
270+
242271
// Issue #865: shared_from_this doesn't work with virtual inheritance
243272
struct SharedFromThisVBase : std::enable_shared_from_this<SharedFromThisVBase> {
244273
SharedFromThisVBase() = default;
@@ -341,6 +370,7 @@ struct holder_helper<ref<T>> {
341370
// Make pybind aware of the ref-counted wrapper type (s):
342371
PYBIND11_DECLARE_HOLDER_TYPE(T, ref<T>, true)
343372
PYBIND11_DECLARE_HOLDER_TYPE(T, const_only_shared_ptr<T>, true)
373+
PYBIND11_DECLARE_HOLDER_TYPE(T, shared_ptr_as_custom_holder<T>)
344374
// The following is not required anymore for std::shared_ptr, but it should compile without error:
345375
PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>)
346376
PYBIND11_DECLARE_HOLDER_TYPE(T, huge_unique_ptr<T>)
@@ -501,6 +531,10 @@ TEST_SUBMODULE(smart_ptr, m) {
501531
.def(py::init([](const std::string &value) { return MyObject6::createObject(value); }))
502532
.def_property_readonly("value", &MyObject6::value);
503533

534+
py::class_<Issue6064UnsafePath, shared_ptr_as_custom_holder<Issue6064UnsafePath>>(
535+
m, "Issue6064UnsafePath");
536+
m.def("get_issue6064_unsafe_path_shared_ptr", []() { return Issue6064UnsafePath::create(); });
537+
504538
// test_shared_ptr_and_references
505539
using A = SharedPtrRef::A;
506540
py::class_<A, std::shared_ptr<A>>(m, "A");
@@ -559,6 +593,7 @@ TEST_SUBMODULE(smart_ptr, m) {
559593
py::class_<C, custom_unique_ptr<C>>(m, "TypeWithMoveOnlyHolder")
560594
.def_static("make", []() { return custom_unique_ptr<C>(new C); })
561595
.def_static("make_as_object", []() { return py::cast(custom_unique_ptr<C>(new C)); });
596+
m.def("get_type_with_move_only_holder_shared_ptr", []() { return std::make_shared<C>(); });
562597

563598
// test_holder_with_addressof_operator
564599
using HolderWithAddressOf = shared_ptr_with_addressof_operator<TypeForHolderWithAddressOf>;

tests/test_smart_ptr.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,3 +368,27 @@ def test_move_only_holder_caster_shared_ptr_with_smart_holder_support_enabled():
368368
def test_const_only_holder():
369369
o = m.MyObject6("my_data")
370370
assert o.value == "my_data"
371+
372+
373+
@pytest.mark.parametrize(
374+
"cast_shared_ptr",
375+
[
376+
pytest.param(
377+
m.get_type_with_move_only_holder_shared_ptr,
378+
id="incompatible-custom-holder",
379+
),
380+
pytest.param(
381+
m.get_issue6064_unsafe_path_shared_ptr,
382+
id="issue6064-unsafe-path",
383+
),
384+
],
385+
)
386+
def test_shared_ptr_cast_for_custom_holder_throws(cast_shared_ptr):
387+
with pytest.raises(
388+
RuntimeError,
389+
match=(
390+
"Unable to convert std::shared_ptr<T> to Python when the bound type does not use"
391+
" std::shared_ptr or py::smart_holder as its holder type"
392+
),
393+
):
394+
cast_shared_ptr()

0 commit comments

Comments
 (0)