From 09f4e5708b0d04fe494ac5f4dc13ae91b3fd0563 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Mon, 18 May 2026 09:12:42 -0700 Subject: [PATCH] [skip ci] test: reject shared_ptr casts for custom holders Lock down the current behavior discussed in issue #6064: py::cast() from std::shared_ptr 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. --- tests/test_smart_ptr.cpp | 35 +++++++++++++++++++++++++++++++++++ tests/test_smart_ptr.py | 24 ++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/tests/test_smart_ptr.cpp b/tests/test_smart_ptr.cpp index 3617fa3e85..14090895bd 100644 --- a/tests/test_smart_ptr.cpp +++ b/tests/test_smart_ptr.cpp @@ -83,6 +83,24 @@ class const_only_shared_ptr { // for demonstration purpose only, this imitates smart pointer with a const-only pointer }; +template +class shared_ptr_as_custom_holder { + std::shared_ptr 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 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 @@ struct SharedFromThisRef { std::shared_ptr shared = std::make_shared(); }; +class Issue6064UnsafePath { +public: + static std::shared_ptr 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() = default; @@ -341,6 +370,7 @@ struct holder_helper> { // Make pybind aware of the ref-counted wrapper type (s): PYBIND11_DECLARE_HOLDER_TYPE(T, ref, true) PYBIND11_DECLARE_HOLDER_TYPE(T, const_only_shared_ptr, true) +PYBIND11_DECLARE_HOLDER_TYPE(T, shared_ptr_as_custom_holder) // The following is not required anymore for std::shared_ptr, but it should compile without error: PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr) PYBIND11_DECLARE_HOLDER_TYPE(T, huge_unique_ptr) @@ -501,6 +531,10 @@ TEST_SUBMODULE(smart_ptr, m) { .def(py::init([](const std::string &value) { return MyObject6::createObject(value); })) .def_property_readonly("value", &MyObject6::value); + py::class_>( + m, "Issue6064UnsafePath"); + m.def("get_issue6064_unsafe_path_shared_ptr", []() { return Issue6064UnsafePath::create(); }); + // test_shared_ptr_and_references using A = SharedPtrRef::A; py::class_>(m, "A"); @@ -559,6 +593,7 @@ TEST_SUBMODULE(smart_ptr, m) { py::class_>(m, "TypeWithMoveOnlyHolder") .def_static("make", []() { return custom_unique_ptr(new C); }) .def_static("make_as_object", []() { return py::cast(custom_unique_ptr(new C)); }); + m.def("get_type_with_move_only_holder_shared_ptr", []() { return std::make_shared(); }); // test_holder_with_addressof_operator using HolderWithAddressOf = shared_ptr_with_addressof_operator; diff --git a/tests/test_smart_ptr.py b/tests/test_smart_ptr.py index 76ebd8cf20..7ee4b78ed5 100644 --- a/tests/test_smart_ptr.py +++ b/tests/test_smart_ptr.py @@ -368,3 +368,27 @@ def test_move_only_holder_caster_shared_ptr_with_smart_holder_support_enabled(): 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 to Python when the bound type does not use" + " std::shared_ptr or py::smart_holder as its holder type" + ), + ): + cast_shared_ptr()