test_private_first_base.cpp 2.39 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Demonstration of UB (Undefined Behavior) in handling of polymorphic pointers,
// specifically:
// https://github.com/pybind/pybind11/blob/30eb39ed79d1e2eeff15219ac00773034300a5e6/include/pybind11/cast.h#L229
//     `return reinterpret_cast<V *&>(vh[0]);`
// casts a `void` pointer to a `base`. The `void` pointer is obtained through
// a `dynamic_cast` here:
// https://github.com/pybind/pybind11/blob/30eb39ed79d1e2eeff15219ac00773034300a5e6/include/pybind11/cast.h#L852
//     `return dynamic_cast<const void*>(src);`
// The `dynamic_cast` is well-defined:
// https://en.cppreference.com/w/cpp/language/dynamic_cast
//     4) If expression is a pointer to a polymorphic type, and new-type
//        is a pointer to void, the result is a pointer to the most derived
//        object pointed or referenced by expression.
// But the `reinterpret_cast` above is UB: `test_make_drvd_pass_base` in
// `test_private_first_base.py` fails with a Segmentation Fault (Linux,
// clang++ -std=c++17).
// The only well-defined cast is back to a `drvd` pointer (`static_cast` can be
// used), which can then safely be cast up to a `base` pointer. Note that
// `test_make_drvd_up_cast_pass_drvd` passes because the `void` pointer is cast
// to `drvd` pointer in this situation.

#include "pybind11_tests.h"

namespace pybind11_tests {
namespace private_first_base {

struct base {
  base() : base_id(100) {}
  virtual ~base() = default;
  virtual int id() const { return base_id; }
31
  base(const base&) = delete;
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
  int base_id;
};

struct private_first_base {  // Any class with a virtual function will do.
  virtual void some_other_virtual_function() const {}
  virtual ~private_first_base() = default;
};

struct drvd : private private_first_base, public base {
  int id() const override { return 2 * base_id; }
};

inline drvd* make_drvd() { return new drvd; }
inline base* make_drvd_up_cast() { return new drvd; }

inline int pass_base(const base* b) { return b->id(); }
inline int pass_drvd(const drvd* d) { return d->id(); }

TEST_SUBMODULE(private_first_base, m) {
  py::class_<base>(m, "base");
  py::class_<drvd, base>(m, "drvd");

  m.def("make_drvd", make_drvd,
        py::return_value_policy::take_ownership);
  m.def("make_drvd_up_cast", make_drvd_up_cast,
        py::return_value_policy::take_ownership);
  m.def("pass_base", pass_base);
  m.def("pass_drvd", pass_drvd);
}

}  // namespace private_first_base
}  // namespace pybind11_tests