mirror of
https://github.com/pybind/pybind11.git
synced 2026-03-14 20:27:47 +00:00
Eliminate cross-DSO RTTI reliance in smart_holder functionality (for platforms like macOS). (#5728)
* Revert PR #5700 production code change (pybind11/detail/struct_smart_holder.h). ``` git checkout b19489145b2c7a117138632d624809dfb3b380bb~1 include/pybind11/detail/struct_smart_holder.h ``` * Introduce `get_internals().get_memory_guarded_delete()` * [skip ci] Only pass around `memory::get_guarded_delete` function pointer. * [skip ci] Change a variable name for internal consistency. Add 3 x NOTE: PYBIND11_INTERNALS_VERSION needs to be bumped if changes are made to this struct. * Add comment: get_internals().get_memory_guarded_delete does not need with_internals() * Traverse all DSOs to find memory::guarded_delete with matching RTTI. * Add nullptr check to dynamic_cast overload. Suggested by ChatGPT for these reasons: * Prevents runtime RTTI lookups on nullptr. * Helps avoid undefined behavior if users pass in nulls from failed casts or optional paths. * Ensures consistent return value semantics and no accidental access to vtable. * Improve smart_holder unique_ptr deleter compatibility checks across DSOs: * Replace RTTI-based detection of std::default_delete<T> with a constexpr check to avoid RTTI reliance * Add type_info_equal_across_dso_boundaries() fallback using type_info::name() for RTTI equality across macOS DSOs * Rename related flags and functions for clarity (e.g., builtin → std_default) * Improves ABI robustness and clarity of ownership checks in smart_holder * Trivial renaming for internal consistency: builtin_delete → std_default_delete * Add get_trampoline_self_life_support to detail::type_info (passes local testing). * Polish previous commit slightly. * [skip ci] Store memory::get_guarded_delete in `detail::type_info` instead of `detail::internals` (no searching across DSOs required). * Revert change suggested by ChatGPT. After double-checking, ChatGPT agrees this isn't needed. * Minor polishing.
This commit is contained in:
committed by
GitHub
parent
e2f86af216
commit
365d41a4ba
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "detail/common.h"
|
||||
#include "cast.h"
|
||||
#include "trampoline_self_life_support.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
@@ -312,6 +313,12 @@ struct type_record {
|
||||
/// Function pointer to class_<..>::dealloc
|
||||
void (*dealloc)(detail::value_and_holder &) = nullptr;
|
||||
|
||||
/// Function pointer for casting alias class (aka trampoline) pointer to
|
||||
/// trampoline_self_life_support pointer. Sidesteps cross-DSO RTTI issues
|
||||
/// on platforms like macOS (see PR #5728 for details).
|
||||
get_trampoline_self_life_support_fn get_trampoline_self_life_support
|
||||
= [](void *) -> trampoline_self_life_support * { return nullptr; };
|
||||
|
||||
/// List of base classes of the newly created type
|
||||
list bases;
|
||||
|
||||
|
||||
@@ -980,7 +980,7 @@ public:
|
||||
|
||||
explicit operator std::shared_ptr<type> &() {
|
||||
if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
|
||||
shared_ptr_storage = sh_load_helper.load_as_shared_ptr(value);
|
||||
shared_ptr_storage = sh_load_helper.load_as_shared_ptr(typeinfo, value);
|
||||
}
|
||||
return shared_ptr_storage;
|
||||
}
|
||||
@@ -989,7 +989,8 @@ public:
|
||||
if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
|
||||
// Reusing shared_ptr code to minimize code complexity.
|
||||
shared_ptr_storage
|
||||
= sh_load_helper.load_as_shared_ptr(value,
|
||||
= sh_load_helper.load_as_shared_ptr(typeinfo,
|
||||
value,
|
||||
/*responsible_parent=*/nullptr,
|
||||
/*force_potentially_slicing_shared_ptr=*/true);
|
||||
}
|
||||
@@ -1019,7 +1020,8 @@ public:
|
||||
copyable_holder_caster loader;
|
||||
loader.load(responsible_parent, /*convert=*/false);
|
||||
assert(loader.typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder);
|
||||
return loader.sh_load_helper.load_as_shared_ptr(loader.value, responsible_parent);
|
||||
return loader.sh_load_helper.load_as_shared_ptr(
|
||||
loader.typeinfo, loader.value, responsible_parent);
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -1240,7 +1242,7 @@ public:
|
||||
|
||||
explicit operator std::unique_ptr<type, deleter>() {
|
||||
if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
|
||||
return sh_load_helper.template load_as_unique_ptr<deleter>(value);
|
||||
return sh_load_helper.template load_as_unique_ptr<deleter>(typeinfo, value);
|
||||
}
|
||||
pybind11_fail("Expected to be UNREACHABLE: " __FILE__ ":" PYBIND11_TOSTRING(__LINE__));
|
||||
}
|
||||
@@ -1248,12 +1250,12 @@ public:
|
||||
explicit operator const std::unique_ptr<type, deleter> &() {
|
||||
if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
|
||||
// Get shared_ptr to ensure that the Python object is not disowned elsewhere.
|
||||
shared_ptr_storage = sh_load_helper.load_as_shared_ptr(value);
|
||||
shared_ptr_storage = sh_load_helper.load_as_shared_ptr(typeinfo, value);
|
||||
// Build a temporary unique_ptr that is meant to never expire.
|
||||
unique_ptr_storage = std::shared_ptr<std::unique_ptr<type, deleter>>(
|
||||
new std::unique_ptr<type, deleter>{
|
||||
sh_load_helper.template load_as_const_unique_ptr<deleter>(
|
||||
shared_ptr_storage.get())},
|
||||
typeinfo, shared_ptr_storage.get())},
|
||||
[](std::unique_ptr<type, deleter> *ptr) {
|
||||
if (!ptr) {
|
||||
pybind11_fail("FATAL: `const std::unique_ptr<T, D> &` was disowned "
|
||||
|
||||
@@ -12,8 +12,10 @@
|
||||
#include <pybind11/conduit/pybind11_platform_abi_id.h>
|
||||
#include <pybind11/gil_simple.h>
|
||||
#include <pybind11/pytypes.h>
|
||||
#include <pybind11/trampoline_self_life_support.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "struct_smart_holder.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <exception>
|
||||
@@ -35,11 +37,11 @@
|
||||
/// further ABI-incompatible changes may be made before the ABI is officially
|
||||
/// changed to the new version.
|
||||
#ifndef PYBIND11_INTERNALS_VERSION
|
||||
# define PYBIND11_INTERNALS_VERSION 10
|
||||
# define PYBIND11_INTERNALS_VERSION 11
|
||||
#endif
|
||||
|
||||
#if PYBIND11_INTERNALS_VERSION < 10
|
||||
# error "PYBIND11_INTERNALS_VERSION 10 is the minimum for all platforms for pybind11v3."
|
||||
#if PYBIND11_INTERNALS_VERSION < 11
|
||||
# error "PYBIND11_INTERNALS_VERSION 11 is the minimum for all platforms for pybind11v3."
|
||||
#endif
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
@@ -308,6 +310,12 @@ struct type_info {
|
||||
void *(*operator_new)(size_t);
|
||||
void (*init_instance)(instance *, const void *);
|
||||
void (*dealloc)(value_and_holder &v_h);
|
||||
|
||||
// Cross-DSO-safe function pointers, to sidestep cross-DSO RTTI issues
|
||||
// on platforms like macOS (see PR #5728 for details):
|
||||
memory::get_guarded_delete_fn get_memory_guarded_delete = memory::get_guarded_delete;
|
||||
get_trampoline_self_life_support_fn get_trampoline_self_life_support = nullptr;
|
||||
|
||||
std::vector<PyObject *(*) (PyObject *, PyTypeObject *)> implicit_conversions;
|
||||
std::vector<std::pair<const std::type_info *, void *(*) (void *)>> implicit_casts;
|
||||
std::vector<bool (*)(PyObject *, void *&)> *direct_conversions;
|
||||
|
||||
@@ -50,6 +50,7 @@ Details:
|
||||
|
||||
#include "pybind11_namespace_macros.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
@@ -58,19 +59,6 @@ Details:
|
||||
#include <typeinfo>
|
||||
#include <utility>
|
||||
|
||||
// IMPORTANT: This code block must stay BELOW the #include <stdexcept> above.
|
||||
// This is only required on some builds with libc++ (one of three implementations
|
||||
// in
|
||||
// https://github.com/llvm/llvm-project/blob/a9b64bb3180dab6d28bf800a641f9a9ad54d2c0c/libcxx/include/typeinfo#L271-L276
|
||||
// require it)
|
||||
#if !defined(PYBIND11_EXPORT_GUARDED_DELETE)
|
||||
# if defined(_LIBCPP_VERSION) && !defined(WIN32) && !defined(_WIN32)
|
||||
# define PYBIND11_EXPORT_GUARDED_DELETE __attribute__((visibility("default")))
|
||||
# else
|
||||
# define PYBIND11_EXPORT_GUARDED_DELETE
|
||||
# endif
|
||||
#endif
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
PYBIND11_NAMESPACE_BEGIN(memory)
|
||||
|
||||
@@ -91,7 +79,8 @@ static constexpr bool type_has_shared_from_this(const void *) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct PYBIND11_EXPORT_GUARDED_DELETE guarded_delete {
|
||||
struct guarded_delete {
|
||||
// NOTE: PYBIND11_INTERNALS_VERSION needs to be bumped if changes are made to this struct.
|
||||
std::weak_ptr<void> released_ptr; // Trick to keep the smart_holder memory footprint small.
|
||||
std::function<void(void *)> del_fun; // Rare case.
|
||||
void (*del_ptr)(void *); // Common case.
|
||||
@@ -113,13 +102,19 @@ struct PYBIND11_EXPORT_GUARDED_DELETE guarded_delete {
|
||||
}
|
||||
};
|
||||
|
||||
inline guarded_delete *get_guarded_delete(const std::shared_ptr<void> &ptr) {
|
||||
return std::get_deleter<guarded_delete>(ptr);
|
||||
}
|
||||
|
||||
using get_guarded_delete_fn = guarded_delete *(*) (const std::shared_ptr<void> &);
|
||||
|
||||
template <typename T, typename std::enable_if<std::is_destructible<T>::value, int>::type = 0>
|
||||
inline void builtin_delete_if_destructible(void *raw_ptr) {
|
||||
inline void std_default_delete_if_destructible(void *raw_ptr) {
|
||||
std::default_delete<T>{}(static_cast<T *>(raw_ptr));
|
||||
}
|
||||
|
||||
template <typename T, typename std::enable_if<!std::is_destructible<T>::value, int>::type = 0>
|
||||
inline void builtin_delete_if_destructible(void *) {
|
||||
inline void std_default_delete_if_destructible(void *) {
|
||||
// This noop operator is needed to avoid a compilation error (for `delete raw_ptr;`), but
|
||||
// throwing an exception from a destructor will std::terminate the process. Therefore the
|
||||
// runtime check for lifetime-management correctness is implemented elsewhere (in
|
||||
@@ -127,12 +122,13 @@ inline void builtin_delete_if_destructible(void *) {
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
guarded_delete make_guarded_builtin_delete(bool armed_flag) {
|
||||
return guarded_delete(builtin_delete_if_destructible<T>, armed_flag);
|
||||
guarded_delete make_guarded_std_default_delete(bool armed_flag) {
|
||||
return guarded_delete(std_default_delete_if_destructible<T>, armed_flag);
|
||||
}
|
||||
|
||||
template <typename T, typename D>
|
||||
struct custom_deleter {
|
||||
// NOTE: PYBIND11_INTERNALS_VERSION needs to be bumped if changes are made to this struct.
|
||||
D deleter;
|
||||
explicit custom_deleter(D &&deleter) : deleter{std::forward<D>(deleter)} {}
|
||||
void operator()(void *raw_ptr) { deleter(static_cast<T *>(raw_ptr)); }
|
||||
@@ -144,17 +140,25 @@ guarded_delete make_guarded_custom_deleter(D &&uqp_del, bool armed_flag) {
|
||||
std::function<void(void *)>(custom_deleter<T, D>(std::forward<D>(uqp_del))), armed_flag);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool is_std_default_delete(const std::type_info &rtti_deleter) {
|
||||
return rtti_deleter == typeid(std::default_delete<T>)
|
||||
|| rtti_deleter == typeid(std::default_delete<T const>);
|
||||
template <typename T, typename D>
|
||||
constexpr bool uqp_del_is_std_default_delete() {
|
||||
return std::is_same<D, std::default_delete<T>>::value
|
||||
|| std::is_same<D, std::default_delete<T const>>::value;
|
||||
}
|
||||
|
||||
inline bool type_info_equal_across_dso_boundaries(const std::type_info &a,
|
||||
const std::type_info &b) {
|
||||
// RTTI pointer comparison may fail across DSOs (e.g., macOS libc++).
|
||||
// Fallback to name comparison, which is generally safe and ABI-stable enough for our use.
|
||||
return a == b || std::strcmp(a.name(), b.name()) == 0;
|
||||
}
|
||||
|
||||
struct smart_holder {
|
||||
// NOTE: PYBIND11_INTERNALS_VERSION needs to be bumped if changes are made to this struct.
|
||||
const std::type_info *rtti_uqp_del = nullptr;
|
||||
std::shared_ptr<void> vptr;
|
||||
bool vptr_is_using_noop_deleter : 1;
|
||||
bool vptr_is_using_builtin_delete : 1;
|
||||
bool vptr_is_using_std_default_delete : 1;
|
||||
bool vptr_is_external_shared_ptr : 1;
|
||||
bool is_populated : 1;
|
||||
bool is_disowned : 1;
|
||||
@@ -166,7 +170,7 @@ struct smart_holder {
|
||||
smart_holder &operator=(const smart_holder &) = delete;
|
||||
|
||||
smart_holder()
|
||||
: vptr_is_using_noop_deleter{false}, vptr_is_using_builtin_delete{false},
|
||||
: vptr_is_using_noop_deleter{false}, vptr_is_using_std_default_delete{false},
|
||||
vptr_is_external_shared_ptr{false}, is_populated{false}, is_disowned{false} {}
|
||||
|
||||
bool has_pointee() const { return vptr != nullptr; }
|
||||
@@ -191,7 +195,7 @@ struct smart_holder {
|
||||
}
|
||||
}
|
||||
|
||||
void ensure_vptr_is_using_builtin_delete(const char *context) const {
|
||||
void ensure_vptr_is_using_std_default_delete(const char *context) const {
|
||||
if (vptr_is_external_shared_ptr) {
|
||||
throw std::invalid_argument(std::string("Cannot disown external shared_ptr (")
|
||||
+ context + ").");
|
||||
@@ -200,24 +204,26 @@ struct smart_holder {
|
||||
throw std::invalid_argument(std::string("Cannot disown non-owning holder (") + context
|
||||
+ ").");
|
||||
}
|
||||
if (!vptr_is_using_builtin_delete) {
|
||||
if (!vptr_is_using_std_default_delete) {
|
||||
throw std::invalid_argument(std::string("Cannot disown custom deleter (") + context
|
||||
+ ").");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename D>
|
||||
void ensure_compatible_rtti_uqp_del(const char *context) const {
|
||||
const std::type_info *rtti_requested = &typeid(D);
|
||||
void ensure_compatible_uqp_del(const char *context) const {
|
||||
if (!rtti_uqp_del) {
|
||||
if (!is_std_default_delete<T>(*rtti_requested)) {
|
||||
if (!uqp_del_is_std_default_delete<T, D>()) {
|
||||
throw std::invalid_argument(std::string("Missing unique_ptr deleter (") + context
|
||||
+ ").");
|
||||
}
|
||||
ensure_vptr_is_using_builtin_delete(context);
|
||||
} else if (!(*rtti_requested == *rtti_uqp_del)
|
||||
&& !(vptr_is_using_builtin_delete
|
||||
&& is_std_default_delete<T>(*rtti_requested))) {
|
||||
ensure_vptr_is_using_std_default_delete(context);
|
||||
return;
|
||||
}
|
||||
if (uqp_del_is_std_default_delete<T, D>() && vptr_is_using_std_default_delete) {
|
||||
return;
|
||||
}
|
||||
if (!type_info_equal_across_dso_boundaries(typeid(D), *rtti_uqp_del)) {
|
||||
throw std::invalid_argument(std::string("Incompatible unique_ptr deleter (") + context
|
||||
+ ").");
|
||||
}
|
||||
@@ -244,19 +250,20 @@ struct smart_holder {
|
||||
}
|
||||
}
|
||||
|
||||
void reset_vptr_deleter_armed_flag(bool armed_flag) const {
|
||||
auto *vptr_del_ptr = std::get_deleter<guarded_delete>(vptr);
|
||||
if (vptr_del_ptr == nullptr) {
|
||||
void reset_vptr_deleter_armed_flag(const get_guarded_delete_fn ggd_fn, bool armed_flag) const {
|
||||
auto *gd = ggd_fn(vptr);
|
||||
if (gd == nullptr) {
|
||||
throw std::runtime_error(
|
||||
"smart_holder::reset_vptr_deleter_armed_flag() called in an invalid context.");
|
||||
}
|
||||
vptr_del_ptr->armed_flag = armed_flag;
|
||||
gd->armed_flag = armed_flag;
|
||||
}
|
||||
|
||||
// Caller is responsible for precondition: ensure_compatible_rtti_uqp_del<T, D>() must succeed.
|
||||
// Caller is responsible for precondition: ensure_compatible_uqp_del<T, D>() must succeed.
|
||||
template <typename T, typename D>
|
||||
std::unique_ptr<D> extract_deleter(const char *context) const {
|
||||
const auto *gd = std::get_deleter<guarded_delete>(vptr);
|
||||
std::unique_ptr<D> extract_deleter(const char *context,
|
||||
const get_guarded_delete_fn ggd_fn) const {
|
||||
auto *gd = ggd_fn(vptr);
|
||||
if (gd && gd->use_del_fun) {
|
||||
const auto &custom_deleter_ptr = gd->del_fun.template target<custom_deleter<T, D>>();
|
||||
if (custom_deleter_ptr == nullptr) {
|
||||
@@ -288,28 +295,28 @@ struct smart_holder {
|
||||
static smart_holder from_raw_ptr_take_ownership(T *raw_ptr, bool void_cast_raw_ptr = false) {
|
||||
ensure_pointee_is_destructible<T>("from_raw_ptr_take_ownership");
|
||||
smart_holder hld;
|
||||
auto gd = make_guarded_builtin_delete<T>(true);
|
||||
auto gd = make_guarded_std_default_delete<T>(true);
|
||||
if (void_cast_raw_ptr) {
|
||||
hld.vptr.reset(static_cast<void *>(raw_ptr), std::move(gd));
|
||||
} else {
|
||||
hld.vptr.reset(raw_ptr, std::move(gd));
|
||||
}
|
||||
hld.vptr_is_using_builtin_delete = true;
|
||||
hld.vptr_is_using_std_default_delete = true;
|
||||
hld.is_populated = true;
|
||||
return hld;
|
||||
}
|
||||
|
||||
// Caller is responsible for ensuring the complex preconditions
|
||||
// (see `smart_holder_type_caster_support::load_helper`).
|
||||
void disown() {
|
||||
reset_vptr_deleter_armed_flag(false);
|
||||
void disown(const get_guarded_delete_fn ggd_fn) {
|
||||
reset_vptr_deleter_armed_flag(ggd_fn, false);
|
||||
is_disowned = true;
|
||||
}
|
||||
|
||||
// Caller is responsible for ensuring the complex preconditions
|
||||
// (see `smart_holder_type_caster_support::load_helper`).
|
||||
void reclaim_disowned() {
|
||||
reset_vptr_deleter_armed_flag(true);
|
||||
void reclaim_disowned(const get_guarded_delete_fn ggd_fn) {
|
||||
reset_vptr_deleter_armed_flag(ggd_fn, true);
|
||||
is_disowned = false;
|
||||
}
|
||||
|
||||
@@ -319,14 +326,14 @@ struct smart_holder {
|
||||
|
||||
void ensure_can_release_ownership(const char *context = "ensure_can_release_ownership") const {
|
||||
ensure_is_not_disowned(context);
|
||||
ensure_vptr_is_using_builtin_delete(context);
|
||||
ensure_vptr_is_using_std_default_delete(context);
|
||||
ensure_use_count_1(context);
|
||||
}
|
||||
|
||||
// Caller is responsible for ensuring the complex preconditions
|
||||
// (see `smart_holder_type_caster_support::load_helper`).
|
||||
void release_ownership() {
|
||||
reset_vptr_deleter_armed_flag(false);
|
||||
void release_ownership(const get_guarded_delete_fn ggd_fn) {
|
||||
reset_vptr_deleter_armed_flag(ggd_fn, false);
|
||||
release_disowned();
|
||||
}
|
||||
|
||||
@@ -335,10 +342,10 @@ struct smart_holder {
|
||||
void *void_ptr = nullptr) {
|
||||
smart_holder hld;
|
||||
hld.rtti_uqp_del = &typeid(D);
|
||||
hld.vptr_is_using_builtin_delete = is_std_default_delete<T>(*hld.rtti_uqp_del);
|
||||
hld.vptr_is_using_std_default_delete = uqp_del_is_std_default_delete<T, D>();
|
||||
guarded_delete gd{nullptr, false};
|
||||
if (hld.vptr_is_using_builtin_delete) {
|
||||
gd = make_guarded_builtin_delete<T>(true);
|
||||
if (hld.vptr_is_using_std_default_delete) {
|
||||
gd = make_guarded_std_default_delete<T>(true);
|
||||
} else {
|
||||
gd = make_guarded_custom_deleter<T, D>(std::move(unq_ptr.get_deleter()), true);
|
||||
}
|
||||
|
||||
@@ -531,8 +531,8 @@ struct value_and_holder_helper {
|
||||
}
|
||||
|
||||
// have_holder() must be true or this function will fail.
|
||||
void throw_if_instance_is_currently_owned_by_shared_ptr() const {
|
||||
auto *vptr_gd_ptr = std::get_deleter<memory::guarded_delete>(holder().vptr);
|
||||
void throw_if_instance_is_currently_owned_by_shared_ptr(const type_info *tinfo) const {
|
||||
auto *vptr_gd_ptr = tinfo->get_memory_guarded_delete(holder().vptr);
|
||||
if (vptr_gd_ptr != nullptr && !vptr_gd_ptr->released_ptr.expired()) {
|
||||
throw value_error("Python instance is currently owned by a std::shared_ptr.");
|
||||
}
|
||||
@@ -564,8 +564,7 @@ handle smart_holder_from_unique_ptr(std::unique_ptr<T, D> &&src,
|
||||
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<trampoline_self_life_support>(src.get());
|
||||
auto *self_life_support = tinfo->get_trampoline_self_life_support(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) {
|
||||
@@ -576,7 +575,7 @@ handle smart_holder_from_unique_ptr(std::unique_ptr<T, D> &&src,
|
||||
}
|
||||
// Critical transfer-of-ownership section. This must stay together.
|
||||
self_life_support->deactivate_life_support();
|
||||
holder.reclaim_disowned();
|
||||
holder.reclaim_disowned(tinfo->get_memory_guarded_delete);
|
||||
(void) src.release();
|
||||
// Critical section end.
|
||||
return existing_inst;
|
||||
@@ -742,7 +741,8 @@ struct load_helper : value_and_holder_helper {
|
||||
return std::shared_ptr<T>(raw_ptr, shared_ptr_parent_life_support(parent.ptr()));
|
||||
}
|
||||
|
||||
std::shared_ptr<T> load_as_shared_ptr(void *void_raw_ptr,
|
||||
std::shared_ptr<T> load_as_shared_ptr(const type_info *tinfo,
|
||||
void *void_raw_ptr,
|
||||
handle responsible_parent = nullptr,
|
||||
// to support py::potentially_slicing_weak_ptr
|
||||
// with minimal added code complexity:
|
||||
@@ -763,7 +763,7 @@ struct load_helper : value_and_holder_helper {
|
||||
}
|
||||
auto *type_raw_ptr = static_cast<T *>(void_raw_ptr);
|
||||
if (python_instance_is_alias && !force_potentially_slicing_shared_ptr) {
|
||||
auto *vptr_gd_ptr = std::get_deleter<memory::guarded_delete>(hld.vptr);
|
||||
auto *vptr_gd_ptr = tinfo->get_memory_guarded_delete(holder().vptr);
|
||||
if (vptr_gd_ptr != nullptr) {
|
||||
std::shared_ptr<void> released_ptr = vptr_gd_ptr->released_ptr.lock();
|
||||
if (released_ptr) {
|
||||
@@ -800,31 +800,32 @@ struct load_helper : value_and_holder_helper {
|
||||
}
|
||||
|
||||
template <typename D>
|
||||
std::unique_ptr<T, D> load_as_unique_ptr(void *raw_void_ptr,
|
||||
std::unique_ptr<T, D> load_as_unique_ptr(const type_info *tinfo,
|
||||
void *raw_void_ptr,
|
||||
const char *context = "load_as_unique_ptr") {
|
||||
if (!have_holder()) {
|
||||
return unique_with_deleter<T, D>(nullptr, std::unique_ptr<D>());
|
||||
}
|
||||
throw_if_uninitialized_or_disowned_holder(typeid(T));
|
||||
throw_if_instance_is_currently_owned_by_shared_ptr();
|
||||
throw_if_instance_is_currently_owned_by_shared_ptr(tinfo);
|
||||
holder().ensure_is_not_disowned(context);
|
||||
holder().template ensure_compatible_rtti_uqp_del<T, D>(context);
|
||||
holder().template ensure_compatible_uqp_del<T, D>(context);
|
||||
holder().ensure_use_count_1(context);
|
||||
|
||||
T *raw_type_ptr = static_cast<T *>(raw_void_ptr);
|
||||
|
||||
auto *self_life_support
|
||||
= dynamic_raw_ptr_cast_if_possible<trampoline_self_life_support>(raw_type_ptr);
|
||||
auto *self_life_support = tinfo->get_trampoline_self_life_support(raw_type_ptr);
|
||||
// This is enforced indirectly by a static_assert in the class_ implementation:
|
||||
assert(!python_instance_is_alias || self_life_support);
|
||||
|
||||
std::unique_ptr<D> extracted_deleter = holder().template extract_deleter<T, D>(context);
|
||||
std::unique_ptr<D> extracted_deleter
|
||||
= holder().template extract_deleter<T, D>(context, tinfo->get_memory_guarded_delete);
|
||||
|
||||
// Critical transfer-of-ownership section. This must stay together.
|
||||
if (self_life_support != nullptr) {
|
||||
holder().disown();
|
||||
holder().disown(tinfo->get_memory_guarded_delete);
|
||||
} else {
|
||||
holder().release_ownership();
|
||||
holder().release_ownership(tinfo->get_memory_guarded_delete);
|
||||
}
|
||||
auto result = unique_with_deleter<T, D>(raw_type_ptr, std::move(extracted_deleter));
|
||||
if (self_life_support != nullptr) {
|
||||
@@ -842,14 +843,17 @@ struct load_helper : value_and_holder_helper {
|
||||
// This assumes load_as_shared_ptr succeeded(), and the returned shared_ptr is still alive.
|
||||
// The returned unique_ptr is meant to never expire (the behavior is undefined otherwise).
|
||||
template <typename D>
|
||||
std::unique_ptr<T, D>
|
||||
load_as_const_unique_ptr(T *raw_type_ptr, const char *context = "load_as_const_unique_ptr") {
|
||||
std::unique_ptr<T, D> load_as_const_unique_ptr(const type_info *tinfo,
|
||||
T *raw_type_ptr,
|
||||
const char *context
|
||||
= "load_as_const_unique_ptr") {
|
||||
if (!have_holder()) {
|
||||
return unique_with_deleter<T, D>(nullptr, std::unique_ptr<D>());
|
||||
}
|
||||
holder().template ensure_compatible_rtti_uqp_del<T, D>(context);
|
||||
return unique_with_deleter<T, D>(
|
||||
raw_type_ptr, std::move(holder().template extract_deleter<T, D>(context)));
|
||||
holder().template ensure_compatible_uqp_del<T, D>(context);
|
||||
return unique_with_deleter<T, D>(raw_type_ptr,
|
||||
std::move(holder().template extract_deleter<T, D>(
|
||||
context, tinfo->get_memory_guarded_delete)));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1571,6 +1571,7 @@ protected:
|
||||
tinfo->holder_size_in_ptrs = size_in_ptrs(rec.holder_size);
|
||||
tinfo->init_instance = rec.init_instance;
|
||||
tinfo->dealloc = rec.dealloc;
|
||||
tinfo->get_trampoline_self_life_support = rec.get_trampoline_self_life_support;
|
||||
tinfo->simple_type = true;
|
||||
tinfo->simple_ancestors = true;
|
||||
tinfo->module_local = rec.module_local;
|
||||
@@ -2066,6 +2067,16 @@ public:
|
||||
record.dealloc = dealloc_without_manipulating_gil;
|
||||
}
|
||||
|
||||
if (std::is_base_of<trampoline_self_life_support, type_alias>::value) {
|
||||
// Store a cross-DSO-safe getter.
|
||||
// This lambda is defined in the same DSO that instantiates
|
||||
// class_<type, alias_type>, but it can be called safely from any other DSO.
|
||||
record.get_trampoline_self_life_support = [](void *type_ptr) {
|
||||
return dynamic_raw_ptr_cast_if_possible<trampoline_self_life_support>(
|
||||
static_cast<type *>(type_ptr));
|
||||
};
|
||||
}
|
||||
|
||||
generic_type::initialize(record);
|
||||
|
||||
if (has_alias) {
|
||||
|
||||
@@ -19,6 +19,7 @@ PYBIND11_NAMESPACE_END(detail)
|
||||
// https://github.com/google/clif/blob/07f95d7e69dca2fcf7022978a55ef3acff506c19/clif/python/runtime.cc#L37
|
||||
// URL provided here mainly to give proper credit.
|
||||
struct trampoline_self_life_support {
|
||||
// NOTE: PYBIND11_INTERNALS_VERSION needs to be bumped if changes are made to this struct.
|
||||
detail::value_and_holder v_h;
|
||||
|
||||
trampoline_self_life_support() = default;
|
||||
@@ -57,4 +58,8 @@ struct trampoline_self_life_support {
|
||||
trampoline_self_life_support &operator=(trampoline_self_life_support &&) = delete;
|
||||
};
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
using get_trampoline_self_life_support_fn = trampoline_self_life_support *(*) (void *);
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
|
||||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
|
||||
|
||||
@@ -36,17 +36,17 @@ T *as_raw_ptr_release_ownership(smart_holder &hld,
|
||||
const char *context = "as_raw_ptr_release_ownership") {
|
||||
hld.ensure_can_release_ownership(context);
|
||||
T *raw_ptr = hld.as_raw_ptr_unowned<T>();
|
||||
hld.release_ownership();
|
||||
hld.release_ownership(get_guarded_delete);
|
||||
return raw_ptr;
|
||||
}
|
||||
|
||||
template <typename T, typename D = std::default_delete<T>>
|
||||
std::unique_ptr<T, D> as_unique_ptr(smart_holder &hld) {
|
||||
static const char *context = "as_unique_ptr";
|
||||
hld.ensure_compatible_rtti_uqp_del<T, D>(context);
|
||||
hld.ensure_compatible_uqp_del<T, D>(context);
|
||||
hld.ensure_use_count_1(context);
|
||||
T *raw_ptr = hld.as_raw_ptr_unowned<T>();
|
||||
hld.release_ownership();
|
||||
hld.release_ownership(get_guarded_delete);
|
||||
// KNOWN DEFECT (see PR #4850): Does not copy the deleter.
|
||||
return std::unique_ptr<T, D>(raw_ptr);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch.hpp"
|
||||
|
||||
using pybind11::memory::guarded_delete;
|
||||
using pybind11::memory::smart_holder;
|
||||
namespace poc = pybind11::memory::smart_holder_poc;
|
||||
|
||||
@@ -160,11 +161,12 @@ TEST_CASE("from_raw_ptr_take_ownership+as_shared_ptr", "[S]") {
|
||||
TEST_CASE("from_raw_ptr_take_ownership+disown+reclaim_disowned", "[S]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
std::unique_ptr<int> new_owner(hld.as_raw_ptr_unowned<int>());
|
||||
hld.disown();
|
||||
hld.disown(pybind11::memory::get_guarded_delete);
|
||||
REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
|
||||
REQUIRE(*new_owner == 19);
|
||||
hld.reclaim_disowned(); // Manually veriified: without this, clang++ -fsanitize=address reports
|
||||
// "detected memory leaks".
|
||||
// Manually verified: without this, clang++ -fsanitize=address reports
|
||||
// "detected memory leaks".
|
||||
hld.reclaim_disowned(pybind11::memory::get_guarded_delete);
|
||||
// NOLINTNEXTLINE(bugprone-unused-return-value)
|
||||
(void) new_owner.release(); // Manually verified: without this, clang++ -fsanitize=address
|
||||
// reports "attempting double-free".
|
||||
@@ -175,7 +177,7 @@ TEST_CASE("from_raw_ptr_take_ownership+disown+reclaim_disowned", "[S]") {
|
||||
TEST_CASE("from_raw_ptr_take_ownership+disown+release_disowned", "[S]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
std::unique_ptr<int> new_owner(hld.as_raw_ptr_unowned<int>());
|
||||
hld.disown();
|
||||
hld.disown(pybind11::memory::get_guarded_delete);
|
||||
REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
|
||||
REQUIRE(*new_owner == 19);
|
||||
hld.release_disowned();
|
||||
@@ -187,7 +189,7 @@ TEST_CASE("from_raw_ptr_take_ownership+disown+ensure_is_not_disowned", "[E]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
hld.ensure_is_not_disowned(context); // Does not throw.
|
||||
std::unique_ptr<int> new_owner(hld.as_raw_ptr_unowned<int>());
|
||||
hld.disown();
|
||||
hld.disown(pybind11::memory::get_guarded_delete);
|
||||
REQUIRE_THROWS_WITH(hld.ensure_is_not_disowned(context),
|
||||
"Holder was disowned already (test_case).");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user