Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add check for matching holder_type when inheriting #588

Merged
merged 1 commit into from
Jan 31, 2017

Conversation

pschella
Copy link
Contributor

@pschella pschella commented Jan 4, 2017

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.

@pschella pschella changed the title Tickets/dm 8631 Add check for matching holder_type when inheriting Jan 4, 2017
@dean0x7d
Copy link
Member

dean0x7d commented Jan 8, 2017

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", []() {
    struct Base { };
    struct Derived : Base { };

    try {
        py::class_<Base>(m, "Base");
        py::class_<Derived, std::shared_ptr<Derived>, Base>(m, "Derived")
            .def(py::init<>());
    } catch (std::runtime_error &) {
        return true;
    }
    return false;
});
assert test_matching_holder_type() is True

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

template <typename... Ts>
struct void_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, typename SFINAE = void>
struct void_holder {
    using type = void;
}

template <typename...> using void_t = void;

template <template<typename...> class Holder, typename... Ts>
struct void_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
Member

wjakob commented Jan 8, 2017

+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
Contributor Author

pschella commented Jan 8, 2017

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
Contributor Author

pschella commented Jan 8, 2017

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

@pschella
Copy link
Contributor Author

pschella commented Jan 8, 2017

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

@pschella
Copy link
Contributor Author

pschella commented Jan 8, 2017

I split it up. Easier to debug.

/// 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
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?

py::class_<Base>(m, "Base");
py::class_<Derived, std::shared_ptr<Derived>, Base>(m, "Derived")
.def(py::init<>());
}
Copy link
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.

.def(py::init<>());
}
} catch (std::runtime_error &) {
return true;
Copy link
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", []() {
    struct Base { };
    struct Derived : Base { };

    py::class_<Base>(m, "Base");
    py::class_<Derived, std::shared_ptr<Derived>, Base>(m, "Derived");
});
with pytest.raises(RuntimeError) as excinfo:
    test_matching_holder_type()
assert str(excinfo.value) == "generic_type: ..."

@pschella
Copy link
Contributor Author

pschella commented Jan 9, 2017

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
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
Contributor Author

Changes implemented and commits squashed.

@wjakob
Copy link
Member

wjakob commented Jan 31, 2017

Looks great -- thank you!

@wjakob wjakob merged commit cc88aae into pybind:master Jan 31, 2017
dean0x7d added a commit to dean0x7d/pybind11 that referenced this pull request Mar 20, 2017
Instead of a segfault. Fixes pybind#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.
Sign up for free to 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