Skip to content

[clang-tidy] std::addressof not checked by clang-analyzer-core.StackAddressEscape #94193

@dfrvfmst

Description

@dfrvfmst

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions