pybind / pybind/pybind11

Segfault on cleanup with multiple_inheritance and return_value_policy::reference

Open
#2,073 0 comments 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

I have a base (pure-abstract) interface that is extended by another (pure abstract) interface.

Then i have an implementation of the base interface and a implementation of the extended interface that derives the extended interface and the base implementation via virtual inheritance (diamond)

Now if i return a reference of type InterfaceExtened i can correctly use it.

If i however delete the object that is being referenced on the c++ side, then i get an error during program exit. I do not get such an error when doing the same using InterfaceBase.

So the issue is that a having a reference to a deleted object causes issues during cleanup when that objects has virtual inheritance. I would expect that using the reference would lead to undefined behavior, but not normal application-closing cleanup.

This happens on both windows and linux (Fedora 21) on release 2.4.3

Reproducible example code

pybind11bug.cpp:

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

class InterfaceBase {
public:
    virtual ~InterfaceBase() = default;
    virtual void foo() = 0;  
};

class InterfaceExtended: public virtual InterfaceBase {
public:
    virtual ~InterfaceExtended() = default;
    virtual void bar() = 0;  
};

class ImplementationBase: public virtual InterfaceBase {
public:
    virtual void foo() { std::cout << "foo" << std::endl;};  
};

class ImplementationExtended: public ImplementationBase, public InterfaceExtended {
public:
    virtual void bar() { std::cout << "bar" << std::endl; };  
};

class Helper {
public:
    InterfaceBase& createBaseInstance() {
        instanceBase.reset(new ImplementationBase());
        return *instanceBase;
    }
    
    void deletebaseInstance() {
        instanceBase.reset();
    }

    InterfaceExtended& createExtendedInstance() {
        instanceExt.reset(new ImplementationExtended());
        return *instanceExt;
    }

    void deleteBaseInstance() {
        instanceBase.reset();
    }

    void deleteExtendedInstance() {
        instanceExt.reset();
    }

private:
    std::unique_ptr<InterfaceBase> instanceBase;
    std::unique_ptr<InterfaceExtended> instanceExt;
};

namespace py = pybind11;
    
PYBIND11_MODULE(example, m) {
    py::class_<InterfaceBase>(m, "InterfaceBase")
        .def("foo", &InterfaceBase::foo);
    py::class_<InterfaceExtended, InterfaceBase>(m, "InterfaceExtended", py::multiple_inheritance())
        .def("bar", &InterfaceExtended::bar);
    py::class_<Helper>(m, "Helper")
        .def(py::init())
        .def("create_base_instance", &Helper::createBaseInstance, py::return_value_policy::reference)
        .def("create_extended_instance", &Helper::createExtendedInstance, py::return_value_policy::reference)
        .def("delete_base_instance", &Helper::deleteBaseInstance)
        .def("delete_extended_instance", &Helper::deleteExtendedInstance);
}

I compile with:

c++ -O3 -Wall -shared -std=c++11 -fPIC `python3 -m pybind11 --includes` pybind11bug.cpp -o example`python3-config --extension-suffix`

Then i run:

import example

helper = example.Helper()
base = helper.create_base_instance()
ext = helper.create_extended_instance()
ext.foo()
ext.bar()
# deleting a return_value_policy::reference on c++ side 
# for the base (non-virtual inheritance class) is fine
helper.delete_base_instance()
# deleting a return_value_policy::reference on c++ side 
# for the extended (virtual inheritance class) generates a segfault on cleanup
# (when commenting out the next line there is no segfault)
helper.delete_extended_instance()

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

Compile pybind11bug.cpp with the shown command and run the Python sequence that deletes the C++-owned reference. Start by tracing return_value_policy::reference handling for InterfaceExtended with py::multiple_inheritance(); done means the sequence exits without a cleanup segfault while the base-instance case still behaves as shown.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, python
Domain
api, backend
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.