pybind / pybind11

Seamless operability between C++11 and Python
https://pybind11.readthedocs.io/
Other
15.09k stars 2.05k forks source link

[BUG]: Accessing field of type shared_ptr crashes with double free #5058

Open bluenote10 opened 3 months ago

bluenote10 commented 3 months ago

Required prerequisites

What version (or hash if on master) of pybind11 are you using?

2.11.1

Problem description

Trying to access a field of type std::shared_ptr crashes the Python code immediately with double free or corruption.

This looks a bit like the reverse of https://github.com/pybind/pybind11/issues/1138 because the holder object should be a std::unique_ptr (at least the documentation says that this is the default holder object when no holder is specified) and the actual data instance is a std::shared_ptr. Not quite sure if the underlying cause is basically the same (=> duplicate) or if it makes sense to track it separately?

Reproducible example code

C++ code:

#include <cstdint>
#include <iostream>
#include <memory>

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

namespace py = pybind11;

struct Foo
{
  int data;

  Foo() : data{}
  {
    std::cout << "[Foo @ " << std::int64_t(this) << "] constructing instance\n";
  }
  Foo(Foo&& other) : data{other.data}
  {
    std::cout << "[Foo @ " << std::int64_t(this) << "] move-constructing instance\n";
  }
  // Make `Foo` move-only.
  Foo(const Foo&) = delete;
  Foo& operator=(const Foo&) = delete;
  ~Foo()
  {
    std::cout << "[Foo @ " << std::int64_t(this) << "] destructing instance\n";
  }
};

struct Wrapper
{
  std::shared_ptr<Foo> foo;

  Wrapper() : foo{}
  {
    std::cout << "[Wrapper @ " << std::int64_t(this) << "] constructing instance\n";
  }
  Wrapper(Wrapper&& other) : foo{std::move(other.foo)}
  {
    std::cout << "[Wrapper @ " << std::int64_t(this) << "] move-constructing instance\n";
  }
  Wrapper(const Wrapper& other) : foo{other.foo}
  {
    std::cout << "[Wrapper @ " << std::int64_t(this) << "] copy-constructing instance\n";
  }
  ~Wrapper()
  {
    std::cout << "[Wrapper @ " << std::int64_t(this) << "] destructing instance\n";
  }
};

PYBIND11_MODULE(my_native_module, m)
{
  py::class_<Foo>(m, "Foo")  //
      .def_readwrite("data", &Foo::data);

  py::class_<Wrapper>(m, "Wrapper")  //
      .def_readonly("foo", &Wrapper::foo);

  m.def("create_instance", []() {
    Wrapper wrapper{};
    wrapper.foo = std::make_shared<Foo>();
    wrapper.foo->data = 42;
    return wrapper;
  });
}

Python usage code:

import my_native_module

wrapper = my_native_module.create_instance()
print(wrapper.foo)

Output:

[Wrapper @ 140732106222592] constructing instance
[Foo @ 32377264] constructing instance
[Wrapper @ 32376784] move-constructing instance
[Wrapper @ 140732106222592] destructing instance
<my_native_module.Foo object at 0x7f1aa7e509f0>
[Foo @ 32377264] destructing instance
double free or corruption (out)
[1]    488989 abort (core dumped)  python ./test_from_python_bug_mre.py

Is this a regression? Put the last known working version here if it is.

Not a regression