-
Notifications
You must be signed in to change notification settings - Fork 15.3k
Description
The code examples are all in C++23 (-std=c++23) and libc++ (-stdlib=libc++ -fexperimental-library).
Given a basic example:
#include <iostream>
#include <memory>
#include <string>
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 <class _Tp>
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 <iostream>
#include <ranges>
#include <vector>
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 <iostream>
#include <functional>
#include <string>
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<typename T>
struct Ref
{
Ref(T &ref)
: ptr(&ref)
{}
auto &get(this auto &&self) { return *self.ptr; }
private:
T *ptr;
};Faulty code utilizing Ref:
#include <iostream>
#include <string>
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