Browse Source

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.
pull/5733/head
Ralf W. Grosse-Kunstleve 3 weeks ago committed by GitHub
parent
commit
365d41a4ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 7
      include/pybind11/attr.h
  2. 14
      include/pybind11/cast.h
  3. 14
      include/pybind11/detail/internals.h
  4. 111
      include/pybind11/detail/struct_smart_holder.h
  5. 44
      include/pybind11/detail/type_caster_base.h
  6. 11
      include/pybind11/pybind11.h
  7. 5
      include/pybind11/trampoline_self_life_support.h
  8. 6
      tests/pure_cpp/smart_holder_poc.h
  9. 12
      tests/pure_cpp/smart_holder_poc_test.cpp

7
include/pybind11/attr.h

@ -12,6 +12,7 @@ @@ -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 { @@ -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;

14
include/pybind11/cast.h

@ -980,7 +980,7 @@ public: @@ -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: @@ -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: @@ -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: @@ -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: @@ -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 "

14
include/pybind11/detail/internals.h

@ -12,8 +12,10 @@ @@ -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 @@ @@ -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 { @@ -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;

111
include/pybind11/detail/struct_smart_holder.h

@ -50,6 +50,7 @@ Details: @@ -50,6 +50,7 @@ Details:
#include "pybind11_namespace_macros.h"
#include <cstring>
#include <functional>
#include <memory>
#include <stdexcept>
@ -58,19 +59,6 @@ Details: @@ -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 *) { @@ -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 { @@ -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 *) { @@ -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) { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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);
}

44
include/pybind11/detail/type_caster_base.h

@ -531,8 +531,8 @@ struct value_and_holder_helper { @@ -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, @@ -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, @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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)));
}
};

11
include/pybind11/pybind11.h

@ -1571,6 +1571,7 @@ protected: @@ -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: @@ -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) {

5
include/pybind11/trampoline_self_life_support.h

@ -19,6 +19,7 @@ PYBIND11_NAMESPACE_END(detail) @@ -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 { @@ -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)

6
tests/pure_cpp/smart_holder_poc.h

@ -36,17 +36,17 @@ T *as_raw_ptr_release_ownership(smart_holder &hld, @@ -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);
}

12
tests/pure_cpp/smart_holder_poc_test.cpp

@ -14,6 +14,7 @@ @@ -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]") { @@ -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]") { @@ -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]") { @@ -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).");
}

Loading…
Cancel
Save