diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index d19a6c15e..d3efcb39f 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -12,6 +12,7 @@ #include "detail/common.h" #include "detail/descr.h" +#include "detail/smart_holder_type_caster_support.h" #include "detail/type_caster_base.h" #include "detail/typeid.h" #include "pytypes.h" @@ -931,8 +932,10 @@ struct move_only_holder_caster> { static_assert(std::is_base_of, type_caster>::value, "Holder classes are only supported for custom types"); - static handle cast(std::unique_ptr &&src, return_value_policy, handle) { - return type_caster_base::unique_ptr_to_python(std::move(src)); + static handle + cast(std::unique_ptr &&src, return_value_policy policy, handle parent) { + return smart_holder_type_caster_support::unique_ptr_to_python( + std::move(src), policy, parent); } static constexpr auto name = type_caster_base::name; }; diff --git a/include/pybind11/detail/smart_holder_type_caster_support.h b/include/pybind11/detail/smart_holder_type_caster_support.h new file mode 100644 index 000000000..4381a32e3 --- /dev/null +++ b/include/pybind11/detail/smart_holder_type_caster_support.h @@ -0,0 +1,107 @@ +#pragma once + +#include "../pytypes.h" +#include "../trampoline_self_life_support.h" +#include "common.h" +#include "dynamic_raw_ptr_cast_if_possible.h" +#include "internals.h" +#include "type_caster_base.h" +#include "typeid.h" + +#include +#include +#include +#include +#include +#include + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN(detail) +PYBIND11_NAMESPACE_BEGIN(smart_holder_type_caster_support) + +template +handle smart_holder_from_unique_ptr(std::unique_ptr &&src, + return_value_policy policy, + handle parent, + const std::pair &st) { + if (policy != return_value_policy::automatic + && policy != return_value_policy::automatic_reference + && policy != return_value_policy::reference_internal + && policy != return_value_policy::move) { + // SMART_HOLDER_WIP: IMPROVABLE: Error message. + throw cast_error("Invalid return_value_policy for unique_ptr."); + } + if (!src) { + return none().release(); + } + void *src_raw_void_ptr = const_cast(st.first); + assert(st.second != nullptr); + const detail::type_info *tinfo = st.second; + if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) { + auto *self_life_support + = dynamic_raw_ptr_cast_if_possible(src.get()); + if (self_life_support != nullptr) { + value_and_holder &v_h = self_life_support->v_h; + if (v_h.inst != nullptr && v_h.vh != nullptr) { + auto &holder = v_h.holder(); + if (!holder.is_disowned) { + pybind11_fail("smart_holder_from_unique_ptr: unexpected " + "smart_holder.is_disowned failure."); + } + // Critical transfer-of-ownership section. This must stay together. + self_life_support->deactivate_life_support(); + holder.reclaim_disowned(); + (void) src.release(); + // Critical section end. + return existing_inst; + } + } + throw cast_error("Invalid unique_ptr: another instance owns this pointer already."); + } + + auto inst = reinterpret_steal(make_new_instance(tinfo->type)); + auto *inst_raw_ptr = reinterpret_cast(inst.ptr()); + inst_raw_ptr->owned = true; + void *&valueptr = values_and_holders(inst_raw_ptr).begin()->value_ptr(); + valueptr = src_raw_void_ptr; + + if (static_cast(src.get()) == src_raw_void_ptr) { + // This is a multiple-inheritance situation that is incompatible with the current + // shared_from_this handling (see PR #3023). + // SMART_HOLDER_WIP: IMPROVABLE: Is there a better solution? + src_raw_void_ptr = nullptr; + } + auto smhldr + = pybindit::memory::smart_holder::from_unique_ptr(std::move(src), src_raw_void_ptr); + tinfo->init_instance(inst_raw_ptr, static_cast(&smhldr)); + + if (policy == return_value_policy::reference_internal) { + keep_alive_impl(inst, parent); + } + + return inst.release(); +} + +template +handle +unique_ptr_to_python(std::unique_ptr &&unq_ptr, return_value_policy policy, handle parent) { + auto *src = unq_ptr.get(); + auto st = type_caster_base::src_and_type(src); + if (st.second == nullptr) { + return handle(); // no type info: error will be set already + } + if (st.second->default_holder) { + return smart_holder_from_unique_ptr(std::move(unq_ptr), policy, parent, st); + } + return type_caster_generic::cast(st.first, + return_value_policy::take_ownership, + {}, + st.second, + nullptr, + nullptr, + std::addressof(unq_ptr)); +} + +PYBIND11_NAMESPACE_END(smart_holder_type_caster_support) +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index 1630c16fb..fd8c81b9a 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -1160,22 +1160,6 @@ public: holder); } - template - static handle unique_ptr_to_python(std::unique_ptr &&unq_ptr) { - auto *src = unq_ptr.get(); - auto st = src_and_type(src); - if (st.second->default_holder) { - throw std::runtime_error("BAKEIN_WIP"); - } - return type_caster_generic::cast(st.first, - return_value_policy::take_ownership, - {}, - st.second, - nullptr, - nullptr, - std::addressof(unq_ptr)); - } - template using cast_op_type = detail::cast_op_type; diff --git a/include/pybind11/trampoline_self_life_support.h b/include/pybind11/trampoline_self_life_support.h new file mode 100644 index 000000000..b7e1f12c4 --- /dev/null +++ b/include/pybind11/trampoline_self_life_support.h @@ -0,0 +1,61 @@ +// Copyright (c) 2021 The Pybind Development Team. +// All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#pragma once + +#include "detail/common.h" +#include "detail/smart_holder_poc.h" +#include "detail/type_caster_base.h" + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + +PYBIND11_NAMESPACE_BEGIN(detail) +// SMART_HOLDER_WIP: Needs refactoring of existing pybind11 code. +inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo); +PYBIND11_NAMESPACE_END(detail) + +// The original core idea for this struct goes back to PyCLIF: +// https://github.com/google/clif/blob/07f95d7e69dca2fcf7022978a55ef3acff506c19/clif/python/runtime.cc#L37 +// URL provided here mainly to give proper credit. To fully explain the `HoldPyObj` feature, more +// context is needed (SMART_HOLDER_WIP). +struct trampoline_self_life_support { + detail::value_and_holder v_h; + + trampoline_self_life_support() = default; + + void activate_life_support(const detail::value_and_holder &v_h_) { + Py_INCREF((PyObject *) v_h_.inst); + v_h = v_h_; + } + + void deactivate_life_support() { + Py_DECREF((PyObject *) v_h.inst); + v_h = detail::value_and_holder(); + } + + ~trampoline_self_life_support() { + if (v_h.inst != nullptr && v_h.vh != nullptr) { + void *value_void_ptr = v_h.value_ptr(); + if (value_void_ptr != nullptr) { + PyGILState_STATE threadstate = PyGILState_Ensure(); + v_h.value_ptr() = nullptr; + v_h.holder().release_disowned(); + detail::deregister_instance(v_h.inst, value_void_ptr, v_h.type); + Py_DECREF((PyObject *) v_h.inst); // Must be after deregister. + PyGILState_Release(threadstate); + } + } + } + + // For the next two, the default implementations generate undefined behavior (ASAN failures + // manually verified). The reason is that v_h needs to be kept default-initialized. + trampoline_self_life_support(const trampoline_self_life_support &) {} + trampoline_self_life_support(trampoline_self_life_support &&) noexcept {} + + // These should never be needed (please provide test cases if you think they are). + trampoline_self_life_support &operator=(const trampoline_self_life_support &) = delete; + trampoline_self_life_support &operator=(trampoline_self_life_support &&) = delete; +}; + +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)