|
| 1 | +// Demonstration of UB (Undefined Behavior) in handling of polymorphic pointers, |
| 2 | +// specifically: |
| 3 | +// https://github.com/pybind/pybind11/blob/30eb39ed79d1e2eeff15219ac00773034300a5e6/include/pybind11/cast.h#L229 |
| 4 | +// `return reinterpret_cast<V *&>(vh[0]);` |
| 5 | +// casts a `void` pointer to a `base`. The `void` pointer is obtained through |
| 6 | +// a `dynamic_cast` here: |
| 7 | +// https://github.com/pybind/pybind11/blob/30eb39ed79d1e2eeff15219ac00773034300a5e6/include/pybind11/cast.h#L852 |
| 8 | +// `return dynamic_cast<const void*>(src);` |
| 9 | +// The `dynamic_cast` is well-defined: |
| 10 | +// https://en.cppreference.com/w/cpp/language/dynamic_cast |
| 11 | +// 4) If expression is a pointer to a polymorphic type, and new-type |
| 12 | +// is a pointer to void, the result is a pointer to the most derived |
| 13 | +// object pointed or referenced by expression. |
| 14 | +// But the `reinterpret_cast` above is UB: `test_make_drvd_pass_base` in |
| 15 | +// `test_private_first_base.py` fails with a Segmentation Fault (Linux, |
| 16 | +// clang++ -std=c++17). |
| 17 | +// The only well-defined cast is back to a `drvd` pointer (`static_cast` can be |
| 18 | +// used), which can then safely be cast up to a `base` pointer. Note that |
| 19 | +// `test_make_drvd_up_cast_pass_drvd` passes because the `void` pointer is cast |
| 20 | +// to `drvd` pointer in this situation. |
| 21 | + |
| 22 | +#include "pybind11_tests.h" |
| 23 | + |
| 24 | +namespace pybind11_tests { |
| 25 | +namespace private_first_base { |
| 26 | + |
| 27 | +struct base { |
| 28 | + base() : base_id(100) {} |
| 29 | + virtual ~base() = default; |
| 30 | + virtual int id() const { return base_id; } |
| 31 | + base(const base&) = default; |
| 32 | + int base_id; |
| 33 | +}; |
| 34 | + |
| 35 | +struct private_first_base { // Any class with a virtual function will do. |
| 36 | + private_first_base() {} |
| 37 | + virtual void some_other_virtual_function() const {} |
| 38 | + virtual ~private_first_base() = default; |
| 39 | + private_first_base(const private_first_base&) = default; |
| 40 | +}; |
| 41 | + |
| 42 | +struct drvd : private private_first_base, public base { |
| 43 | + drvd() {} |
| 44 | + int id() const override { return 2 * base_id; } |
| 45 | +}; |
| 46 | + |
| 47 | +inline drvd* make_drvd() { return new drvd; } |
| 48 | +inline base* make_drvd_up_cast() { return new drvd; } |
| 49 | + |
| 50 | +inline int pass_base(const base* b) { return b->id(); } |
| 51 | +inline int pass_drvd(const drvd* d) { return d->id(); } |
| 52 | + |
| 53 | +TEST_SUBMODULE(private_first_base, m) { |
| 54 | + py::class_<base>(m, "base"); |
| 55 | + py::class_<drvd, base>(m, "drvd"); |
| 56 | + |
| 57 | + m.def("make_drvd", make_drvd, |
| 58 | + py::return_value_policy::take_ownership); |
| 59 | + m.def("make_drvd_up_cast", make_drvd_up_cast, |
| 60 | + py::return_value_policy::take_ownership); |
| 61 | + m.def("pass_base", pass_base); |
| 62 | + m.def("pass_drvd", pass_drvd); |
| 63 | +} |
| 64 | + |
| 65 | +} // namespace private_first_base |
| 66 | +} // namespace pybind11_tests |
0 commit comments