Skip to content

pybind11 wrapped method segfaults if a function returns unique_ptr<T> when holder is shared_ptr<T> #1138

@EricCousineau-TRI

Description

@EricCousineau-TRI

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>:

static handle cast(holder_type &&src, return_value_policy, handle) {
auto *ptr = holder_helper<holder_type>::get(src);
return type_caster_base<type>::cast_holder(ptr, &src);
}

template <typename T>
struct holder_helper {
static auto get(const T &p) -> decltype(p.get()) { return p.get(); }
};

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);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    holdersIssues and PRs regarding holders

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions