Segfault on cleanup with multiple_inheritance and return_value_policy::reference
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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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