Потенциальная утечка памяти анализатора Clang - ложное срабатывание - PullRequest
1 голос
/ 29 мая 2020

У меня есть реализация функции stati c в моей кодовой базе, и при запуске clang-tidy на ней я заметил, что анализатор stati c указывает на возможную утечку памяти, когда я почти уверен, что код правильный . (Я проверил это с помощью дезинфицирующих средств). Я думаю, что это, скорее всего, из-за того, что в анализаторах stati c отсутствует какой-то оператор ветвления, но я не уверен на 100%.

Вот сокращенная версия кода:

#include <array>
#include <functional>
#include <utility>

struct SmallFunction {
  struct Base {
    virtual ~Base() = default;
    virtual void destroy() = 0;
  };

  template <typename T>
  struct Inner : Base {
    Inner(T&& f) : f_(std::move(f)) {}
    void destroy() override { f_.~T(); }
    T f_;
  };

  template <typename T>
  SmallFunction(T&& f) : empty(false) {
    static_assert(sizeof(T) <= 32);
    new (storage) Inner<T>(std::forward<T>(f));
  }

  ~SmallFunction() {
    if (!empty) {
      reinterpret_cast<Base*>(storage)->destroy();
    }
  }

  bool empty = true;
  alignas(8) char storage[40];  // 32 + 8
};

int main() {
  std::array<char, 64> large;
  auto lambda = [large] {};
  std::function<void()> f = lambda;
  SmallFunction sf = std::move(f);
}

Вот анализ clang-tidy:

/home/ce/example.cpp:39:1: warning: Potential memory leak [clang-analyzer-cplusplus.NewDeleteLeaks]
}
^
/home/ce/example.cpp:37:29: note: Calling constructor for 'function<void ()>'
  std::function<void()> f = lambda;
                            ^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/bits/std_function.h:675:2: note: Taking true branch
        if (_My_handler::_M_not_empty_function(__f))
        ^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/bits/std_function.h:677:6: note: Calling '_Base_manager::_M_init_functor'
            _My_handler::_M_init_functor(_M_functor, std::move(__f));
            ^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/bits/std_function.h:223:4: note: Calling '_Base_manager::_M_init_functor'
        { _M_init_functor(__functor, std::move(__f), _Local_storage()); }
          ^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/bits/std_function.h:252:39: note: Memory is allocated
        { __functor._M_access<_Functor*>() = new _Functor(std::move(__f)); }
                                             ^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/bits/std_function.h:223:4: note: Returned allocated memory
        { _M_init_functor(__functor, std::move(__f), _Local_storage()); }
          ^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/bits/std_function.h:677:6: note: Returned allocated memory
            _My_handler::_M_init_functor(_M_functor, std::move(__f));
            ^
/home/ce/example.cpp:37:29: note: Returning from constructor for 'function<void ()>'
  std::function<void()> f = lambda;
                            ^
/home/ce/example.cpp:39:1: note: Potential memory leak
}
^
1 warning generated.

Здесь - Godbolt ссылка с включенным clang-tidy.

1 Ответ

3 голосов
/ 29 мая 2020

Отчет clang-tidy определенно немного странный и требует некоторых пояснений.

Он расстроен по поводу нового размещения Inner<T>, не увидев соответствующего явного вызова деструктора. У вас есть этот странный метод destroy(), в котором даже нет необходимости, поскольку деструктор Base является виртуальным, а неявный деструктор Inner очистит Inner::f_.

Это тривиально исправляется следующими способами :

  1. Замените bool SmallFunction::empty на Base *SmallFunction::value и сохраните в нем результат размещения new. (В этом нет строгой необходимости, но я считаю, что код работает лучше, не требуя reinterpret_cast, и это легче сделать, поскольку компилятор может проверять типы.)
  2. В SmallFunction::~SmallFunction замените destroy вызов с помощью value->~Base().
  3. Удалить метод destroy(); это не нужно.

Это удовлетворяет clang-tidy ( см. здесь ).

Я не думаю, что была утечка памяти, но была объект (Inner<T>), который был построен и никогда не разрушался. Я не вижу никаких последствий, но это не мешает делать все правильно - и в любом случае это облегчает работу анализаторов stati c.

...