[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
Mon Nov 18 08:48:03 PST 2024


https://github.com/higher-performance updated https://github.com/llvm/llvm-project/pull/107627

>From 581264c9ec198e98ff5f0f2c6e46073d79959a0b 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      | 15 +++++-----
 clang/lib/Sema/SemaAttr.cpp               | 34 +++++++++++++++--------
 clang/test/SemaCXX/attr-lifetimebound.cpp |  5 ++++
 3 files changed, 35 insertions(+), 19 deletions(-)

diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index a1a402b4a2b530..3d9858097f07dc 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -607,9 +607,9 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
     }
   }
 
-  for (unsigned I = 0,
-                N = std::min<unsigned>(Callee->getNumParams(), Args.size());
-       I != N; ++I) {
+  const FunctionDecl *CanonCallee = Callee->getCanonicalDecl();
+  unsigned NP = std::min(Callee->getNumParams(), CanonCallee->getNumParams());
+  for (unsigned I = 0, N = std::min<unsigned>(NP, Args.size()); I != N; ++I) {
     Expr *Arg = Args[I];
     RevertToOldSizeRAII RAII(Path);
     if (auto *DAE = dyn_cast<CXXDefaultArgExpr>(Arg)) {
@@ -617,12 +617,13 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
           {IndirectLocalPathEntry::DefaultArg, DAE, DAE->getParam()});
       Arg = DAE->getExpr();
     }
-    if (CheckCoroCall || Callee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
-      VisitLifetimeBoundArg(Callee->getParamDecl(I), Arg);
+    if (CheckCoroCall ||
+        CanonCallee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
+      VisitLifetimeBoundArg(CanonCallee->getParamDecl(I), Arg);
     else if (EnableGSLAnalysis && I == 0) {
       // Perform GSL analysis for the first argument
-      if (shouldTrackFirstArgument(Callee)) {
-        VisitGSLPointerArg(Callee, Arg);
+      if (shouldTrackFirstArgument(CanonCallee)) {
+        VisitGSLPointerArg(CanonCallee, Arg);
       } else if (auto *Ctor = dyn_cast<CXXConstructExpr>(Call);
                  Ctor && shouldTrackFirstArgumentForConstructor(Ctor)) {
         VisitGSLPointerArg(Ctor->getConstructor(), Arg);
diff --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp
index 9fbad7ed67ccbe..85274aee785a58 100644
--- a/clang/lib/Sema/SemaAttr.cpp
+++ b/clang/lib/Sema/SemaAttr.cpp
@@ -216,7 +216,8 @@ void Sema::inferGslOwnerPointerAttribute(CXXRecordDecl *Record) {
 }
 
 void Sema::inferLifetimeBoundAttribute(FunctionDecl *FD) {
-  if (FD->getNumParams() == 0)
+  unsigned NumParams = FD->getNumParams();
+  if (NumParams == 0)
     return;
 
   if (unsigned BuiltinID = FD->getBuiltinID()) {
@@ -238,18 +239,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);
@@ -265,6 +261,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.
+    unsigned NP = std::min(NumParams, CanonDecl->getNumParams());
+    for (unsigned 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 81e9193cf76a04..fb5a68f2497013 100644
--- a/clang/test/SemaCXX/attr-lifetimebound.cpp
+++ b/clang/test/SemaCXX/attr-lifetimebound.cpp
@@ -19,6 +19,10 @@ namespace usage_invalid {
 namespace usage_ok {
   struct IntRef { int *target; };
 
+  const int &crefparam(const int &param); // Omitted in first decl
+  const int &crefparam(const int &param); // Omitted in second decl
+  const int &crefparam(const int &param [[clang::lifetimebound]]); // Add LB
+  const int &crefparam(const int &param) { return param; } // Omit in impl
   int &refparam(int &param [[clang::lifetimebound]]);
   int &classparam(IntRef param [[clang::lifetimebound]]);
 
@@ -48,6 +52,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