Clang 分析器潜在的内存泄漏 - 误报

Clang analyzer potential memory leak - false positive

我的代码库中有一个静态函数的实现,当 运行 clang-tidy 在上面时,我注意到当我非常确定代码时静态分析器指向可能的内存泄漏是正确的。 (我已经用消毒剂验证过了)。我认为这很可能是由于静态分析器缺少一些分支语句,但我不是 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);
}

这里是整洁的分析:

/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.

Here 是 godbolt link,启用了 clang-tidy。

clang-tidy 的报告确实有点奇怪,需要澄清一下。

对于 Inner<T> 的 placement-new 没有看到匹配的显式析构函数调用感到不安。你有这个奇怪的 destroy() 方法,它甚至不是必需的,因为 Base 析构函数是虚拟的,隐式 Inner 析构函数将清理 Inner::f_.

这可以通过以下方式轻松解决:

  1. bool SmallFunction::empty替换为Base *SmallFunction::value并将placement-new的结果存入其中。 (这不是绝对必要的,但我发现不需要 reinterpret_cast 的代码流动性更好,而且由于编译器可以进行类型检查,因此更容易正确。)
  2. SmallFunction::~SmallFunction 中,将 destroy 调用替换为 value->~Base()
  3. 移除destroy()方法;不需要。

这满足 clang-tidy (see here).

我认为没有内存泄漏,但有一个对象(Inner<T>)被构造并且从未被破坏。我看不出有什么后果,但以正确的方式做事并没有什么坏处——无论如何,它使静态分析器的工作更容易。