boost te memory access failure with visual c++

63 Views Asked by At

I am looking at using the boost te lib in a project compiled with visual c++ (MSVC 19.29.30153.0 with c++ standard 20). I take the simpilest example erase it which is essentially:

struct Drawable {
  void draw(std::ostream &out) const {
    te::call([](auto const &self, auto &out) { self.draw(out); }, *this, out);
  }
};

struct Square {
  void draw(std::ostream &out) const { out << "Square"; }
};

int main() {
  te::poly<Drawable> d = Square{};
  d.draw();
}

This compiles, but at runtime fails at this point in te::poly:

  return reinterpret_cast<R (*)(void *, Ts...)>(self.vptr[N - 1])(
      self.ptr(), std::forward<Ts>(args)...);

With a read access violation:

Exception thrown: read access violation. self was 0xFFFFFFFFFFFFFFFF.

If i add in a line to watch the variable:

auto my_pointer = self.ptr();

It does indeed fail to call this, and on inspecting the variables:

self    {vptr=0x00007ff6698614e2 {check_te_windows.exe! ...
+       __vfptr 0x00000142775a64a0 {0x000000fdfdfdfdcd} void * *
+       vptr    0x00007ff6698614e2 

The vtable pointer does appear to be broken. This library has worked for me in clang, gcc and intels oneapi compiler. Here is a gcc live example: https://godbolt.org/z/boKaY19oj

Does anyone know what the problem might be on visual c++ compiler?

1

There are 1 best solutions below

8
sehe On

In the class template poly the constructor initializer list shows the initializer for vtable before storage, but storage is still initialized first, because of declaration order:

  constexpr explicit poly(T &&t, std::index_sequence<Ns...>) noexcept(std::is_nothrow_constructible_v<T_,T&&>)
      : detail::poly_base{},
        vtable{std::forward<T>(t), vptr,
               std::integral_constant<std::size_t, sizeof...(Ns)>{}},
        storage{std::forward<T>(t)} {
    static_assert(sizeof...(Ns) > 0);
    static_assert(std::is_destructible_v<T_>, "type must be desctructible");
    static_assert(std::is_copy_constructible_v<T_> ||
                  std::is_move_constructible_v<T_>,
                  "type must be either copyable or moveable");
    (init<Ns + 1, std::decay_t<T> >(
         decltype(get(detail::mappings<I, Ns + 1>{})){}),
     ...);
  }

That's suspect, but what's even more suspect is the double std::forward which may invite a double-move from the t argument. In your sample, this suspect constructor is indeed invoked with the following template parameters:

constexpr boost::ext::te::v1::poly<I, TStorage, TVtable>::poly(T&&, std::index_sequence<Ns ...>);
// with T = Square;
// T_ = Square
// long unsigned int ...Ns = {0}
// I = Drawable
// TStorage = boost::ext::te::v1::dynamic_storage
// TVtable = boost::ext::te::v1::static_vtable
// std::index_sequence<Ns ...> = std::integer_sequence<long unsigned int, 0>

Instrumenting the Square class to prohibit the move make it not compile: https://godbolt.org/z/v4bjYf18b

IDEA 1

Based on these observations, you might think to avoid rvalue-arguments (your temporaries): https://godbolt.org/z/cMhd6Pa4h (of course, no problem on GCC, but check on MSVC?)

int main() {
    Circle c;
    Square s;
    draw(c); // prints Circle
    draw(s); // prints Square
}

IDEA 2

You might flip the declaration order of the poly members from:

TStorage storage;
TVtable vtable;

to:

TVtable vtable;
TStorage storage;

Of course, this changes layout and depending on nefarious practices might cause other things to change behaviour.

Summary, Pointers

Regardless, I'd suggest raising the issue with the TE devs.

Also consider using Boost Type Erasure, which is properly part of Boost: https://www.boost.org/doc/libs/1_84_0/doc/html/boost_typeerasure.html