Skip to content

Add check for matching holder_type when inheriting - #588

Merged
wjakob merged 1 commit into
pybind:masterfrom
lsst-dm:tickets/DM-8631
Jan 31, 2017
Merged

Add check for matching holder_type when inheriting#588
wjakob merged 1 commit into
pybind:masterfrom
lsst-dm:tickets/DM-8631

Conversation

@pschella

@pschellapschella commented Jan 4, 2017

Copy link
Copy Markdown
Contributor

One frequently encountered source of errors are mismatching holder types between bases and derived classes. If either the base has unique_ptr holder type and the derived class shared_ptr (or vice versa) a segfault results.

Tracking holder types for all classes in a large codebase can be cumbersome and error prone.

This pull request is an example of how import-time checks for matching holder types can be added. I welcome your comments.

@pschellapschella changed the title Tickets/dm 8631Add check for matching holder_type when inheritingJan 4, 2017
@dean0x7d

Copy link
Copy Markdown
Member

I think the test could be simplified so it doesn't require a separate module. It should be possible to pack up everything into a function within the existing test module. Something like:

m.def("test_matching_holder_type", []() {
structBase { };
structDerived : Base { };
try {
py::class_<Base>(m, "Base");
py::class_<Derived, std::shared_ptr<Derived>, Base>(m, "Derived")
.def(py::init<>());
} catch (std::runtime_error &) {
returntrue;
}
returnfalse;
});
asserttest_matching_holder_type() isTrue

It might be possible to simplify void_holder to a pure type metafunction:

template <typename... Ts>
structvoid_holder<std::unique_ptr<Ts...>> {
using type = std::unique_ptr<void>;
};

where the typeid just moves outside:

tinfo->void_holder_type = &typeid(detail::void_holder<holder_type>::type);

A more generic approach for void_holder might also be worth considering so that users don't need to provide specializations in most cases:

template <typename T, typenameSFINAE = void>
structvoid_holder {
using type = void;
}template <typename...> usingvoid_t = void;
template <template<typename...> classHolder, typename... Ts>
structvoid_holder<Holder<Ts...>, void_t<Holder<void>>> {
using type = Holder<void>;
};

The void_t SFINAE would make sure Holder<void> is a valid type. But I'm just thinking out loud here and this might not even compile properly.

@wjakob

Copy link
Copy Markdown
Member

+1 for the single module approach proposed by @dean0x7d.

FWIW I think that storing a full type ID is overkill. I suggest to record a single bool which records whether a non-standard holder (i.e. something other than std::unique_ptr<Type>) is used.

@pschella

Copy link
Copy Markdown
ContributorAuthor

Thanks for the feedback. I pushed a simplified version.
(left the previous commit intact, so will have to be squashed before potential merge)

@pschella

Copy link
Copy Markdown
ContributorAuthor

That is why it was variadic, but I agree this is better. I'll make the change.

@pschella

Copy link
Copy Markdown
ContributorAuthor

Wondering if we should split the error reporting telling which one has non-default holder type?

@pschella

Copy link
Copy Markdown
ContributorAuthor

I split it up. Easier to debug.

Comment threadinclude/pybind11/attr.h Outdated
/// Is the default (unique_ptr) holder type used?
bool default_holder : 1;

PYBIND11_NOINLINE void add_base(const std::type_info *base, void *(*caster)(void *), const bool check_holder_type = false) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the check_holder_type parameter needed?

Comment threadtests/test_inheritance.cpp Outdated
py::class_<Base>(m, "Base");
py::class_<Derived, std::shared_ptr<Derived>, Base>(m, "Derived")
.def(py::init<>());
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm missing something, the second test is going to be skipped completely because the first one throws. Consider separate functions.

Also, .def(py::init<>()) is probably not needed for the test.

Comment threadtests/test_inheritance.cpp Outdated
.def(py::init<>());
}
} catch (std::runtime_error &) {
return true;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I need to revise my original suggestion a bit. It might be nicer to catch the exception in Python in order to also check that the correct exception was thrown.

m.def("test_matching_holder_type", []() {
structBase { };
structDerived : Base { };
py::class_<Base>(m, "Base");
py::class_<Derived, std::shared_ptr<Derived>, Base>(m, "Derived");
});
withpytest.raises(RuntimeError) asexcinfo:
test_matching_holder_type()
assertstr(excinfo.value) =="generic_type: ..."

@pschella

Copy link
Copy Markdown
ContributorAuthor

I followed the suggestions from @dean0x7d to clean things up a bit more. Please let me know if I should squash the commits into one.

@dean0x7d

Copy link
Copy Markdown
Member

There are some weird failures on AppVeyor. It may have to do with the uninitialized py::module m;. Try initializing it with a name or maybe auto m = py::module::import("__main__"); and see if that clears it.

There are also some minor style issues. See the log on Travis.

@pschella

Copy link
Copy Markdown
ContributorAuthor

Changes implemented and commits squashed.

@wjakob

Copy link
Copy Markdown
Member

Looks great -- thank you!

@wjakob
wjakob merged commit cc88aae into pybind:masterJan 31, 2017
dean0x7d added a commit to dean0x7d/pybind11 that referenced this pull request Mar 20, 2017
Instead of a segfault. Fixespybind#751.
This covers the case of loading a custom holder from a default-holder
instance. Attempting to load one custom holder from a different custom
holder (i.e. not `std::unique_ptr`) yields undefined behavior, just as
pybind#588 established for inheritance.
dean0x7d added a commit that referenced this pull request Mar 21, 2017
Instead of a segfault. Fixes#751.
This covers the case of loading a custom holder from a default-holder
instance. Attempting to load one custom holder from a different custom
holder (i.e. not `std::unique_ptr`) yields undefined behavior, just as
#588 established for inheritance.
@rwgkrwgk mentioned this pull request Feb 9, 2023
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@pschella@dean0x7d@wjakob