Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions include/pybind11/attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include "detail/common.h"
#include "cast.h"
#include "trampoline_self_life_support.h"

#include <functional>

Expand Down Expand Up @@ -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;

Expand Down
14 changes: 8 additions & 6 deletions include/pybind11/cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -980,7 +980,7 @@ struct copyable_holder_caster<

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;
}
Expand All @@ -989,7 +989,8 @@ struct copyable_holder_caster<
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);
}
Expand Down Expand Up @@ -1019,7 +1020,8 @@ struct copyable_holder_caster<
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:
Expand Down Expand Up @@ -1240,20 +1242,20 @@ struct move_only_holder_caster<

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__));
}

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 "
Expand Down
14 changes: 11 additions & 3 deletions include/pybind11/detail/internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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)
Expand Down Expand Up @@ -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;
Expand Down
111 changes: 59 additions & 52 deletions include/pybind11/detail/struct_smart_holder.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ High-level aspects:

#include "pybind11_namespace_macros.h"

#include <cstring>
#include <functional>
#include <memory>
#include <stdexcept>
Expand All @@ -58,19 +59,6 @@ High-level aspects:
#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)

Expand All @@ -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.
Expand All @@ -113,26 +102,33 @@ 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
// ensure_pointee_is_destructible()).
}

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)); }
Expand All @@ -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;
Expand All @@ -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; }
Expand All @@ -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 + ").");
Expand All @@ -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
+ ").");
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
}

Expand All @@ -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();
}

Expand All @@ -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);
}
Expand Down
Loading
Loading