Description
Testing out alternatives w.r.t. #1132, I was testing out the behavior of something like this:
class A {
public:
A(int value)
: value_(value) {}
int value() const { return value_; }
private:
int value_{};
};
unique_ptr<A> create_instance() {
return make_unique<A>(50);
}
shared_ptr<A> check_creation(py::function py_factory) {
shared_ptr<A> obj;
{
py::object py_obj = py_factory();
obj = py::cast<shared_ptr<A>>(py_obj);
// ERROR: Segfault here. I believe it may be due to a double-free, since in one of the implicit `py::cast<>` calls, the move'd `unique_ptr<>` is destructed while still containing a reference.
}
return obj;
}
...
py::class_<A, std::shared_ptr<A>>(m, "A")
.def(py::init<int>())
.def("value", &A::value);
m.def("create_instance", &create_instance);
m.def("check_creation", &check_creation);
...
py::exec(R"(
factory = lambda: m.create_instance()
obj = m.check_creation(factory)
print(obj.value())
)");
Issue
It seems that move_only_holder_caster
is not aggressive enough when calling get()
, and it will double-free with a non-empty unique_ptr<T>
:
pybind11/include/pybind11/cast.h
Lines 1460 to 1463 in 86e2ad4
pybind11/include/pybind11/cast.h
Lines 1369 to 1372 in 86e2ad4
Potential Solution
I believe the simple solution would be to teach holder_helper<holder_type>::get(src)
to use release()
for holder_type&&
when holder_type = unique_ptr<U, ...>
.
Workaround
The simple workaround is to explicitly cast to shared_ptr<T>
in C++, since it can be implicitly constructed from unique_ptr<T>
. This sidesteps pybind11
destructing a non-empty but unused unique_ptr<T>
:
shared_ptr<A> create_instance() {
return make_unique<A>(50);
}