Skip to content

Commit 6a82cfa

Browse files
yeganerjagerman
andcommitted
Detect virtual bases as non-simple types
We currently automatically detect multiple inheritance (when registered with pybind) to enable non-simple casting, but simple casting is also invalid when there is single-base, virtual inheritance (#1256). (This happens to work under gcc/clang, but makes MSVC segfault when upcasting). This commit adds detection for a specified base class being a virtual base, and if so, turns on the `multiple_inheritance` flag to mark the class and base as non-simple, and adds tests (which segfault under MSVC without this fix). (Earlier versions can work around the issue by explicitly passing a `py::multiple_inheritance{}` option to the `py::class_<...>` constructor). Co-authored-by: Jason Rhinelander <[email protected]>
1 parent add56cc commit 6a82cfa

File tree

5 files changed

+119
-0
lines changed

5 files changed

+119
-0
lines changed

include/pybind11/detail/common.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,9 @@ template <typename Base, typename Derived> using is_strict_base_of = bool_consta
592592
template <typename Base, typename Derived> using is_accessible_base_of = bool_constant<
593593
std::is_base_of<Base, Derived>::value && std::is_convertible<Derived *, Base *>::value>;
594594

595+
// Related to the above is also `is_virtual_base_of<Base, Derived>`; but it's provided in
596+
// `detail/is_virtual_base_of.h`
597+
595598
template <template<typename...> class Base>
596599
struct is_template_base_of_impl {
597600
template <typename... Us> static std::true_type check(Base<Us...> *);
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
pybind11/detail/virtual_base.h -- provides detail::is_virtual_base_of
3+
4+
Copyright (c) 2018 Jason Rhinelander <[email protected]>
5+
6+
All rights reserved. Use of this source code is governed by a
7+
BSD-style license that can be found in the LICENSE file.
8+
*/
9+
10+
#pragma once
11+
12+
#include "common.h"
13+
14+
// This metaprogramming template is in its own header because the only way to make gcc not generate
15+
// a warning for the approach used to detect virtual inheritance (which comes from boost) is to tell
16+
// GCC that this is a system header.
17+
18+
#if defined(_MSC_VER)
19+
# pragma warning(push)
20+
# pragma warning(disable: 4250) // warning C4250: 'X': inherits 'Y::member' via dominance
21+
# pragma warning(disable: 4584) // warning C4584: base-class 'X' is already a base-class of 'Y'
22+
# pragma warning(disable: 4594) // warning C4594: indirect virtual base class is inaccessible
23+
#elif defined(__GNUG__)
24+
// Lie to GCC that this is a system header so as to not generated the unconditional warning for the
25+
// inaccessible base caused by the implementation below when the base class is *not* virtual.
26+
# pragma GCC system_header
27+
#endif
28+
29+
NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
30+
NAMESPACE_BEGIN(detail)
31+
32+
template <typename Base, typename Derived, bool is_strict_base> struct is_virtual_base_of_impl {
33+
struct X : Derived, virtual Base { char data[1024]; };
34+
struct Y : Derived { char data[1024]; };
35+
constexpr static bool value = sizeof(X) == sizeof(Y);
36+
};
37+
template <typename Base, typename Derived> struct is_virtual_base_of_impl<Base, Derived, false> : std::false_type {};
38+
39+
/// is_virtual_base_of<Base, Derived>::value is true if and only if Base is a virtual base of Derived.
40+
template <typename Base, typename Derived>
41+
using is_virtual_base_of = bool_constant<is_virtual_base_of_impl<Base, Derived, is_strict_base_of<Base, Derived>::value>::value>;
42+
43+
NAMESPACE_END(detail)
44+
NAMESPACE_END(PYBIND11_NAMESPACE)
45+
46+
#if defined(_MSC_VER)
47+
# pragma warning(pop)
48+
#endif

include/pybind11/pybind11.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include "options.h"
4545
#include "detail/class.h"
4646
#include "detail/init.h"
47+
#include "detail/is_virtual_base_of.h"
4748

4849
NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
4950

@@ -1075,6 +1076,12 @@ class class_ : public detail::generic_type {
10751076
rec.add_base(typeid(Base), [](void *src) -> void * {
10761077
return static_cast<Base *>(reinterpret_cast<type *>(src));
10771078
});
1079+
1080+
// If the specified base class is a virtual base class, flip on the multiple inheritance
1081+
// flag. Even if, technically, multiple inheritance isn't actually involved, we still have
1082+
// to resort to the same non-simple casting to get to the base class.
1083+
if (detail::is_virtual_base_of<Base, type>::value)
1084+
rec.multiple_inheritance = true;
10781085
}
10791086

10801087
template <typename Base, detail::enable_if_t<!is_base<Base>::value, int> = 0>

tests/test_virtual_functions.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,53 @@ TEST_SUBMODULE(virtual_functions, m) {
289289
// .def("str_ref", &OverrideTest::str_ref)
290290
.def("A_value", &OverrideTest::A_value)
291291
.def("A_ref", &OverrideTest::A_ref);
292+
293+
// test_virtual_inheritance:
294+
// Interface class, not meant to be instantiated
295+
class Interface1 {
296+
public:
297+
virtual ~Interface1() = default;
298+
virtual int run(Interface1*) = 0;
299+
virtual int number() = 0;
300+
};
301+
class Final: public virtual Interface1 {
302+
public:
303+
virtual int run(Interface1* ptr) { return ptr->number() + 1; }
304+
virtual int number() { return 5; }
305+
};
306+
307+
// Duplicate of the above (but we want a distinct type for the test)
308+
class Interface2 {
309+
public:
310+
virtual ~Interface2() = default;
311+
virtual int run(Interface2*) = 0;
312+
virtual int number() = 0;
313+
};
314+
315+
class B_Concrete : public virtual Interface2 {
316+
public:
317+
virtual int run(Interface2* ptr) { return ptr->number() + 2; }
318+
};
319+
class C_Concrete : public virtual Interface2 {
320+
public:
321+
virtual int number() { return 2; }
322+
};
323+
324+
#if defined(_MSC_VER)
325+
# pragma warning(disable: 4250) // warning C4250 'Derived' inherits 'Base::method' via dominance
326+
#endif
327+
class Diamond : public C_Concrete, public B_Concrete { };
328+
329+
py::class_<Interface1>(m, "Interface1").def("run", &Interface1::run);
330+
py::class_<Final, Interface1>(m, "Final").def(py::init<>());
331+
332+
py::class_<Interface2>(m, "Interface2").def("run", &Interface2::run);
333+
py::class_<B_Concrete, Interface2>(m, "B_Concrete");
334+
py::class_<C_Concrete, Interface2>(m, "C_Concrete");
335+
py::class_<Diamond, B_Concrete, C_Concrete>(m, "Diamond").def(py::init<>());
336+
337+
m.def("run_virtual_inheritance", []() { Diamond d; return d.run(&d); },
338+
"Runs the diamond test in c++");
292339
}
293340

294341

tests/test_virtual_functions.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,3 +369,17 @@ def lucky_number(self):
369369
assert obj.unlucky_number() == -7
370370
assert obj.lucky_number() == -1.375
371371
assert obj.say_everything() == "BT -7"
372+
373+
374+
def test_virtual_inheritance():
375+
"""Issue #1256 - single inheritance from a virtual base class breaks upcasting"""
376+
# This test only seems to fail (via segfault) under MSVC, but it's not an MSVC problem: we were
377+
# previously erroneously treating a virtual base class as simple inheritance, but it is not.
378+
final = m.Final()
379+
assert final.run(final) == 6
380+
381+
# The diamond inheritance case here is similar, but wasn't actually an issue: pybind seeing the
382+
# multiple inheritance already triggers the non-simple inheritance path:
383+
diamond = m.Diamond()
384+
assert diamond.run(diamond) == 4
385+
assert m.run_virtual_inheritance() == 4

0 commit comments

Comments
 (0)