RTTI + dlopen + GCC = баг

Submitted by 0xd34df00d on Fri, 01/16/2009 - 15:09

Если грузить библиотеку, юзающую какой-либо класс, через dlopen, то слияния объектов type_info не произойдет, поэтому имеем забавное поведение, а лично я поимел три часа секса от обнаружения сломанного dynamic_cast до написания тесткейса.

classb.h:

  1. struct a
  2. {
  3. virtual ~a() {}
  4. };
  5.  
  6. struct b : public a
  7. {
  8. virtual ~b() {}
  9. };

test2.cpp:

  1. #include <typeinfo>
  2.  
  3. #include "classb.h"
  4.  
  5. extern "C"
  6. {
  7. a* getnewb() { return new b; }
  8. const std::type_info& gettypeb() { return typeid (b); }
  9. }

test.cpp:

  1. #include <dlfcn.h>
  2. #include <typeinfo>
  3. #include <iostream>
  4. #include <cassert>
  5.  
  6. #include "classb.h"
  7.  
  8. typedef a *(*func_t) (void);
  9.  
  10. int main()
  11. {
  12. func_t func;
  13. const std::type_info& (*type) (void);
  14.  
  15. void *handle = dlopen ("./test2.so", RTLD_NOW);
  16. func = (func_t) dlsym (handle, "getnewb");
  17.  
  18. type = (const std::type_info& (*) (void)) dlsym (handle, "gettypeb");
  19.  
  20. a *a_ptr = (*func) ();
  21. const std::type_info& t = (*type)();
  22.  
  23. assert (dynamic_cast<b*> (a_ptr));
  24. assert (typeid (b) == t);
  25. }

Компилять:

  1. g++ -g -fPIC -o test2.so -shared test2.cpp
  2. g++ -g -o testcase test.cpp -ldl

UPD: Если чуть подробнее, type_info::operator== на линугзах (ну и везде, где есть поддержка weak symbols) в общем случае сравнивает указатели на строки с типами вместо честного strcmp по самим строкам. В обычных условиях все type_info для физически одинаковых объектов изменяются так, чтобы указатели указывали (разработчики разрабатывают) на одну и ту же строку, это и есть слияние (merging). Если грузить библиотеку через dlopen, как в примере, то этого не происходит, поэтому получаем интересное поведение, как, например, assertion failed в примере.

Однако, мне не очень понятно, почему функция-член объекта, создающая его клон, определенная в чужой библиотеке и вызываемая в моем коде, не избавляет от всех проблем. Возможно, инлайнится, надо еще об этом подумать.