[clang-tidy] std::addressof not checked by clang-analyzer-core.StackAddressEscape (original) (raw)

The code examples are all in C++23 (-std=c++23) and libc++ (-stdlib=libc++ -fexperimental-library).


Given a basic example:

#include #include #include

auto f1() { std::string a = "123"; return std::addressof(a); }

int main() { auto result = f1(); std::cout << *result; }

It isn't caught by clang-tidy, and instead ASan catches it in runtime:

==270312==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7f5177600020 at pc 0x557cec62fff6 bp 0x7ffcf239dcb0 sp 0x7ffcf239dca8

This is weird, given the fact that __builtin_addressof is checked by clang-tidy and (at least in libc++) std::addressof is simply a wrapper that calls __builtin_addressof.

Definition of std::addressof:

template inline _LIBCPP_CONSTEXPR_SINCE_CXX17 _LIBCPP_NO_CFI _LIBCPP_HIDE_FROM_ABI _Tp* addressof(_Tp& __x) _NOEXCEPT { return __builtin_addressof(__x); }

When changing the example to use __builtin_addressof:

auto f1() { std::string a = "123"; return __builtin_addressof(a); }

clang-tidy is able to figure out that it's returning a dangling pointer:

main.cpp:38: error: Address of stack memory associated with local variable 'a' returned to caller [clang-analyzer-core.StackAddressEscape,-warnings-as-errors]

This issue might be what caused std::reference_wrapper and std::ranges::ref_view to be unchecked, since (in libc++) they also used std::addressof in their constructors.

Example of faulty code utilizing std::ranges::ref_view:

#include #include #include

auto f2() { std::vector a{1, 2, 3}; return a | std::ranges::views::filter([](auto &&_) { return _ > 1; }); }

int main() { auto result = f2(); for (auto &&e : result) { std::cout << e; } }

clang-tidy doesn't catch it. ASan catches it (stack-use-after-return).

Example of faulty code utilizing std::reference_wrapper:

#include #include #include

auto f3() { std::string a = "123"; return std::ref(a); }

int main() { auto result = f3(); std::cout << result.get(); }

clang-tidy doesn't catch it. ASan catches it (stack-use-after-return).


More interestingly, if you implement your own reference wrapper that works similar to the one in libc++ by storing address, clang-tidy can detect the returning of dangling pointer/reference as long as std::addressof isn't used in constructor.

Definition of Ref:

template struct Ref { Ref(T &ref) : ptr(&ref) {} auto &get(this auto &&self) { return *self.ptr; } private: T *ptr; };

Faulty code utilizing Ref:

#include #include

auto f4() { std::string a = "123"; return Ref{a}; }

int main() { auto result = f4(); std::cout << result.get(); }

clang-tidy catches it:

main.cpp:28: error: Address of stack memory associated with local variable 'a' is still referred to by the stack variable 'result' upon returning to the caller.  This will be a dangling reference [clang-analyzer-core.StackAddressEscape,-warnings-as-errors]

But, when you change the constructor of Ref to use std::addressof(ref), clang-tidy can no longer detect it, which leads to me thinking the case with std::reference_wrapper and std::ranges::ref_view.


Version:

> clang-tidy-18 --version
Debian LLVM version 18.1.6
  Optimized build.
> clang++-18 --version
Debian clang version 18.1.6 (++20240518023138+1118c2e05e67-1~exp1~20240518143226.133)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin