diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index c5dbda9ed3..e3111e89c6 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1219,17 +1219,19 @@ move(T &&value) { } template -detail::enable_if_t::value, T> move(object &&obj) { - if (obj.ref_count() > 1) { +detail::enable_if_t::value, T> move(object &&obj) {\ + if constexpr (detail::cast_is_temporary_value_reference::value) { + if (obj.ref_count() > 1) { #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) - throw cast_error( + throw cast_error( "Unable to cast Python instance to C++ rvalue: instance has multiple references" - " (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); + " (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); #else - throw cast_error("Unable to move from Python " + (std::string) str(type::handle_of(obj)) - + " instance to C++ " + type_id() + throw cast_error("Unable to move from Python " + (std::string) str(type::handle_of(obj)) + + " instance to C++ " + type_id() + " instance: instance has multiple references"); #endif + } } // Move into a temporary and return that, because the reference may be a local value of `conv` diff --git a/tests/test_smart_ptr.cpp b/tests/test_smart_ptr.cpp index e5529f58a2..81d2414781 100644 --- a/tests/test_smart_ptr.cpp +++ b/tests/test_smart_ptr.cpp @@ -10,6 +10,7 @@ #include "object.h" #include "pybind11_tests.h" +#include "pybind11/functional.h" namespace { @@ -531,6 +532,16 @@ TEST_SUBMODULE(smart_ptr, m) { return out; }); + using Callback = std::function()>; + m.def("unique_ptr_callback", + [](const Callback& callback) { + auto obj = callback(); + if (obj->value() != 10) { + throw std::runtime_error("obj.value() must be 10"); + } + return callback(); + }); + // Ensure class is non-empty, so it's easier to detect double-free // corruption. (If empty, this may be harder to see easily.) struct SharedPtrHeld { diff --git a/tests/test_smart_ptr.py b/tests/test_smart_ptr.py index c6b1d80530..80ecbbc0fc 100644 --- a/tests/test_smart_ptr.py +++ b/tests/test_smart_ptr.py @@ -378,3 +378,16 @@ def check_reset(obj_new): check_reset(obj_new=m.UniquePtrHeld(10)) check_reset(obj_new=None) + + +def test_unique_ptr_self_reference(): + obj = None + + def make_unique_ptr_held(): + nonlocal obj + # This simulates a self-reference for a derived type. + obj = m.UniquePtrHeld(10) + return obj + + out = m.unique_ptr_callback(make_unique_ptr_held) + assert obj is out diff --git a/tests/test_virtual_functions.py b/tests/test_virtual_functions.py index 4d00d3690d..85e088b3b7 100644 --- a/tests/test_virtual_functions.py +++ b/tests/test_virtual_functions.py @@ -216,7 +216,10 @@ def get_movable(self, a, b): ncv2 = NCVirtExt2() assert ncv2.print_movable(7, 7) == "14" # Don't check the exception message here because it differs under debug/non-debug mode - with pytest.raises(RuntimeError): + + # N.B. Drake fork allows this behavior. + if True: + # with pytest.raises(RuntimeError): ncv2.print_nc(9, 9) nc_stats = ConstructorStats.get(m.NonCopyable)