Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::any across shared library bounding in mingw

I stumbled about an issue while using libstdc++'s std::any implementation with mingw across a shared library boundary. It produces a std::bad_any_cast where it obviously should not (i believe).

I use mingw-w64, gcc-7 and compile the code with -std=c++1z.

The simplified code:

main.cpp:

#include <any>
#include <string>

// prototype from lib.cpp
void do_stuff_with_any(const std::any& obj);

int main()
{
    do_stuff_with_any(std::string{"Hello World"});
}

lib.cpp:

Will be compiled into a shared library and linked with the executable from main.cpp.

#include <any>
#include <iostream>

void do_stuff_with_any(const std::any& obj)
{
    std::cout << std::any_cast<const std::string&>(obj) << "\n";
}

This triggers a std::bad_any_cast although the any passed to do_stuff_with_any does contain a string. I digged into gcc's any implementation and it seems to use comparison of the address of a static inline member function (a manager chosen from a template struct depending on the type of the stored object) to check if the any holds an object of the requested type. And the address of this function seems to change across the shared library boundary.

Isn't std::any guaranteed to work across shared library boundaries? Does this code trigger UB somewhere? Or is this a bug in the gcc implementation? I am pretty sure it works on linux so is this only a bug in mingw? Is it known or should i report it somewhere if so? Any ideas for (temporary) workarounds?

like image 245
nyorain Avatar asked Oct 18 '22 08:10

nyorain


1 Answers

While it is true that this is an issue on how Windows DLLs work, and that as of GCC 8.2.0, the issue still remains, this can be easily worked around by changing the __any_caster function inside the any header to this:

template<typename _Tp>
void* __any_caster(const any* __any)
{
  if constexpr (is_copy_constructible_v<decay_t<_Tp>>)
{
#if __cpp_rtti
  if (__any->type().hash_code() == typeid(_Tp).hash_code())
#else
  if (__any->_M_manager == &any::_Manager<decay_t<_Tp>>::_S_manage)
#endif
    {
      any::_Arg __arg;
      __any->_M_manager(any::_Op_access, __any, &__arg);
      return __arg._M_obj;
    }
}
  return nullptr;
}

Or something similar, the only relevant part is the comparison line wrapped in the #if.

To elaborate, there is 2 copies of the manager function one on the exe and one on the dll, the passed object contains the address of the exe because that's where it was created, but once it reaches the dll side, the pointer gets compared to the one in the dll address space, which will never match, so, instead type info hash_codes should be compared instead.

like image 117
Rodrigo Hernandez Avatar answered Oct 21 '22 09:10

Rodrigo Hernandez