diff --git a/include/nanobind/nb_class.h b/include/nanobind/nb_class.h index 4120f43f..ec4bc013 100644 --- a/include/nanobind/nb_class.h +++ b/include/nanobind/nb_class.h @@ -340,6 +340,16 @@ class class_ : public object { sizeof...(Ts) == !std::is_same_v + !std::is_same_v, "nanobind::class_<> was invoked with extra arguments that could not be handled"); + // Fail on virtual bases -- they need a this-ptr adjustment, but they're + // not amenable to the runtime test in the class_ constructor (because + // a C-style cast will do reinterpret_cast if static_cast is invalid). + // Primary but inaccessible (ambiguous or private) bases will also + // fail is_accessible_static_base_of; the !is_convertible option is to + // avoid mis-detecting them as virtual bases. + static_assert(!std::is_convertible_v || + detail::is_accessible_static_base_of::value, + "nanobind does not support virtual base classes"); + template NB_INLINE class_(handle scope, const char *name, const Extra &... extra) { detail::type_init_data d; @@ -354,6 +364,13 @@ class class_ : public object { if constexpr (!std::is_same_v) { d.base = &typeid(Base); d.flags |= (uint32_t) detail::type_init_flags::has_base; + + if (uintptr_t offset = (uintptr_t) (Base*) (T*) 0x1000 - 0x1000) { + detail::raise("nanobind::class_<>: base class %s is at offset " + "%td within %s! nanobind does not support " + "multiple inheritance", typeid(Base).name(), + offset, typeid(T).name()); + } } if constexpr (!std::is_same_v) diff --git a/include/nanobind/nb_traits.h b/include/nanobind/nb_traits.h index 66ea2105..83601d3f 100644 --- a/include/nanobind/nb_traits.h +++ b/include/nanobind/nb_traits.h @@ -132,6 +132,14 @@ struct detector>, Op, Arg> avoid redundancy when combined with nb::arg(...).none(). */ template struct remove_opt_mono { using type = T; }; +template +struct is_accessible_static_base_of { + template + static decltype(static_cast(std::declval()), std::true_type()) check(B*); + static std::false_type check(...); + static constexpr bool value = decltype(check((Base*)nullptr))::value; +}; + NAMESPACE_END(detail) template diff --git a/tests/test_classes.cpp b/tests/test_classes.cpp index e54e8261..adcd7c19 100644 --- a/tests/test_classes.cpp +++ b/tests/test_classes.cpp @@ -477,4 +477,23 @@ NB_MODULE(test_classes_ext, m) { m.def("polymorphic_factory_2", []() { return (PolymorphicBase *) new AnotherPolymorphicSubclass(); }); m.def("factory", []() { return (Base *) new Subclass(); }); m.def("factory_2", []() { return (Base *) new AnotherSubclass(); }); + + // Test detection of unsupported base/derived relationships + // (nanobind requires base subobjects to be at offset zero in the + // derived class) + struct DerivedBecomesPolymorphic : Struct { + virtual ~DerivedBecomesPolymorphic() {} + }; + struct DerivedNonPrimary : Big, Struct {}; + struct DerivedVirt : virtual Struct {}; + m.def("bind_newly_polymorphic_subclass", []() { + nb::class_( + nb::handle(), "DerivedBecomesPolymorphic"); + }); + m.def("bind_subclass_with_non_primary_base", []() { + nb::class_(nb::handle(), "DerivedNonPrimary"); + }); + static_assert(!nb::detail::is_accessible_static_base_of::value); + // this fails at compile time: + //nb::class_(nb::handle(), "DerivedVirt"); } diff --git a/tests/test_classes.py b/tests/test_classes.py index 6fc27ca8..d4305e14 100644 --- a/tests/test_classes.py +++ b/tests/test_classes.py @@ -631,3 +631,10 @@ def name(self): assert t.go(d2) == 'Rufus says woof' finally: t.Dog.name = old + + +def test35_non_primary(): + with pytest.raises(RuntimeError, match="not support multiple inheritance"): + t.bind_newly_polymorphic_subclass() + with pytest.raises(RuntimeError, match="not support multiple inheritance"): + t.bind_subclass_with_non_primary_base()