[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.
diff --git a/tests/test_smart_ptr.cpp b/tests/test_smart_ptr.cpp
index 3617fa3..1409089 100644
--- a/tests/test_smart_ptr.cpp
+++ b/tests/test_smart_ptr.cpp
@@ -83,6 +83,24 @@
// for demonstration purpose only, this imitates smart pointer with a const-only pointer
};
+template <typename T>
+class shared_ptr_as_custom_holder {
+ std::shared_ptr<T> ptr_;
+
+public:
+ using element_type = T;
+
+ shared_ptr_as_custom_holder() = default;
+
+ explicit shared_ptr_as_custom_holder(T *) {
+ throw std::runtime_error("invalid shared_ptr_as_custom_holder constructor call");
+ }
+
+ explicit shared_ptr_as_custom_holder(std::shared_ptr<T> ptr) : ptr_(std::move(ptr)) {}
+
+ T *get() const { return ptr_.get(); }
+};
+
// Custom object with builtin reference counting (see 'object.h' for the implementation)
class MyObject1 : public Object {
public:
@@ -239,6 +257,17 @@
std::shared_ptr<B> shared = std::make_shared<B>();
};
+class Issue6064UnsafePath {
+public:
+ static std::shared_ptr<Issue6064UnsafePath> create() {
+ return {new Issue6064UnsafePath(), [](Issue6064UnsafePath *ptr) { delete ptr; }};
+ }
+
+private:
+ Issue6064UnsafePath() = default;
+ ~Issue6064UnsafePath() = default;
+};
+
// Issue #865: shared_from_this doesn't work with virtual inheritance
struct SharedFromThisVBase : std::enable_shared_from_this<SharedFromThisVBase> {
SharedFromThisVBase() = default;
@@ -341,6 +370,7 @@
// Make pybind aware of the ref-counted wrapper type (s):
PYBIND11_DECLARE_HOLDER_TYPE(T, ref<T>, true)
PYBIND11_DECLARE_HOLDER_TYPE(T, const_only_shared_ptr<T>, true)
+PYBIND11_DECLARE_HOLDER_TYPE(T, shared_ptr_as_custom_holder<T>)
// The following is not required anymore for std::shared_ptr, but it should compile without error:
PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>)
PYBIND11_DECLARE_HOLDER_TYPE(T, huge_unique_ptr<T>)
@@ -501,6 +531,10 @@
.def(py::init([](const std::string &value) { return MyObject6::createObject(value); }))
.def_property_readonly("value", &MyObject6::value);
+ py::class_<Issue6064UnsafePath, shared_ptr_as_custom_holder<Issue6064UnsafePath>>(
+ m, "Issue6064UnsafePath");
+ m.def("get_issue6064_unsafe_path_shared_ptr", []() { return Issue6064UnsafePath::create(); });
+
// test_shared_ptr_and_references
using A = SharedPtrRef::A;
py::class_<A, std::shared_ptr<A>>(m, "A");
@@ -559,6 +593,7 @@
py::class_<C, custom_unique_ptr<C>>(m, "TypeWithMoveOnlyHolder")
.def_static("make", []() { return custom_unique_ptr<C>(new C); })
.def_static("make_as_object", []() { return py::cast(custom_unique_ptr<C>(new C)); });
+ m.def("get_type_with_move_only_holder_shared_ptr", []() { return std::make_shared<C>(); });
// test_holder_with_addressof_operator
using HolderWithAddressOf = shared_ptr_with_addressof_operator<TypeForHolderWithAddressOf>;
diff --git a/tests/test_smart_ptr.py b/tests/test_smart_ptr.py
index 76ebd8c..7ee4b78 100644
--- a/tests/test_smart_ptr.py
+++ b/tests/test_smart_ptr.py
@@ -368,3 +368,27 @@
def test_const_only_holder():
o = m.MyObject6("my_data")
assert o.value == "my_data"
+
+
+@pytest.mark.parametrize(
+ "cast_shared_ptr",
+ [
+ pytest.param(
+ m.get_type_with_move_only_holder_shared_ptr,
+ id="incompatible-custom-holder",
+ ),
+ pytest.param(
+ m.get_issue6064_unsafe_path_shared_ptr,
+ id="issue6064-unsafe-path",
+ ),
+ ],
+)
+def test_shared_ptr_cast_for_custom_holder_throws(cast_shared_ptr):
+ with pytest.raises(
+ RuntimeError,
+ 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"
+ ),
+ ):
+ cast_shared_ptr()