Skip to content

Commit 8949194

Browse files
committed
Bringing back ValueError: "... instance cannot safely be transferred to C++.", but based on dynamic_cast<AliasType>.
1 parent 21c3694 commit 8949194

5 files changed

+37
-30
lines changed

include/pybind11/detail/smart_holder_type_casters.h

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -263,15 +263,13 @@ struct smart_holder_type_caster_class_hooks : smart_holder_type_caster_base_tag
263263
return &modified_type_caster_generic_load_impl::local_load;
264264
}
265265

266-
template <typename T>
267-
static void init_instance_for_type(detail::instance *inst,
268-
const void *holder_const_void_ptr,
269-
bool has_alias) {
266+
template <typename WrappedType, typename AliasType>
267+
static void init_instance_for_type(detail::instance *inst, const void *holder_const_void_ptr) {
270268
// Need for const_cast is a consequence of the type_info::init_instance type:
271269
// void (*init_instance)(instance *, const void *);
272270
auto holder_void_ptr = const_cast<void *>(holder_const_void_ptr);
273271

274-
auto v_h = inst->get_value_and_holder(detail::get_type_info(typeid(T)));
272+
auto v_h = inst->get_value_and_holder(detail::get_type_info(typeid(WrappedType)));
275273
if (!v_h.instance_registered()) {
276274
register_instance(inst, v_h.value_ptr(), v_h.type);
277275
v_h.set_instance_registered();
@@ -282,13 +280,14 @@ struct smart_holder_type_caster_class_hooks : smart_holder_type_caster_base_tag
282280
auto holder_ptr = static_cast<holder_type *>(holder_void_ptr);
283281
new (std::addressof(v_h.holder<holder_type>())) holder_type(std::move(*holder_ptr));
284282
} else if (inst->owned) {
285-
new (std::addressof(v_h.holder<holder_type>()))
286-
holder_type(holder_type::from_raw_ptr_take_ownership(v_h.value_ptr<T>()));
283+
new (std::addressof(v_h.holder<holder_type>())) holder_type(
284+
holder_type::from_raw_ptr_take_ownership(v_h.value_ptr<WrappedType>()));
287285
} else {
288286
new (std::addressof(v_h.holder<holder_type>()))
289-
holder_type(holder_type::from_raw_ptr_unowned(v_h.value_ptr<T>()));
287+
holder_type(holder_type::from_raw_ptr_unowned(v_h.value_ptr<WrappedType>()));
290288
}
291-
v_h.holder<holder_type>().pointee_depends_on_holder_owner = has_alias;
289+
v_h.holder<holder_type>().pointee_depends_on_holder_owner
290+
= dynamic_raw_ptr_cast_if_possible<AliasType>(v_h.value_ptr<WrappedType>()) != nullptr;
292291
v_h.set_holder_constructed();
293292
}
294293

@@ -391,7 +390,12 @@ struct smart_holder_type_caster_load {
391390
T *raw_type_ptr = convert_type(raw_void_ptr);
392391

393392
auto *self_life_support
394-
= dynamic_cast_virtual_overrider_self_life_support_ptr(raw_type_ptr);
393+
= dynamic_raw_ptr_cast_if_possible<virtual_overrider_self_life_support>(raw_type_ptr);
394+
if (self_life_support == nullptr && holder().pointee_depends_on_holder_owner) {
395+
throw value_error("Alias class (also known as trampoline) does not inherit from "
396+
"py::detail::virtual_overrider_self_life_support, therefore the "
397+
"ownership of this instance cannot safely be transferred to C++.");
398+
}
395399

396400
// Critical transfer-of-ownership section. This must stay together.
397401
if (self_life_support != nullptr) {

include/pybind11/detail/virtual_overrider_self_life_support.h

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,18 @@ struct virtual_overrider_self_life_support {
4646
= default;
4747
};
4848

49-
template <typename T, detail::enable_if_t<!std::is_polymorphic<T>::value, int> = 0>
50-
virtual_overrider_self_life_support *
51-
dynamic_cast_virtual_overrider_self_life_support_ptr(T * /*raw_type_ptr*/) {
49+
template <typename To,
50+
typename From,
51+
detail::enable_if_t<!(std::is_polymorphic<From>::value), int> = 0>
52+
To *dynamic_raw_ptr_cast_if_possible(From * /*ptr*/) {
5253
return nullptr;
5354
}
5455

55-
template <typename T, detail::enable_if_t<std::is_polymorphic<T>::value, int> = 0>
56-
virtual_overrider_self_life_support *
57-
dynamic_cast_virtual_overrider_self_life_support_ptr(T *raw_type_ptr) {
58-
return dynamic_cast<virtual_overrider_self_life_support *>(raw_type_ptr);
56+
template <typename To,
57+
typename From,
58+
detail::enable_if_t<std::is_polymorphic<From>::value, int> = 0>
59+
To *dynamic_raw_ptr_cast_if_possible(From *ptr) {
60+
return dynamic_cast<To *>(ptr);
5961
}
6062

6163
PYBIND11_NAMESPACE_END(detail)

include/pybind11/pybind11.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1612,9 +1612,10 @@ class class_ : public detail::generic_type {
16121612

16131613
// clang-format on
16141614
template <typename T = type,
1615+
typename A = type_alias,
16151616
detail::enable_if_t<detail::type_uses_smart_holder_type_caster<T>::value, int> = 0>
16161617
static void init_instance(detail::instance *inst, const void *holder_ptr) {
1617-
detail::type_caster<T>::template init_instance_for_type<type>(inst, holder_ptr, has_alias);
1618+
detail::type_caster<T>::template init_instance_for_type<T, A>(inst, holder_ptr);
16181619
}
16191620
// clang-format off
16201621

tests/test_class_sh_trampoline_shared_ptr_cpp_arg.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,10 @@ def test_shared_ptr_arg_identity():
5858
del obj
5959
pytest.gc_collect()
6060

61-
# python reference is still around since C++ has it
62-
assert objref() is not None
63-
assert tester.get_object() is objref()
64-
assert tester.has_python_instance() is True
65-
66-
# python reference disappears once the C++ object releases it
67-
tester.set_object(None)
68-
pytest.gc_collect()
61+
# SMART_HOLDER_WIP: the behavior below is DIFFERENT from PR #2839
62+
# python reference is gone because it is not an Alias instance
6963
assert objref() is None
64+
assert tester.has_python_instance() is False
7065

7166

7267
def test_shared_ptr_alias_nonpython():
@@ -90,7 +85,6 @@ def test_shared_ptr_alias_nonpython():
9085
assert tester.has_instance() is True
9186
assert tester.has_python_instance() is False
9287

93-
# SMART_HOLDER_WIP: the behavior below is DIFFERENT from PR #2839
9488
# When we pass it as an arg to a new tester the python instance should
9589
# disappear because it wasn't created with an alias
9690
new_tester = m.SpBaseTester()
@@ -107,9 +101,9 @@ def test_shared_ptr_alias_nonpython():
107101

108102
# Gone!
109103
assert tester.has_instance() is True
110-
assert tester.has_python_instance() is True # False in PR #2839
104+
assert tester.has_python_instance() is False
111105
assert new_tester.has_instance() is True
112-
assert new_tester.has_python_instance() is True # False in PR #2839
106+
assert new_tester.has_python_instance() is False
113107

114108

115109
def test_shared_ptr_goaway():

tests/test_class_sh_with_alias.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,14 @@ def test_drvd0_add_in_cpp_shared_ptr():
4040
def test_drvd0_add_in_cpp_unique_ptr():
4141
while True:
4242
drvd = PyDrvd0(0)
43-
with pytest.raises(RuntimeError):
43+
with pytest.raises(ValueError) as exc_info:
4444
m.AddInCppUniquePtr(drvd, 0)
45+
assert (
46+
str(exc_info.value)
47+
== "Alias class (also known as trampoline) does not inherit from"
48+
" py::detail::virtual_overrider_self_life_support, therefore the ownership of this"
49+
" instance cannot safely be transferred to C++."
50+
)
4551
return # Comment out for manual leak checking (use `top` command).
4652

4753

0 commit comments

Comments
 (0)