diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst index dbec1cd7c19667..2e99a6072a94df 100644 --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -2933,7 +2933,8 @@ implementation details of ``__sync_lock_test_and_set()``. The ``__builtin_addressof`` performs the functionality of the built-in ``&`` operator, ignoring any ``operator&`` overload. This is useful in constant expressions in C++11, where there is no other way to take the address of an -object that overloads ``operator&``. +object that overloads ``operator&``. Clang automatically adds +``[[clang::lifetimebound]]`` to the parameter of ``__builtin_addressof``. **Example of use**: diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 97ce8866d3ccf3..541f8e1eb671d0 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -458,6 +458,9 @@ Improvements to Clang's diagnostics only when ``x`` is a string literal. - Clang will now reject the GNU extension address of label in coroutines explicitly. This fixes `Issue 56436 `_. +- Clang now automatically adds ``[[clang::lifetimebound]]`` to the parameters of + ``std::move, std::forward`` et al, this enables Clang to diagnose more cases + where the returned reference outlives the object. Non-comprehensive list of changes in this release ------------------------------------------------- diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index faa75f671fde2d..3ae3e33a670410 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -16182,6 +16182,24 @@ void Sema::AddKnownFunctionAttributes(FunctionDecl *FD) { default: break; } + + // Add lifetime attribute to std::move, std::fowrard et al. + switch (BuiltinID) { + case Builtin::BIaddressof: + case Builtin::BI__addressof: + case Builtin::BI__builtin_addressof: + case Builtin::BIas_const: + case Builtin::BIforward: + case Builtin::BImove: + case Builtin::BImove_if_noexcept: + if (ParmVarDecl *P = FD->getParamDecl(0u); + !P->hasAttr()) + P->addAttr( + LifetimeBoundAttr::CreateImplicit(Context, FD->getLocation())); + break; + default: + break; + } } AddKnownFunctionAttributesForReplaceableGlobalAllocationFunction(FD); diff --git a/clang/test/SemaCXX/attr-lifetimebound.cpp b/clang/test/SemaCXX/attr-lifetimebound.cpp index 90124c323d02cb..22cf5d0ab00101 100644 --- a/clang/test/SemaCXX/attr-lifetimebound.cpp +++ b/clang/test/SemaCXX/attr-lifetimebound.cpp @@ -113,3 +113,77 @@ namespace p0936r0_examples { std::map m; const std::string &v = findOrDefault(m, "foo"s, "bar"s); // expected-warning {{temporary bound to local reference 'v'}} } + +// definitions for std::move, std::forward et al. +namespace std { +inline namespace foo { + +template struct remove_reference { + typedef T type; +}; +template struct remove_reference { + typedef T type; +}; +template struct remove_reference { + typedef T type; +}; + +template constexpr typename remove_reference::type &&move(T &&t) { + return static_cast::type>(t); +} + +template +constexpr T &&forward(typename remove_reference::type &t) { + return static_cast(t); +} + +template +constexpr T &&forward(typename remove_reference::type &&t) { + return static_cast(t); +} + +template constexpr const T &as_const(T &x) { return x; } + +template struct PickRef { + using type = typename remove_reference::type &; +}; +template struct PickRef { + using type = typename remove_reference::type &&; +}; + +template +auto move_if_noexcept(T &t) -> + typename PickRef(t)))>::type { + return static_cast< + typename PickRef(t)))>::type>(t); +} + +template T *addressof(T &arg) { + return reinterpret_cast( + &const_cast(reinterpret_cast(arg))); +} + +} // namespace foo +} // namespace std + +namespace move_forward_et_al_examples { + struct S { + S &self() [[clang::lifetimebound]] { return *this; } + }; + + S &&Move = std::move(S{}); // expected-warning {{temporary bound to local reference 'Move' will be destroyed at the end of the full-expression}} + S MoveOk = std::move(S{}); + + S &&Forward = std::forward(S{}); // expected-warning {{temporary bound to local reference 'Forward' will be destroyed at the end of the full-expression}} + S ForwardOk = std::forward(S{}); + + const S &Const = std::as_const(S{}.self()); // expected-warning {{temporary bound to local reference 'Const' will be destroyed at the end of the full-expression}} + const S ConstOk = std::as_const(S{}.self()); + + S &&MoveIfNoExcept = std::move_if_noexcept(S{}.self()); // expected-warning {{temporary bound to local reference 'MoveIfNoExcept' will be destroyed at the end of the full-expression}} + S MoveIfNoExceptOk = std::move_if_noexcept(S{}.self()); + + S *AddressOf = std::addressof(S{}.self()); // expected-warning {{temporary whose address is used as value of local variable 'AddressOf' will be destroyed at the end of the full-expression}} + S X; + S *AddressOfOk = std::addressof(X); +} // namespace move_forward_et_al_examples diff --git a/clang/test/SemaCXX/builtin-std-move.cpp b/clang/test/SemaCXX/builtin-std-move.cpp index eb2b85e8e7c7f6..8e4928da4edb03 100644 --- a/clang/test/SemaCXX/builtin-std-move.cpp +++ b/clang/test/SemaCXX/builtin-std-move.cpp @@ -85,7 +85,7 @@ static_assert(f({}), "should be constexpr"); A &forward_rval_as_lval() { std::forward(A()); // expected-warning {{const attribute}} - return std::forward(A()); // expected-note {{instantiation of}} + return std::forward(A()); // expected-note {{instantiation of}} expected-warning {{returning reference}} } struct B {}; diff --git a/clang/test/SemaCXX/builtins.cpp b/clang/test/SemaCXX/builtins.cpp index 3687acdeed695a..82d1820bf9f31b 100644 --- a/clang/test/SemaCXX/builtins.cpp +++ b/clang/test/SemaCXX/builtins.cpp @@ -40,7 +40,7 @@ namespace addressof { struct U { int n : 5; } u; int *pbf = __builtin_addressof(u.n); // expected-error {{address of bit-field requested}} - S *ptmp = __builtin_addressof(S{}); // expected-error {{taking the address of a temporary}} + S *ptmp = __builtin_addressof(S{}); // expected-error {{taking the address of a temporary}} expected-warning {{temporary whose address is used as value of local variable 'ptmp' will be destroyed at the end of the full-expression}} } namespace function_start {