Безопасность базы / производной с классом, загруженным из разделяемой библиотеки - PullRequest
0 голосов
/ 01 июля 2019

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

  void* handle = nullptr;

  if (!(handle = dlopen(path.c_str(), RTLD_LOCAL | RTLD_NOW))) {
    throw std::runtime_error("Failed to load library: " + path);
  }

  using allocClass = Plugin *(*)();
  using deleteClass = void (*)(Plugin *);


  auto allocFunc = reinterpret_cast<allocClass>(
    dlsym(handle, allocClassSymbol.c_str()));
  auto deleteFunc = reinterpret_cast<deleteClass>(
    dlsym(handle, deleteClassSymbol.c_str()));

  if (!allocFunc || !deleteFunc) {
    throw std::runtime_error("Allocator or deleter not found");
  }

  return std::shared_ptr<Plugin>(
    allocFunc(),
    [deleteFunc](Plugin *p){ deleteFunc(p); }
  );

Функции alloc / delete на стороне плагина в основном просто вызывают new или delete, например ::

extern "C" {
  TestPlugin *allocator() {
        return new TestPlugin();
    }

    void deleter(TestPlugin *ptr) {
        delete ptr;
    }
}

Мой вопрос в основном о безопасности этого несоответствия типов - плагин использует свой собственный тип, но загрузчик объявляет его в качестве базового типа. Из ограниченного тестирования ничего не получается , но я не уверен, что под капотом он удаляет часть памяти для части плагина, а не производную часть.

Есть ли лучшие способы сделать это без приложения, импортирующего заголовки каждого плагина?

1 Ответ

0 голосов
/ 02 июля 2019

Оставляя в силе переинтерпретацию void* к указателю на функцию, UB - это вызов функции через указатель другого типа. Неважно, связаны ли типы, например Plugin* (*)() и TestPlugin* (). Это также UB для доступа к объекту одного типа через указатель другого типа, , даже если эти типы являются производными и базовый класс соответственно . Например

Derived derived;
Base *base;
base = &derived; // OK, will access base subobject
base = reinterpret_cast<Base*>(&derived); // not OK, will access derived object

Ваш код вероятно будет работать, как ожидается, со многими (но, вероятно, не со всеми) популярными компиляторами, если задействовано только одно не виртуальное наследование. Однако нет необходимости брать на себя такие риски. Все это можно легко исправить, просто выставив библиотеку только на интерфейсах Plugin*.

extern "C" {
    Plugin *allocator() {
        return new TestPlugin();
    }

    void deleter(Plugin *ptr) {
        delete ptr;
    }
}

Эта реализация требует, чтобы плагин имел виртуальный деструктор. В любом случае, это хорошая идея. OTOH функция удаления на самом деле не нужна, клиент может просто вызвать delete Plugin. А еще лучше вернуть shared_ptr из allocator, возможно, с пользовательским удалением.

...