pybind / pybind/pybind11

Bound C++ object contains "garbage" after being copied in Python

Open
#1,214 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

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

Description

Issue description

This is caused because this (in bound object) becomes an invalid pointer after copying the object in Python using copy.copy.

Reproducible example code

#include <pybind11/pybind11.h>
#include <memory>
#include <iostream>

namespace py = pybind11;

class CopyTest {
public:
    CopyTest(size_t number) : number(number) {
        std::cerr << "[CONSTRUCTOR of CopyTest at " << this << "; number = " << this->number << "]\n";
    }

    ~CopyTest() {
        std::cerr << "[DESTRUCTOR of CopyTest at " << this << "; number = " << this->number << "]\n";
    }

    CopyTest() = delete;
    CopyTest(CopyTest const&) = delete;

    size_t get_number() const { return number; }
    size_t get_memory_location() const { return reinterpret_cast<size_t>(this); }

protected:
    size_t number;
};

std::shared_ptr<CopyTest> create_copy_test(size_t number) {
    return std::make_shared<CopyTest>(number);
}

PYBIND11_MODULE(_pb11pg, m) {
    m.doc() = "pybind11 testing module";

    py::class_<CopyTest, std::shared_ptr<CopyTest>>(m, "CopyTest")
        .def_property_readonly("number", &CopyTest::get_number)
        .def_property_readonly("memory_location", &CopyTest::get_memory_location);

    m.def("create_copy_test", &create_copy_test);
}
def test_copy_error():
    import _pb11pg
    x = _pb11pg.create_copy_test(424242)

    memory_location = x.memory_location
    assert x.number == 424242

    import copy
    x_copy = copy.copy(x)

    copy_memory_location = x_copy.memory_location
    assert x_copy.number == 424242
    assert copy_memory_location == memory_location

Note that the copied object now contains "garbage":

>       assert x_copy.number == 424242
E       assert 287762808833 == 424242
E        +  where 287762808833 = <_pb11pg.CopyTest object at 0x0000012AAAFE7570>.number

and the this pointer is changed:

>       assert copy_memory_location == memory_location
E       assert 1663910680432 == 1663870278992

I've noticed that the constructor and destructor are called only once, and no copy-constructor or default-constructor are called, as I've "removed" them with = delete.

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 building the supplied C++ binding and running the provided Python test_copy_error reproduction with copy.copy. Trace pybind11's handling of copying bound objects and their this pointers. Done means the copied object preserves the original memory location and number value without invalid object state.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.