diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index d9fa068c2910f..4289b3374a810 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -220,6 +220,11 @@ Attribute Changes in Clang - ``[[clang::lifetimebound]]`` is now explicitly disallowed on explicit object member functions where they were previously silently ignored. +- Clang now automatically adds ``[[clang::lifetimebound]]`` to the parameters of + ``std::span, std::string_view`` constructors, this enables Clang to capture + more cases where the returned reference outlives the object. + (#GH100567) + Improvements to Clang's diagnostics ----------------------------------- diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 1f7e555d1b871..ed1c279fefe29 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -1806,6 +1806,9 @@ class Sema final : public SemaBase { /// Add [[gsl::Owner]] and [[gsl::Pointer]] attributes for std:: types. void inferGslOwnerPointerAttribute(CXXRecordDecl *Record); + /// Add [[clang:::lifetimebound]] attr for std:: functions and methods. + void inferLifetimeBoundAttribute(FunctionDecl *FD); + /// Add [[gsl::Pointer]] attributes for std:: types. void inferGslPointerAttribute(TypedefNameDecl *TD); diff --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp index a1724820472b5..fb594e6f13c0b 100644 --- a/clang/lib/Sema/SemaAttr.cpp +++ b/clang/lib/Sema/SemaAttr.cpp @@ -216,6 +216,59 @@ void Sema::inferGslOwnerPointerAttribute(CXXRecordDecl *Record) { inferGslPointerAttribute(Record, Record); } +void Sema::inferLifetimeBoundAttribute(FunctionDecl *FD) { + if (FD->getNumParams() == 0) + return; + + if (unsigned BuiltinID = FD->getBuiltinID()) { + // 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::BIforward_like: + 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; + } + return; + } + if (auto *CMD = dyn_cast(FD)) { + const auto *CRD = CMD->getParent(); + if (!CRD->isInStdNamespace() || !CRD->getIdentifier()) + return; + + if (isa(CMD)) { + auto *Param = CMD->getParamDecl(0); + if (Param->hasAttr()) + return; + if (CRD->getName() == "basic_string_view" && + Param->getType()->isPointerType()) { + // construct from a char array pointed by a pointer. + // basic_string_view(const CharT* s); + // basic_string_view(const CharT* s, size_type count); + Param->addAttr( + LifetimeBoundAttr::CreateImplicit(Context, FD->getLocation())); + } else if (CRD->getName() == "span") { + // construct from a reference of array. + // span(std::type_identity_t (&arr)[N]); + const auto *LRT = Param->getType()->getAs(); + if (LRT && LRT->getPointeeType().IgnoreParens()->isArrayType()) + Param->addAttr( + LifetimeBoundAttr::CreateImplicit(Context, FD->getLocation())); + } + } + } +} + void Sema::inferNullableClassAttribute(CXXRecordDecl *CRD) { static const llvm::StringSet<> Nullable{ "auto_ptr", "shared_ptr", "unique_ptr", "exception_ptr", diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index b0ccbbe34b70c..4efa80778e71b 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -16608,27 +16608,9 @@ 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::BIforward_like: - 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; - } } + inferLifetimeBoundAttribute(FD); AddKnownFunctionAttributesForReplaceableGlobalAllocationFunction(FD); // If C++ exceptions are enabled but we are told extern "C" functions cannot diff --git a/clang/test/SemaCXX/attr-lifetimebound.cpp b/clang/test/SemaCXX/attr-lifetimebound.cpp index f4ca840cb276f..6566ed6270cd1 100644 --- a/clang/test/SemaCXX/attr-lifetimebound.cpp +++ b/clang/test/SemaCXX/attr-lifetimebound.cpp @@ -238,6 +238,16 @@ template T *addressof(T &arg) { &const_cast(reinterpret_cast(arg))); } +template +struct basic_string_view { + basic_string_view(const T *); +}; + +template struct span { + template + span(const T (&__arr)[_ArrayExtent]) noexcept; +}; + } // namespace foo } // namespace std @@ -266,3 +276,14 @@ namespace move_forward_et_al_examples { S *AddressOfOk = std::addressof(X); } // namespace move_forward_et_al_examples +namespace ctor_cases { +std::basic_string_view test1() { + char abc[10]; + return abc; // expected-warning {{address of stack memory associated with local variable}} +} + +std::span test2() { + int abc[10]; + return abc; // expected-warning {{address of stack memory associated with local variable}} +} +} // namespace ctor_cases