Skip to content

Commit 4697920

Browse files
authored
Adding virtual_overrider_self_life_support. (#2902)
* Initial version of virtual_overrider_self_life_support (enables safely passing unique_ptr to C++). * Clang 3.6, 3.7 compatibility. * Adding missing default constructor. * Restoring test for exception for the case that virtual_overrider_self_life_support is not used. * Fixing oversight: Adding missing holder().ensure_was_not_disowned(). * Adding unit tests for new `struct smart_holder` member functions. * Moving virtual_overrider_self_life_support to separate include file, with iwyu cleanup.
1 parent d6cf6df commit 4697920

8 files changed

+224
-57
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ set(PYBIND11_HEADERS
110110
include/pybind11/detail/smart_holder_type_casters.h
111111
include/pybind11/detail/type_caster_base.h
112112
include/pybind11/detail/typeid.h
113+
include/pybind11/detail/virtual_overrider_self_life_support.h
113114
include/pybind11/attr.h
114115
include/pybind11/buffer_info.h
115116
include/pybind11/cast.h

include/pybind11/detail/smart_holder_poc.h

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,14 @@ inline bool is_std_default_delete(const std::type_info &rtti_deleter) {
9595
}
9696

9797
struct smart_holder {
98-
const std::type_info *rtti_uqp_del;
98+
const std::type_info *rtti_uqp_del = nullptr;
9999
std::shared_ptr<bool> vptr_deleter_armed_flag_ptr;
100100
std::shared_ptr<void> vptr;
101101
bool vptr_is_using_noop_deleter : 1;
102102
bool vptr_is_using_builtin_delete : 1;
103103
bool vptr_is_external_shared_ptr : 1;
104104
bool is_populated : 1;
105+
bool was_disowned : 1;
105106
bool pointee_depends_on_holder_owner : 1; // SMART_HOLDER_WIP: See PR #2839.
106107

107108
// Design choice: smart_holder is movable but not copyable.
@@ -111,15 +112,15 @@ struct smart_holder {
111112
smart_holder &operator=(const smart_holder &) = delete;
112113

113114
smart_holder()
114-
: rtti_uqp_del{nullptr}, vptr_is_using_noop_deleter{false},
115-
vptr_is_using_builtin_delete{false}, vptr_is_external_shared_ptr{false},
116-
is_populated{false}, pointee_depends_on_holder_owner{false} {}
115+
: vptr_is_using_noop_deleter{false}, vptr_is_using_builtin_delete{false},
116+
vptr_is_external_shared_ptr{false}, is_populated{false}, was_disowned{false},
117+
pointee_depends_on_holder_owner{false} {}
117118

118119
explicit smart_holder(bool vptr_deleter_armed_flag)
119-
: rtti_uqp_del{nullptr}, vptr_deleter_armed_flag_ptr{new bool{vptr_deleter_armed_flag}},
120+
: vptr_deleter_armed_flag_ptr{new bool{vptr_deleter_armed_flag}},
120121
vptr_is_using_noop_deleter{false}, vptr_is_using_builtin_delete{false},
121-
vptr_is_external_shared_ptr{false}, is_populated{false}, pointee_depends_on_holder_owner{
122-
false} {}
122+
vptr_is_external_shared_ptr{false}, is_populated{false}, was_disowned{false},
123+
pointee_depends_on_holder_owner{false} {}
123124

124125
bool has_pointee() const { return vptr.get() != nullptr; }
125126

@@ -135,6 +136,12 @@ struct smart_holder {
135136
throw std::runtime_error(std::string("Unpopulated holder (") + context + ").");
136137
}
137138
}
139+
void ensure_was_not_disowned(const char *context) const {
140+
if (was_disowned) {
141+
throw std::runtime_error(std::string("Holder was disowned already (") + context
142+
+ ").");
143+
}
144+
}
138145

139146
void ensure_vptr_is_using_builtin_delete(const char *context) const {
140147
if (vptr_is_external_shared_ptr) {
@@ -227,16 +234,29 @@ struct smart_holder {
227234
return hld;
228235
}
229236

237+
// Caller is responsible for ensuring preconditions (SMART_HOLDER_WIP: details).
238+
void disown() {
239+
*vptr_deleter_armed_flag_ptr = false;
240+
was_disowned = true;
241+
}
242+
243+
// Caller is responsible for ensuring preconditions (SMART_HOLDER_WIP: details).
244+
void release_disowned() {
245+
vptr.reset();
246+
vptr_deleter_armed_flag_ptr.reset();
247+
}
248+
249+
// SMART_HOLDER_WIP: review this function.
230250
void ensure_can_release_ownership(const char *context = "ensure_can_release_ownership") {
251+
ensure_was_not_disowned(context);
231252
ensure_vptr_is_using_builtin_delete(context);
232253
ensure_use_count_1(context);
233254
}
234255

235-
// Caller is responsible for calling ensure_can_release_ownership().
256+
// Caller is responsible for ensuring preconditions (SMART_HOLDER_WIP: details).
236257
void release_ownership() {
237258
*vptr_deleter_armed_flag_ptr = false;
238-
vptr.reset();
239-
vptr_deleter_armed_flag_ptr.reset();
259+
release_disowned();
240260
}
241261

242262
template <typename T>

include/pybind11/detail/smart_holder_type_casters.h

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "smart_holder_sfinae_hooks_only.h"
1414
#include "type_caster_base.h"
1515
#include "typeid.h"
16+
#include "virtual_overrider_self_life_support.h"
1617

1718
#include <cstddef>
1819
#include <memory>
@@ -352,6 +353,7 @@ struct smart_holder_type_caster_load {
352353
if (!have_holder())
353354
return nullptr;
354355
throw_if_uninitialized_or_disowned_holder();
356+
holder().ensure_was_not_disowned("loaded_as_shared_ptr");
355357
auto void_raw_ptr = holder().template as_raw_ptr_unowned<void>();
356358
auto type_raw_ptr = convert_type(void_raw_ptr);
357359
if (holder().pointee_depends_on_holder_owner) {
@@ -373,28 +375,44 @@ struct smart_holder_type_caster_load {
373375
if (!have_holder())
374376
return nullptr;
375377
throw_if_uninitialized_or_disowned_holder();
378+
holder().ensure_was_not_disowned(context);
376379
holder().template ensure_compatible_rtti_uqp_del<T, D>(context);
377380
holder().ensure_use_count_1(context);
378-
if (holder().pointee_depends_on_holder_owner) {
379-
throw value_error("Ownership of instance with virtual overrides in Python cannot be "
380-
"transferred to C++.");
381-
}
382381
auto raw_void_ptr = holder().template as_raw_ptr_unowned<void>();
382+
383+
void *value_void_ptr = load_impl.loaded_v_h.value_ptr();
384+
if (value_void_ptr != raw_void_ptr) {
385+
pybind11_fail("smart_holder_type_casters: loaded_as_unique_ptr failure:"
386+
" value_void_ptr != raw_void_ptr");
387+
}
388+
383389
// SMART_HOLDER_WIP: MISSING: Safety checks for type conversions
384390
// (T must be polymorphic or meet certain other conditions).
385391
T *raw_type_ptr = convert_type(raw_void_ptr);
386392

393+
auto *self_life_support
394+
= dynamic_cast_virtual_overrider_self_life_support_ptr(raw_type_ptr);
395+
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++.");
398+
}
399+
387400
// Critical transfer-of-ownership section. This must stay together.
388-
holder().release_ownership();
401+
if (self_life_support != nullptr) {
402+
holder().disown();
403+
} else {
404+
holder().release_ownership();
405+
}
389406
auto result = std::unique_ptr<T, D>(raw_type_ptr);
390-
391-
void *value_void_ptr = load_impl.loaded_v_h.value_ptr();
392-
if (value_void_ptr != raw_void_ptr) {
393-
pybind11_fail("smart_holder_type_casters: loaded_as_unique_ptr failure:"
394-
" value_void_ptr != raw_void_ptr");
407+
if (self_life_support != nullptr) {
408+
Py_INCREF((PyObject *) load_impl.loaded_v_h.inst);
409+
self_life_support->loaded_v_h = load_impl.loaded_v_h;
410+
} else {
411+
load_impl.loaded_v_h.value_ptr() = nullptr;
412+
deregister_instance(
413+
load_impl.loaded_v_h.inst, value_void_ptr, load_impl.loaded_v_h.type);
395414
}
396-
load_impl.loaded_v_h.value_ptr() = nullptr;
397-
deregister_instance(load_impl.loaded_v_h.inst, value_void_ptr, load_impl.loaded_v_h.type);
415+
// Critical section end.
398416

399417
return result;
400418
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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+
#include "smart_holder_poc.h"
9+
#include "type_caster_base.h"
10+
11+
#include <type_traits>
12+
13+
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
14+
PYBIND11_NAMESPACE_BEGIN(detail)
15+
16+
// SMART_HOLDER_WIP: Needs refactoring of existing pybind11 code.
17+
inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo);
18+
19+
// The original core idea for this struct goes back to PyCLIF:
20+
// https://github.com/google/clif/blob/07f95d7e69dca2fcf7022978a55ef3acff506c19/clif/python/runtime.cc#L37
21+
// URL provided here mainly to give proper credit. To fully explain the `HoldPyObj` feature, more
22+
// context is needed (SMART_HOLDER_WIP).
23+
struct virtual_overrider_self_life_support {
24+
value_and_holder loaded_v_h;
25+
~virtual_overrider_self_life_support() {
26+
if (loaded_v_h.inst != nullptr && loaded_v_h.vh != nullptr) {
27+
void *value_void_ptr = loaded_v_h.value_ptr();
28+
if (value_void_ptr != nullptr) {
29+
PyGILState_STATE threadstate = PyGILState_Ensure();
30+
Py_DECREF((PyObject *) loaded_v_h.inst);
31+
loaded_v_h.value_ptr() = nullptr;
32+
loaded_v_h.holder<pybindit::memory::smart_holder>().release_disowned();
33+
deregister_instance(loaded_v_h.inst, value_void_ptr, loaded_v_h.type);
34+
PyGILState_Release(threadstate);
35+
}
36+
}
37+
}
38+
39+
// Some compilers complain about implicitly defined versions of some of the following:
40+
virtual_overrider_self_life_support() = default;
41+
virtual_overrider_self_life_support(const virtual_overrider_self_life_support &) = default;
42+
virtual_overrider_self_life_support(virtual_overrider_self_life_support &&) = default;
43+
virtual_overrider_self_life_support &operator=(const virtual_overrider_self_life_support &)
44+
= default;
45+
virtual_overrider_self_life_support &operator=(virtual_overrider_self_life_support &&)
46+
= default;
47+
};
48+
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)
62+
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

tests/extra_python_package/test_files.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"include/pybind11/detail/smart_holder_type_casters.h",
4949
"include/pybind11/detail/type_caster_base.h",
5050
"include/pybind11/detail/typeid.h",
51+
"include/pybind11/detail/virtual_overrider_self_life_support.h",
5152
}
5253

5354
cmake_files = {

tests/pure_cpp/smart_holder_poc_test.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,26 @@ TEST_CASE("from_raw_ptr_take_ownership+as_shared_ptr", "[S]") {
129129
REQUIRE(*new_owner == 19);
130130
}
131131

132+
TEST_CASE("from_raw_ptr_take_ownership+disown+release_disowned", "[S]") {
133+
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
134+
std::unique_ptr<int> new_owner(hld.as_raw_ptr_unowned<int>());
135+
hld.disown();
136+
REQUIRE(hld.as_lvalue_ref<int>() == 19);
137+
REQUIRE(*new_owner == 19);
138+
hld.release_disowned();
139+
REQUIRE(!hld.has_pointee());
140+
}
141+
142+
TEST_CASE("from_raw_ptr_take_ownership+disown+ensure_was_not_disowned", "[E]") {
143+
const char *context = "test_case";
144+
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
145+
hld.ensure_was_not_disowned(context); // Does not throw.
146+
std::unique_ptr<int> new_owner(hld.as_raw_ptr_unowned<int>());
147+
hld.disown();
148+
REQUIRE_THROWS_WITH(hld.ensure_was_not_disowned(context),
149+
"Holder was disowned already (test_case).");
150+
}
151+
132152
TEST_CASE("from_unique_ptr+as_lvalue_ref", "[S]") {
133153
std::unique_ptr<int> orig_owner(new int(19));
134154
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));

tests/test_class_sh_with_alias.cpp

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
namespace pybind11_tests {
88
namespace test_class_sh_with_alias {
99

10+
template <int SerNo> // Using int as a trick to easily generate a series of types.
1011
struct Abase {
1112
int val = 0;
1213
virtual ~Abase() = default;
@@ -21,41 +22,65 @@ struct Abase {
2122
Abase &operator=(Abase &&) = default;
2223
};
2324

24-
struct AbaseAlias : Abase {
25-
using Abase::Abase;
25+
template <int SerNo>
26+
struct AbaseAlias : Abase<SerNo> {
27+
using Abase<SerNo>::Abase;
2628

2729
int Add(int other_val) const override {
28-
PYBIND11_OVERRIDE_PURE(int, /* Return type */
29-
Abase, /* Parent class */
30-
Add, /* Name of function in C++ (must match Python name) */
30+
PYBIND11_OVERRIDE_PURE(int, /* Return type */
31+
Abase<SerNo>, /* Parent class */
32+
Add, /* Name of function in C++ (must match Python name) */
3133
other_val);
3234
}
3335
};
3436

35-
int AddInCppRawPtr(const Abase *obj, int other_val) { return obj->Add(other_val) * 10 + 7; }
37+
template <>
38+
struct AbaseAlias<1> : Abase<1>, py::detail::virtual_overrider_self_life_support {
39+
using Abase<1>::Abase;
3640

37-
int AddInCppSharedPtr(std::shared_ptr<Abase> obj, int other_val) {
41+
int Add(int other_val) const override {
42+
PYBIND11_OVERRIDE_PURE(int, /* Return type */
43+
Abase<1>, /* Parent class */
44+
Add, /* Name of function in C++ (must match Python name) */
45+
other_val);
46+
}
47+
};
48+
49+
template <int SerNo>
50+
int AddInCppRawPtr(const Abase<SerNo> *obj, int other_val) {
51+
return obj->Add(other_val) * 10 + 7;
52+
}
53+
54+
template <int SerNo>
55+
int AddInCppSharedPtr(std::shared_ptr<Abase<SerNo>> obj, int other_val) {
3856
return obj->Add(other_val) * 100 + 11;
3957
}
4058

41-
int AddInCppUniquePtr(std::unique_ptr<Abase> obj, int other_val) {
59+
template <int SerNo>
60+
int AddInCppUniquePtr(std::unique_ptr<Abase<SerNo>> obj, int other_val) {
4261
return obj->Add(other_val) * 100 + 13;
4362
}
4463

64+
template <int SerNo>
65+
void wrap(py::module_ m, const char *py_class_name) {
66+
py::classh<Abase<SerNo>, AbaseAlias<SerNo>>(m, py_class_name)
67+
.def(py::init<int>(), py::arg("val"))
68+
.def("Get", &Abase<SerNo>::Get)
69+
.def("Add", &Abase<SerNo>::Add, py::arg("other_val"));
70+
71+
m.def("AddInCppRawPtr", AddInCppRawPtr<SerNo>, py::arg("obj"), py::arg("other_val"));
72+
m.def("AddInCppSharedPtr", AddInCppSharedPtr<SerNo>, py::arg("obj"), py::arg("other_val"));
73+
m.def("AddInCppUniquePtr", AddInCppUniquePtr<SerNo>, py::arg("obj"), py::arg("other_val"));
74+
}
75+
4576
} // namespace test_class_sh_with_alias
4677
} // namespace pybind11_tests
4778

48-
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_with_alias::Abase)
79+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_with_alias::Abase<0>)
80+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_with_alias::Abase<1>)
4981

5082
TEST_SUBMODULE(class_sh_with_alias, m) {
5183
using namespace pybind11_tests::test_class_sh_with_alias;
52-
53-
py::classh<Abase, AbaseAlias>(m, "Abase")
54-
.def(py::init<int>(), py::arg("val"))
55-
.def("Get", &Abase::Get)
56-
.def("Add", &Abase::Add, py::arg("other_val"));
57-
58-
m.def("AddInCppRawPtr", AddInCppRawPtr, py::arg("obj"), py::arg("other_val"));
59-
m.def("AddInCppSharedPtr", AddInCppSharedPtr, py::arg("obj"), py::arg("other_val"));
60-
m.def("AddInCppUniquePtr", AddInCppUniquePtr, py::arg("obj"), py::arg("other_val"));
84+
wrap<0>(m, "Abase0");
85+
wrap<1>(m, "Abase1");
6186
}

0 commit comments

Comments
 (0)