pybind / pybind/pybind11

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

Open
#5,058 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

triage
Dominant language
C++
Stars
18k
Forks
2.3k
Avg merge
5d 17h
Merged PRs (30d)
10

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the C++ and Python reproducer shown, focusing on the .def_readonly("foo", &Wrapper::foo) binding for the std::shared_ptr field. Trace the holder and ownership handling used there; done means accessing wrapper.foo completes without double-free or corruption and the Foo instance is destroyed safely.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, python
Domain
backend-api-design
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.