Skip to content

Commit 246d686

Browse files
committed
Bug fix: trampoline_self_life_support CpCtor, MvCtor.
1 parent 6c92261 commit 246d686

4 files changed

+132
-6
lines changed

include/pybind11/trampoline_self_life_support.h

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ PYBIND11_NAMESPACE_END(detail)
2222
struct trampoline_self_life_support {
2323
detail::value_and_holder v_h;
2424

25+
trampoline_self_life_support() = default;
26+
2527
void activate_life_support(const detail::value_and_holder &v_h_) {
2628
Py_INCREF((PyObject *) v_h_.inst);
2729
v_h = v_h_;
@@ -46,12 +48,14 @@ struct trampoline_self_life_support {
4648
}
4749
}
4850

49-
// Some compilers complain about implicitly defined versions of some of the following:
50-
trampoline_self_life_support() = default;
51-
trampoline_self_life_support(const trampoline_self_life_support &) = default;
52-
trampoline_self_life_support(trampoline_self_life_support &&) = default;
53-
trampoline_self_life_support &operator=(const trampoline_self_life_support &) = default;
54-
trampoline_self_life_support &operator=(trampoline_self_life_support &&) = default;
51+
// For the next two, the default implementations generate undefined behavior (ASAN failures
52+
// manually verified). The reason is that v_h needs to be kept default-initialized.
53+
trampoline_self_life_support(const trampoline_self_life_support &) {}
54+
trampoline_self_life_support(trampoline_self_life_support &&) {}
55+
56+
// These should never be needed (please provide test cases if you think they are).
57+
trampoline_self_life_support &operator=(const trampoline_self_life_support &) = delete;
58+
trampoline_self_life_support &operator=(trampoline_self_life_support &&) = delete;
5559
};
5660

5761
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ set(PYBIND11_TEST_FILES
107107
test_class_sh_factory_constructors.cpp
108108
test_class_sh_inheritance.cpp
109109
test_class_sh_trampoline_basic.cpp
110+
test_class_sh_trampoline_self_life_support.cpp
110111
test_class_sh_trampoline_shared_ptr_cpp_arg.cpp
111112
test_class_sh_trampoline_unique_ptr.cpp
112113
test_class_sh_unique_ptr_member.cpp
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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+
#include "pybind11/smart_holder.h"
6+
#include "pybind11/trampoline_self_life_support.h"
7+
#include "pybind11_tests.h"
8+
9+
#include <memory>
10+
#include <string>
11+
12+
namespace {
13+
14+
struct Big5 { // Also known as "rule of five".
15+
std::string history;
16+
17+
explicit Big5(std::string history_start) : history{history_start} {}
18+
19+
Big5(const Big5 &other) { history = other.history + "_CpCtor"; }
20+
21+
Big5(Big5 &&other) { history = other.history + "_MvCtor"; }
22+
23+
Big5 &operator=(const Big5 &other) {
24+
history = other.history + "_OpEqLv";
25+
return *this;
26+
}
27+
28+
Big5 &operator=(Big5 &&other) {
29+
history = other.history + "_OpEqRv";
30+
return *this;
31+
}
32+
33+
virtual ~Big5() = default;
34+
35+
protected:
36+
Big5() : history{"DefaultConstructor"} {}
37+
};
38+
39+
struct Big5Trampoline : Big5, py::trampoline_self_life_support {
40+
using Big5::Big5;
41+
};
42+
43+
} // namespace
44+
45+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(Big5)
46+
47+
TEST_SUBMODULE(class_sh_trampoline_self_life_support, m) {
48+
py::classh<Big5, Big5Trampoline>(m, "Big5")
49+
.def(py::init<std::string>())
50+
.def_readonly("history", &Big5::history);
51+
52+
m.def("action", [](std::unique_ptr<Big5> obj, int action_id) {
53+
py::object o2 = py::none();
54+
// This is very unusual, but needed to directly exercise the trampoline_self_life_support
55+
// CpCtor, MvCtor, operator= lvalue, operator= rvalue.
56+
auto obj_trampoline = dynamic_cast<Big5Trampoline *>(obj.get());
57+
if (obj_trampoline != nullptr) {
58+
switch (action_id) {
59+
case 0: { // CpCtor
60+
std::unique_ptr<Big5> cp(new Big5Trampoline(*obj_trampoline));
61+
o2 = py::cast(std::move(cp));
62+
} break;
63+
case 1: { // MvCtor
64+
std::unique_ptr<Big5> mv(new Big5Trampoline(std::move(*obj_trampoline)));
65+
o2 = py::cast(std::move(mv));
66+
} break;
67+
case 2: { // operator= lvalue
68+
std::unique_ptr<Big5> lv(new Big5Trampoline);
69+
*lv = *obj_trampoline;
70+
o2 = py::cast(std::move(lv));
71+
} break;
72+
case 3: { // operator= rvalue
73+
std::unique_ptr<Big5> rv(new Big5Trampoline);
74+
*rv = std::move(*obj_trampoline);
75+
o2 = py::cast(std::move(rv));
76+
} break;
77+
default:
78+
break;
79+
}
80+
}
81+
py::object o1 = py::cast(std::move(obj));
82+
return py::make_tuple(o1, o2);
83+
});
84+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# -*- coding: utf-8 -*-
2+
import pytest
3+
4+
import pybind11_tests.class_sh_trampoline_self_life_support as m
5+
6+
7+
class PyBig5(m.Big5):
8+
pass
9+
10+
11+
def test_m_big5():
12+
obj = m.Big5("Seed")
13+
assert obj.history == "Seed"
14+
o1, o2 = m.action(obj, 0)
15+
assert o1 is not obj
16+
assert o1.history == "Seed"
17+
with pytest.raises(ValueError) as excinfo:
18+
obj.history
19+
assert "Python instance was disowned" in str(excinfo.value)
20+
assert o2 is None
21+
22+
23+
@pytest.mark.parametrize(
24+
"action_id, expected_history",
25+
[
26+
(0, "Seed_CpCtor"),
27+
(1, "Seed_MvCtor"),
28+
(2, "Seed_OpEqLv"),
29+
(3, "Seed_OpEqRv"),
30+
],
31+
)
32+
def test_py_big5(action_id, expected_history):
33+
obj = PyBig5("Seed")
34+
assert obj.history == "Seed"
35+
o1, o2 = m.action(obj, action_id)
36+
assert o1 is obj
37+
assert o2.history == expected_history

0 commit comments

Comments
 (0)