Skip to content

Commit 5319ca3

Browse files
authored
Using dynamic_cast<AliasType> to determine pointee_depends_on_holder_owner. (#2910)
* Adaption of PyCLIF virtual_py_cpp_mix test. * Removing ValueError: Ownership of instance with virtual overrides in Python cannot be transferred to C++. TODO: static_assert alias class needs to inherit from virtual_overrider_self_life_support. * Bringing back ValueError: "... instance cannot safely be transferred to C++.", but based on dynamic_cast<AliasType>. * Fixing oversight: adding test_class_sh_virtual_py_cpp_mix.cpp to cmake file. * clang <= 3.6 compatibility. * Fixing oversight: dynamic_raw_ptr_cast_if_possible needs special handling for To = void. Adding corresponding missing test in test_class_sh_virtual_py_cpp_mix. Moving dynamic_raw_ptr_cast_if_possible to separate header. * Changing py::detail::virtual_overrider_self_life_support to py::virtual_overrider_self_life_support.
1 parent 3f35af7 commit 5319ca3

12 files changed

+207
-52
lines changed

CMakeLists.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,14 @@ set(PYBIND11_HEADERS
103103
include/pybind11/detail/class.h
104104
include/pybind11/detail/common.h
105105
include/pybind11/detail/descr.h
106+
include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h
106107
include/pybind11/detail/init.h
107108
include/pybind11/detail/internals.h
108109
include/pybind11/detail/smart_holder_poc.h
109110
include/pybind11/detail/smart_holder_sfinae_hooks_only.h
110111
include/pybind11/detail/smart_holder_type_casters.h
111112
include/pybind11/detail/type_caster_base.h
112113
include/pybind11/detail/typeid.h
113-
include/pybind11/detail/virtual_overrider_self_life_support.h
114114
include/pybind11/attr.h
115115
include/pybind11/buffer_info.h
116116
include/pybind11/cast.h
@@ -130,7 +130,8 @@ set(PYBIND11_HEADERS
130130
include/pybind11/pytypes.h
131131
include/pybind11/smart_holder.h
132132
include/pybind11/stl.h
133-
include/pybind11/stl_bind.h)
133+
include/pybind11/stl_bind.h
134+
include/pybind11/virtual_overrider_self_life_support.h)
134135

135136
# Compare with grep and warn if mismatched
136137
if(PYBIND11_MASTER_PROJECT AND NOT CMAKE_VERSION VERSION_LESS 3.12)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) 2021 The Pybind Development Team.
2+
// All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
#pragma once
6+
7+
#include "common.h"
8+
9+
#include <type_traits>
10+
11+
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
12+
PYBIND11_NAMESPACE_BEGIN(detail)
13+
14+
template <typename To, typename From, typename SFINAE = void>
15+
struct dynamic_raw_ptr_cast_is_possible : std::false_type {};
16+
17+
template <typename To, typename From>
18+
struct dynamic_raw_ptr_cast_is_possible<
19+
To,
20+
From,
21+
detail::enable_if_t<!std::is_same<To, void>::value && std::is_polymorphic<From>::value>>
22+
: std::true_type {};
23+
24+
template <typename To,
25+
typename From,
26+
detail::enable_if_t<!dynamic_raw_ptr_cast_is_possible<To, From>::value, int> = 0>
27+
To *dynamic_raw_ptr_cast_if_possible(From * /*ptr*/) {
28+
return nullptr;
29+
}
30+
31+
template <typename To,
32+
typename From,
33+
detail::enable_if_t<dynamic_raw_ptr_cast_is_possible<To, From>::value, int> = 0>
34+
To *dynamic_raw_ptr_cast_if_possible(From *ptr) {
35+
return dynamic_cast<To *>(ptr);
36+
}
37+
38+
PYBIND11_NAMESPACE_END(detail)
39+
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

include/pybind11/detail/smart_holder_type_casters.h

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66

77
#include "../gil.h"
88
#include "../pytypes.h"
9+
#include "../virtual_overrider_self_life_support.h"
910
#include "common.h"
1011
#include "descr.h"
12+
#include "dynamic_raw_ptr_cast_if_possible.h"
1113
#include "internals.h"
1214
#include "smart_holder_poc.h"
1315
#include "smart_holder_sfinae_hooks_only.h"
1416
#include "type_caster_base.h"
1517
#include "typeid.h"
16-
#include "virtual_overrider_self_life_support.h"
1718

1819
#include <cstddef>
1920
#include <memory>
@@ -263,15 +264,13 @@ struct smart_holder_type_caster_class_hooks : smart_holder_type_caster_base_tag
263264
return &modified_type_caster_generic_load_impl::local_load;
264265
}
265266

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) {
267+
template <typename WrappedType, typename AliasType>
268+
static void init_instance_for_type(detail::instance *inst, const void *holder_const_void_ptr) {
270269
// Need for const_cast is a consequence of the type_info::init_instance type:
271270
// void (*init_instance)(instance *, const void *);
272271
auto holder_void_ptr = const_cast<void *>(holder_const_void_ptr);
273272

274-
auto v_h = inst->get_value_and_holder(detail::get_type_info(typeid(T)));
273+
auto v_h = inst->get_value_and_holder(detail::get_type_info(typeid(WrappedType)));
275274
if (!v_h.instance_registered()) {
276275
register_instance(inst, v_h.value_ptr(), v_h.type);
277276
v_h.set_instance_registered();
@@ -282,13 +281,14 @@ struct smart_holder_type_caster_class_hooks : smart_holder_type_caster_base_tag
282281
auto holder_ptr = static_cast<holder_type *>(holder_void_ptr);
283282
new (std::addressof(v_h.holder<holder_type>())) holder_type(std::move(*holder_ptr));
284283
} 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>()));
284+
new (std::addressof(v_h.holder<holder_type>())) holder_type(
285+
holder_type::from_raw_ptr_take_ownership(v_h.value_ptr<WrappedType>()));
287286
} else {
288287
new (std::addressof(v_h.holder<holder_type>()))
289-
holder_type(holder_type::from_raw_ptr_unowned(v_h.value_ptr<T>()));
288+
holder_type(holder_type::from_raw_ptr_unowned(v_h.value_ptr<WrappedType>()));
290289
}
291-
v_h.holder<holder_type>().pointee_depends_on_holder_owner = has_alias;
290+
v_h.holder<holder_type>().pointee_depends_on_holder_owner
291+
= dynamic_raw_ptr_cast_if_possible<AliasType>(v_h.value_ptr<WrappedType>()) != nullptr;
292292
v_h.set_holder_constructed();
293293
}
294294

@@ -391,10 +391,11 @@ struct smart_holder_type_caster_load {
391391
T *raw_type_ptr = convert_type(raw_void_ptr);
392392

393393
auto *self_life_support
394-
= dynamic_cast_virtual_overrider_self_life_support_ptr(raw_type_ptr);
394+
= dynamic_raw_ptr_cast_if_possible<virtual_overrider_self_life_support>(raw_type_ptr);
395395
if (self_life_support == nullptr && holder().pointee_depends_on_holder_owner) {
396-
throw value_error("Ownership of instance with virtual overrides in Python cannot be "
397-
"transferred to C++.");
396+
throw value_error("Alias class (also known as trampoline) does not inherit from "
397+
"py::virtual_overrider_self_life_support, therefore the "
398+
"ownership of this instance cannot safely be transferred to C++.");
398399
}
399400

400401
// Critical transfer-of-ownership section. This must stay together.

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

include/pybind11/detail/virtual_overrider_self_life_support.h renamed to include/pybind11/virtual_overrider_self_life_support.h

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,23 @@
44

55
#pragma once
66

7-
#include "common.h"
8-
#include "smart_holder_poc.h"
9-
#include "type_caster_base.h"
10-
11-
#include <type_traits>
7+
#include "detail/common.h"
8+
#include "detail/smart_holder_poc.h"
9+
#include "detail/type_caster_base.h"
1210

1311
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
14-
PYBIND11_NAMESPACE_BEGIN(detail)
1512

13+
PYBIND11_NAMESPACE_BEGIN(detail)
1614
// SMART_HOLDER_WIP: Needs refactoring of existing pybind11 code.
1715
inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo);
16+
PYBIND11_NAMESPACE_END(detail)
1817

1918
// The original core idea for this struct goes back to PyCLIF:
2019
// https://github.com/google/clif/blob/07f95d7e69dca2fcf7022978a55ef3acff506c19/clif/python/runtime.cc#L37
2120
// URL provided here mainly to give proper credit. To fully explain the `HoldPyObj` feature, more
2221
// context is needed (SMART_HOLDER_WIP).
2322
struct virtual_overrider_self_life_support {
24-
value_and_holder loaded_v_h;
23+
detail::value_and_holder loaded_v_h;
2524
~virtual_overrider_self_life_support() {
2625
if (loaded_v_h.inst != nullptr && loaded_v_h.vh != nullptr) {
2726
void *value_void_ptr = loaded_v_h.value_ptr();
@@ -30,7 +29,7 @@ struct virtual_overrider_self_life_support {
3029
Py_DECREF((PyObject *) loaded_v_h.inst);
3130
loaded_v_h.value_ptr() = nullptr;
3231
loaded_v_h.holder<pybindit::memory::smart_holder>().release_disowned();
33-
deregister_instance(loaded_v_h.inst, value_void_ptr, loaded_v_h.type);
32+
detail::deregister_instance(loaded_v_h.inst, value_void_ptr, loaded_v_h.type);
3433
PyGILState_Release(threadstate);
3534
}
3635
}
@@ -46,17 +45,4 @@ struct virtual_overrider_self_life_support {
4645
= default;
4746
};
4847

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*/) {
52-
return nullptr;
53-
}
54-
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);
59-
}
60-
61-
PYBIND11_NAMESPACE_END(detail)
6248
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ set(PYBIND11_TEST_FILES
106106
test_class_sh_inheritance.cpp
107107
test_class_sh_trampoline_shared_ptr_cpp_arg.cpp
108108
test_class_sh_unique_ptr_member.cpp
109+
test_class_sh_virtual_py_cpp_mix.cpp
109110
test_class_sh_with_alias.cpp
110111
test_constants_and_functions.cpp
111112
test_copy_move.cpp

tests/extra_python_package/test_files.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,21 @@
3535
"include/pybind11/smart_holder.h",
3636
"include/pybind11/stl.h",
3737
"include/pybind11/stl_bind.h",
38+
"include/pybind11/virtual_overrider_self_life_support.h",
3839
}
3940

4041
detail_headers = {
4142
"include/pybind11/detail/class.h",
4243
"include/pybind11/detail/common.h",
4344
"include/pybind11/detail/descr.h",
45+
"include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h",
4446
"include/pybind11/detail/init.h",
4547
"include/pybind11/detail/internals.h",
4648
"include/pybind11/detail/smart_holder_poc.h",
4749
"include/pybind11/detail/smart_holder_sfinae_hooks_only.h",
4850
"include/pybind11/detail/smart_holder_type_casters.h",
4951
"include/pybind11/detail/type_caster_base.h",
5052
"include/pybind11/detail/typeid.h",
51-
"include/pybind11/detail/virtual_overrider_self_life_support.h",
5253
}
5354

5455
cmake_files = {

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():
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#include "pybind11_tests.h"
2+
3+
#include <pybind11/smart_holder.h>
4+
5+
#include <memory>
6+
7+
namespace pybind11_tests {
8+
namespace test_class_sh_virtual_py_cpp_mix {
9+
10+
class Base {
11+
public:
12+
virtual ~Base() = default;
13+
virtual int get() const { return 101; }
14+
15+
// Some compilers complain about implicitly defined versions of some of the following:
16+
Base() = default;
17+
Base(const Base &) = default;
18+
};
19+
20+
class CppDerivedPlain : public Base {
21+
public:
22+
int get() const override { return 202; }
23+
};
24+
25+
class CppDerived : public Base {
26+
public:
27+
int get() const override { return 212; }
28+
};
29+
30+
int get_from_cpp_plainc_ptr(const Base *b) { return b->get() + 4000; }
31+
32+
int get_from_cpp_unique_ptr(std::unique_ptr<Base> b) { return b->get() + 5000; }
33+
34+
struct BaseVirtualOverrider : Base, py::virtual_overrider_self_life_support {
35+
using Base::Base;
36+
37+
int get() const override { PYBIND11_OVERRIDE(int, Base, get); }
38+
};
39+
40+
struct CppDerivedVirtualOverrider : CppDerived, py::virtual_overrider_self_life_support {
41+
using CppDerived::CppDerived;
42+
43+
int get() const override { PYBIND11_OVERRIDE(int, CppDerived, get); }
44+
};
45+
46+
} // namespace test_class_sh_virtual_py_cpp_mix
47+
} // namespace pybind11_tests
48+
49+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_virtual_py_cpp_mix::Base)
50+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(
51+
pybind11_tests::test_class_sh_virtual_py_cpp_mix::CppDerivedPlain)
52+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_virtual_py_cpp_mix::CppDerived)
53+
54+
TEST_SUBMODULE(class_sh_virtual_py_cpp_mix, m) {
55+
using namespace pybind11_tests::test_class_sh_virtual_py_cpp_mix;
56+
57+
py::classh<Base, BaseVirtualOverrider>(m, "Base").def(py::init<>()).def("get", &Base::get);
58+
59+
py::classh<CppDerivedPlain, Base>(m, "CppDerivedPlain").def(py::init<>());
60+
61+
py::classh<CppDerived, Base, CppDerivedVirtualOverrider>(m, "CppDerived").def(py::init<>());
62+
63+
m.def("get_from_cpp_plainc_ptr", get_from_cpp_plainc_ptr, py::arg("b"));
64+
m.def("get_from_cpp_unique_ptr", get_from_cpp_unique_ptr, py::arg("b"));
65+
}

0 commit comments

Comments
 (0)