[clang] Propagate lifetimebound from formal parameters to those in the canonical declaration and use that for analysis (PR #107627)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Sep 6 13:34:36 PDT 2024
https://github.com/higher-performance updated https://github.com/llvm/llvm-project/pull/107627
>From edadbdc5844b1d99ce624e3c64dc3d519e2a1e51 Mon Sep 17 00:00:00 2001
From: higher-performance <higher.performance.github at gmail.com>
Date: Fri, 6 Sep 2024 14:16:15 -0400
Subject: [PATCH] Propagate lifetimebound from formal parameters to those in
the canonical declaration, then use the canonical declaration for analysis
Note that this doesn't handle the implicit 'this' parameter; that can be addressed in a separate commit.
---
clang/lib/Sema/CheckExprLifetime.cpp | 16 ++++++-----
clang/lib/Sema/SemaAttr.cpp | 34 +++++++++++++++--------
clang/test/SemaCXX/attr-lifetimebound.cpp | 4 +++
3 files changed, 35 insertions(+), 19 deletions(-)
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index f1507ebb9a5068..03e91f2628b290 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -462,14 +462,16 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
}
}
- for (unsigned I = 0,
- N = std::min<unsigned>(Callee->getNumParams(), Args.size());
- I != N; ++I) {
- if (CheckCoroCall || Callee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
- VisitLifetimeBoundArg(Callee->getParamDecl(I), Args[I]);
+ const FunctionDecl *CanonCallee = Callee->getCanonicalDecl();
+ const unsigned int NP =
+ std::min(Callee->getNumParams(), CanonCallee->getNumParams());
+ for (unsigned I = 0, N = std::min<unsigned>(NP, Args.size()); I != N; ++I) {
+ if (CheckCoroCall ||
+ CanonCallee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
+ VisitLifetimeBoundArg(CanonCallee->getParamDecl(I), Args[I]);
else if (EnableGSLAnalysis && I == 0) {
- if (shouldTrackFirstArgument(Callee)) {
- VisitGSLPointerArg(Callee, Args[0]);
+ if (shouldTrackFirstArgument(CanonCallee)) {
+ VisitGSLPointerArg(CanonCallee, Args[0]);
} else if (auto *CCE = dyn_cast<CXXConstructExpr>(Call);
CCE &&
CCE->getConstructor()->getParent()->hasAttr<PointerAttr>()) {
diff --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp
index fb594e6f13c0b3..37909c822b7588 100644
--- a/clang/lib/Sema/SemaAttr.cpp
+++ b/clang/lib/Sema/SemaAttr.cpp
@@ -217,7 +217,8 @@ void Sema::inferGslOwnerPointerAttribute(CXXRecordDecl *Record) {
}
void Sema::inferLifetimeBoundAttribute(FunctionDecl *FD) {
- if (FD->getNumParams() == 0)
+ const unsigned int NumParams = FD->getNumParams();
+ if (NumParams == 0)
return;
if (unsigned BuiltinID = FD->getBuiltinID()) {
@@ -239,18 +240,13 @@ void Sema::inferLifetimeBoundAttribute(FunctionDecl *FD) {
default:
break;
}
- return;
- }
- if (auto *CMD = dyn_cast<CXXMethodDecl>(FD)) {
- const auto *CRD = CMD->getParent();
- if (!CRD->isInStdNamespace() || !CRD->getIdentifier())
- return;
-
- if (isa<CXXConstructorDecl>(CMD)) {
+ } else if (auto *CMD = dyn_cast<CXXMethodDecl>(FD)) {
+ const CXXRecordDecl *CRD = CMD->getParent();
+ if (CRD->isInStdNamespace() && CRD->getIdentifier() &&
+ isa<CXXConstructorDecl>(CMD)) {
auto *Param = CMD->getParamDecl(0);
- if (Param->hasAttr<LifetimeBoundAttr>())
- return;
- if (CRD->getName() == "basic_string_view" &&
+ if (!Param->hasAttr<LifetimeBoundAttr>() &&
+ CRD->getName() == "basic_string_view" &&
Param->getType()->isPointerType()) {
// construct from a char array pointed by a pointer.
// basic_string_view(const CharT* s);
@@ -266,6 +262,20 @@ void Sema::inferLifetimeBoundAttribute(FunctionDecl *FD) {
LifetimeBoundAttr::CreateImplicit(Context, FD->getLocation()));
}
}
+ } else if (auto *CanonDecl = FD->getCanonicalDecl(); FD != CanonDecl) {
+ // Propagate the lifetimebound attribute from parameters to the canonical
+ // declaration.
+ // Note that this doesn't include the implicit 'this' parameter, as the
+ // attribute is applied to the function type in that case.
+ const unsigned int NP = std::min(NumParams, CanonDecl->getNumParams());
+ for (unsigned int I = 0; I < NP; ++I) {
+ auto *CanonParam = CanonDecl->getParamDecl(I);
+ if (!CanonParam->hasAttr<LifetimeBoundAttr>() &&
+ FD->getParamDecl(I)->hasAttr<LifetimeBoundAttr>()) {
+ CanonParam->addAttr(LifetimeBoundAttr::CreateImplicit(
+ Context, CanonParam->getLocation()));
+ }
+ }
}
}
diff --git a/clang/test/SemaCXX/attr-lifetimebound.cpp b/clang/test/SemaCXX/attr-lifetimebound.cpp
index 0fb997a5671085..c0255e9a9f7280 100644
--- a/clang/test/SemaCXX/attr-lifetimebound.cpp
+++ b/clang/test/SemaCXX/attr-lifetimebound.cpp
@@ -19,6 +19,9 @@ namespace usage_invalid {
namespace usage_ok {
struct IntRef { int *target; };
+ const int &crefparam(const int ¶m); // Omitted in first decl
+ const int &crefparam(const int ¶m [[clang::lifetimebound]]); // Add LB
+ const int &crefparam(const int ¶m) { return param; } // Omit in impl
int &refparam(int ¶m [[clang::lifetimebound]]);
int &classparam(IntRef param [[clang::lifetimebound]]);
@@ -41,6 +44,7 @@ namespace usage_ok {
int *p = A().class_member(); // expected-warning {{temporary whose address is used as value of local variable 'p' will be destroyed at the end of the full-expression}}
int *q = A(); // expected-warning {{temporary whose address is used as value of local variable 'q' will be destroyed at the end of the full-expression}}
int *r = A(1); // expected-warning {{temporary whose address is used as value of local variable 'r' will be destroyed at the end of the full-expression}}
+ const int& s = crefparam(2); // expected-warning {{temporary bound to local reference 's' will be destroyed at the end of the full-expression}}
void test_assignment() {
p = A().class_member(); // expected-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
More information about the cfe-commits
mailing list