Author: Yuan Suo
Date: 2026-07-05T13:04:44+02:00
New Revision: 4fb7bf3323ea54d929f3846e6cd28d9dda1b891c

URL: 
https://github.com/llvm/llvm-project/commit/4fb7bf3323ea54d929f3846e6cd28d9dda1b891c
DIFF: 
https://github.com/llvm/llvm-project/commit/4fb7bf3323ea54d929f3846e6cd28d9dda1b891c.diff

LOG: [LifetimeSafety] Add multi-block support to buildOriginFlowChain (#204592)

After introducing `buildOriginFlowChain` to use-after-scope diagnostics,
it should support multi-block analysis. This also allows it to be reused
by other diagnostics.

In some loops, `UseFact` may appear before `OriginFlowFact`:

```cpp
void for_loop_use_before_loop_body(MyObj safe) {
  MyObj* p = &safe;
  for (int i = 0; i < 1; ++i) {
    (void)*p;
    MyObj s;
    p = &s;
  }
  (void)*p;
}
```

So I no longer use `StartPoint` as the initial search starting point; it
is only used to locate the corresponding block.

---------

Signed-off-by: Yuan Suo <[email protected]>

Added: 
    

Modified: 
    clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
    clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h
    clang/lib/Analysis/LifetimeSafety/Checker.cpp
    clang/lib/Analysis/LifetimeSafety/Facts.cpp
    clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
    clang/test/Sema/LifetimeSafety/safety-c.c
    clang/test/Sema/LifetimeSafety/safety.cpp
    clang/unittests/Analysis/LifetimeSafetyTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 8dccccc0f2257..94db2a7f311ae 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -385,6 +385,7 @@ class FactManager {
   /// Retrieves all the facts in the block containing Program Point P.
   /// \note This is intended for testing only.
   llvm::ArrayRef<const Fact *> getBlockContaining(ProgramPoint P) const;
+  size_t getBlockID(ProgramPoint P) const;
 
   unsigned getNumFacts() const { return NextFactID.Value; }
 

diff  --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h
index 724c6eee7d3c2..838daa024c953 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h
@@ -40,16 +40,21 @@ class LoanPropagationAnalysis {
 
   /// Builds the chain of origins through which a loan has propagated.
   ///
-  /// Starting from StartPoint where StartOID currently holds TargetLoan,
-  /// this function traces backwards through OriginFlowFacts to identify the
+  /// Starting from the last fact of the block containing StartPoint, this
+  /// function performs a DFS over CFG blocks to explore all reachable blocks.
+  /// Within each block, facts are processed in reverse order.
+  ///
+  /// The traversal follows OriginFlowFacts backwards to reconstruct the
   /// sequence of origins through which the loan flowed, ending at the origin
   /// where the loan was originally issued.
-  llvm::SmallVector<OriginID>
-  buildOriginFlowChain(ProgramPoint StartPoint, const OriginID StartOID,
-                       const LoanID TargetLoan) const;
+  llvm::SmallVector<OriginID> buildOriginFlowChain(ProgramPoint StartPoint,
+                                                   const OriginID StartOID,
+                                                   const LoanID TargetLoan,
+                                                   const CFG *Cfg) const;
 
-  llvm::SmallVector<OriginID>
-  buildOriginFlowChain(const UseFact *UF, const LoanID TargetLoan) const;
+  llvm::SmallVector<OriginID> buildOriginFlowChain(const UseFact *UF,
+                                                   const LoanID TargetLoan,
+                                                   const CFG *Cfg) const;
 
 private:
   class Impl;

diff  --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp 
b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 746ebbfb15c39..f72f7f80abbc0 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -67,6 +67,7 @@ class LifetimeChecker {
   FactManager &FactMgr;
   LifetimeSafetySemaHelper *SemaHelper;
   ASTContext &AST;
+  const CFG *Cfg;
   const Decl *FD;
   const LifetimeSafetyOpts &LSOpts;
 
@@ -92,7 +93,8 @@ class LifetimeChecker {
                   const LifetimeSafetyOpts &LSOpts)
       : LoanPropagation(LoanPropagation), MovedLoans(MovedLoans),
         LiveOrigins(LiveOrigins), FactMgr(FM), SemaHelper(SemaHelper),
-        AST(ADC.getASTContext()), FD(ADC.getDecl()), LSOpts(LSOpts) {
+        AST(ADC.getASTContext()), Cfg(ADC.getCFG()), FD(ADC.getDecl()),
+        LSOpts(LSOpts) {
     for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>())
       for (const Fact *F : FactMgr.getFacts(B))
         if (const auto *EF = F->getAs<ExpireFact>())
@@ -272,7 +274,7 @@ class LifetimeChecker {
           // Scope-based expiry (use-after-scope).
           SemaHelper->reportUseAfterScope(
               IssueExpr, UF->getUseExpr(), MovedExpr, ExpiryLoc,
-              getExprChain(LoanPropagation.buildOriginFlowChain(UF, LID)));
+              getExprChain(LoanPropagation.buildOriginFlowChain(UF, LID, 
Cfg)));
 
       } else if (const auto *OEF =
                      CausingFact.dyn_cast<const OriginEscapesFact *>()) {

diff  --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp 
b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index 15b666fbdf7ca..ec2d42e10206a 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -171,12 +171,14 @@ void FactManager::dump(const CFG &Cfg, 
AnalysisDeclContext &AC,
 
 llvm::ArrayRef<const Fact *>
 FactManager::getBlockContaining(ProgramPoint P) const {
-  for (const auto &BlockToFactsVec : BlockToFacts) {
-    for (const Fact *F : BlockToFactsVec)
-      if (F == P)
-        return BlockToFactsVec;
-  }
-  return {};
+  return BlockToFacts[getBlockID(P)];
 }
 
+size_t FactManager::getBlockID(ProgramPoint P) const {
+  for (size_t i = 0; i < BlockToFacts.size(); ++i)
+    for (const Fact *F : BlockToFacts[i])
+      if (F == P)
+        return i;
+  llvm_unreachable("Failed to find BlockID for given ProgramPoint");
+}
 } // namespace clang::lifetimes::internal

diff  --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp 
b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
index a67b1b3c0f826..078892bd48c10 100644
--- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
@@ -18,6 +18,7 @@
 #include "clang/Analysis/CFG.h"
 #include "clang/Basic/LLVM.h"
 #include "llvm/ADT/BitVector.h"
+#include "llvm/ADT/SmallSet.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/Support/TimeProfiler.h"
 #include "llvm/Support/raw_ostream.h"
@@ -202,53 +203,74 @@ class AnalysisImpl
     return getLoans(getState(P), OID);
   }
 
-  llvm::SmallVector<OriginID>
-  buildOriginFlowChain(ProgramPoint StartPoint, const OriginID StartOID,
-                       const LoanID TargetLoan) const {
+  llvm::SmallVector<OriginID> buildOriginFlowChain(ProgramPoint StartPoint,
+                                                   const OriginID StartOID,
+                                                   const LoanID TargetLoan,
+                                                   const CFG *Cfg) const {
     assert(getLoans(StartOID, StartPoint).contains(TargetLoan) &&
            "TargetLoan must be present in the StartOID at the StartPoint");
 
-    OriginID CurrOID = StartOID;
-    llvm::SmallVector<OriginID> OriginFlowChain;
-    llvm::ArrayRef<const Fact *> Facts = 
FactMgr.getBlockContaining(StartPoint);
-    const auto *StartIt = llvm::find(Facts, StartPoint);
-    assert(StartIt != Facts.end());
-
-    for (const Fact *F :
-         llvm::reverse(llvm::make_range(Facts.begin(), StartIt))) {
-      if (const auto *IF = F->getAs<IssueFact>())
-        if (IF->getLoanID() == TargetLoan) {
-          assert(IF->getOriginID() == CurrOID);
-          return OriginFlowChain;
-        }
+    // Locate the CFG block containing the StartPoint
+    const CFGBlock *EndBlock = nullptr;
+    size_t BlockID = FactMgr.getBlockID(StartPoint);
+    for (const CFGBlock *Block : *Cfg)
+      if (Block->getBlockID() == BlockID) {
+        EndBlock = Block;
+        break;
+      }
 
-      const auto *OFF = F->getAs<OriginFlowFact>();
-      if (!OFF)
-        continue;
-      if (OFF->getDestOriginID() != CurrOID)
-        continue;
+    // Set up DFS traversal state
+    // SearchState tracks which block we're in and which origin we're tracing
+    // Each DFSNode maintains its own OriginFlowChain.
+    using SearchState = std::pair<const CFGBlock *, OriginID>;
+    struct DFSNode {
+      SearchState CurrState;
+      llvm::SmallVector<OriginID> OriginFlowChain;
+    };
+
+    llvm::SmallVector<DFSNode> PendingStates;
+    llvm::SmallSet<SearchState, 16> VistedStates;
+    PendingStates.push_back({{EndBlock, StartOID}, {}});
+
+    // DFS loop to trace loan backwards through CFG
+    while (!PendingStates.empty()) {
+      DFSNode CurrNode = PendingStates.pop_back_val();
+      auto [CurrBlock, CurrOID] = CurrNode.CurrState;
+
+      // Trace origins within the current block
+      const auto [BuildResult, Complete] =
+          buildOriginFlowChain(CurrBlock, CurrOID, TargetLoan);
+      if (!BuildResult.empty()) {
+        CurrNode.OriginFlowChain.append(BuildResult);
+        CurrOID = BuildResult.back();
+      }
 
-      const OriginID SrcOriginID = OFF->getSrcOriginID();
-      if (!getLoans(SrcOriginID, OFF).contains(TargetLoan))
-        continue;
-      OriginFlowChain.push_back(SrcOriginID);
-      CurrOID = SrcOriginID;
+      // If we found the IssueFact, we're done
+      if (Complete)
+        return CurrNode.OriginFlowChain;
+
+      // Only explore predecessor blocks where the target loan is present in 
the
+      // current origin.
+      for (const CFGBlock *PredBlock : CurrBlock->preds()) {
+        SearchState NextState = {PredBlock, CurrOID};
+        if (getLoans(getOutState(PredBlock), CurrOID).contains(TargetLoan) &&
+            VistedStates.insert(NextState).second)
+          PendingStates.push_back({NextState, CurrNode.OriginFlowChain});
+      }
     }
 
-    // FIXME: Ideally, this return is unreachable and should be an assert
-    // because we expect to always finish at an IssueFact. But since current
-    // traversal is limited to a single CFG block, multi-block OriginFlowChain
-    // construction might miss the IssueFact. We should add llvm_unreachable
-    // here once multi-block support is implemented.
-    return {};
+    llvm_unreachable(
+        "buildOriginFlowChain did not reach IssueFact for TargetLoan");
   }
 
-  llvm::SmallVector<OriginID>
-  buildOriginFlowChain(const UseFact *UF, const LoanID TargetLoan) const {
+  llvm::SmallVector<OriginID> buildOriginFlowChain(const UseFact *UF,
+                                                   const LoanID TargetLoan,
+                                                   const CFG *Cfg) const {
     for (const OriginList *Cur = UF->getUsedOrigins(); Cur;
          Cur = Cur->peelOuterOrigin())
       if (getLoans(Cur->getOuterOriginID(), UF).contains(TargetLoan))
-        return buildOriginFlowChain(UF, Cur->getOuterOriginID(), TargetLoan);
+        return buildOriginFlowChain(UF, Cur->getOuterOriginID(), TargetLoan,
+                                    Cfg);
 
     return {};
   }
@@ -275,6 +297,40 @@ class AnalysisImpl
     return LoanSetFactory.getEmptySet();
   }
 
+  /// Builds the chain of origins through which a loan has propagated.
+  ///
+  /// This procedure operates strictly within a single Block. Starting from the
+  /// last fact of the Block, it traces backwards through OriginFlowFacts to
+  /// identify the sequence of origins through which the loan flowed.
+  ///
+  /// Returns (chain, true) if the target loan origin is found during the
+  /// traversal, otherwise returns (chain, false).
+  std::pair<llvm::SmallVector<OriginID>, bool>
+  buildOriginFlowChain(const CFGBlock *Block, const OriginID StartOID,
+                       const LoanID TargetLoan) const {
+    OriginID CurrOID = StartOID;
+    llvm::SmallVector<OriginID> OriginFlowChain;
+
+    for (const Fact *F : llvm::reverse(FactMgr.getFacts(Block))) {
+      if (const auto *IF = F->getAs<IssueFact>())
+        if (IF->getLoanID() == TargetLoan && IF->getOriginID() == CurrOID)
+          return {OriginFlowChain, true};
+
+      const auto *OFF = F->getAs<OriginFlowFact>();
+      if (!OFF || OFF->getDestOriginID() != CurrOID)
+        continue;
+
+      const OriginID SrcOriginID = OFF->getSrcOriginID();
+      if (!getLoans(SrcOriginID, OFF).contains(TargetLoan))
+        continue;
+
+      OriginFlowChain.push_back(SrcOriginID);
+      CurrOID = SrcOriginID;
+    }
+
+    return {OriginFlowChain, false};
+  }
+
   OriginLoanMap::Factory &OriginLoanMapFactory;
   LoanSet::Factory &LoanSetFactory;
   /// Boolean vector indexed by origin ID. If true, the origin appears in
@@ -303,16 +359,14 @@ LoanSet LoanPropagationAnalysis::getLoans(OriginID OID, 
ProgramPoint P) const {
   return PImpl->getLoans(OID, P);
 }
 
-llvm::SmallVector<OriginID>
-LoanPropagationAnalysis::buildOriginFlowChain(ProgramPoint StartPoint,
-                                              const OriginID StartOID,
-                                              const LoanID TargetLoan) const {
-  return PImpl->buildOriginFlowChain(StartPoint, StartOID, TargetLoan);
+llvm::SmallVector<OriginID> LoanPropagationAnalysis::buildOriginFlowChain(
+    ProgramPoint StartPoint, const OriginID StartOID, const LoanID TargetLoan,
+    const CFG *Cfg) const {
+  return PImpl->buildOriginFlowChain(StartPoint, StartOID, TargetLoan, Cfg);
 }
 
-llvm::SmallVector<OriginID>
-LoanPropagationAnalysis::buildOriginFlowChain(const UseFact *UF,
-                                              const LoanID TargetLoan) const {
-  return PImpl->buildOriginFlowChain(UF, TargetLoan);
+llvm::SmallVector<OriginID> LoanPropagationAnalysis::buildOriginFlowChain(
+    const UseFact *UF, const LoanID TargetLoan, const CFG *Cfg) const {
+  return PImpl->buildOriginFlowChain(UF, TargetLoan, Cfg);
 }
 } // namespace clang::lifetimes::internal

diff  --git a/clang/test/Sema/LifetimeSafety/safety-c.c 
b/clang/test/Sema/LifetimeSafety/safety-c.c
index e9443899c9935..42adbbb3f0628 100644
--- a/clang/test/Sema/LifetimeSafety/safety-c.c
+++ b/clang/test/Sema/LifetimeSafety/safety-c.c
@@ -93,7 +93,9 @@ void conditional_operator_lifetimebound(int cond) {
   int *p;
   {
     int a, b;
-    p = identity(cond ? &a    // expected-warning {{local variable 'a' does 
not live long enough}}
+    p = identity(cond ? &a    // expected-warning {{local variable 'a' does 
not live long enough}} \
+                              // expected-note {{result of call to 'identity' 
aliases the storage of local variable 'a'}} \
+                              // expected-note {{result of call to 'identity' 
aliases the storage of local variable 'b'}}
                       : &b);  // expected-warning {{local variable 'b' does 
not live long enough}}
   }                           // expected-note {{local variable 'a' is 
destroyed here}} \
                               // expected-note {{local variable 'b' is 
destroyed here}}

diff  --git a/clang/test/Sema/LifetimeSafety/safety.cpp 
b/clang/test/Sema/LifetimeSafety/safety.cpp
index 7362ca632b2e5..3c9b58c4e4957 100644
--- a/clang/test/Sema/LifetimeSafety/safety.cpp
+++ b/clang/test/Sema/LifetimeSafety/safety.cpp
@@ -236,7 +236,7 @@ void overrides_potential(bool cond) {
   {
     MyObj s;
     q = &s;       // expected-warning {{does not live long enough}}
-    p = q;
+    p = q;        // expected-note {{local variable 'q' aliases the storage of 
local variable 's'}}
   }               // expected-note {{local variable 's' is destroyed here}}
 
   if (cond) {
@@ -448,7 +448,7 @@ void loan_from_previous_iteration(MyObj safe, bool 
condition) {
     p = &x;     // expected-warning {{does not live long enough}}
 
     if (condition)
-      q = p;
+      q = p;    // expected-note {{local variable 'p' aliases the storage of 
local variable 'x'}}
     (void)*p;
     (void)*q;   // expected-note {{later used here}}
   }             // expected-note {{local variable 'x' is destroyed here}}
@@ -846,7 +846,8 @@ void lifetimebound_multiple_args_potential(bool cond) {
     MyObj obj1;
     if (cond) {
       MyObj obj2;
-      v = Choose(true,
+      v = Choose(true,             // expected-note {{result of call to 
'Choose' aliases the storage of local variable 'obj1'}} \
+                                   // expected-note {{result of call to 
'Choose' aliases the storage of local variable 'obj2'}}
                  obj1,             // expected-warning {{local variable 'obj1' 
does not live long enough}}
                  obj2);            // expected-warning {{local variable 'obj2' 
does not live long enough}}
     }                              // expected-note {{local variable 'obj2' is 
destroyed here}}
@@ -854,6 +855,18 @@ void lifetimebound_multiple_args_potential(bool cond) {
   v.use();                         // expected-note 2 {{later used here}}
 }
 
+// FIXME: Detect this.
+void func_pointer() {
+  View p;
+  View (*func_ptr)(View v [[clang::lifetimebound]]);
+  {
+   MyObj s;
+   View a = Identity(s);
+   p = func_ptr(a);
+  }
+  p.use();
+}
+
 View SelectFirst(View a [[clang::lifetimebound]], View b);
 void lifetimebound_mixed_args() {
   View v;
@@ -940,7 +953,7 @@ void lifetimebound_partial_safety(bool cond) {
   
   if (cond) {
     MyObj temp_obj;
-    v = Choose(true, 
+    v = Choose(true,      // expected-note {{result of call to 'Choose' 
aliases the storage of local variable 'temp_obj'}}
                safe_obj,
                temp_obj); // expected-warning {{local variable 'temp_obj' does 
not live long enough}}
   }                       // expected-note {{local variable 'temp_obj' is 
destroyed here}}
@@ -1223,7 +1236,9 @@ void conditional_operator_lifetimebound(bool cond) {
   MyObj* p;
   {
     MyObj a, b;
-    p = Identity(cond ? &a    // expected-warning {{local variable 'a' does 
not live long enough}}
+    p = Identity(cond ? &a    // expected-warning {{local variable 'a' does 
not live long enough}} \
+                              // expected-note {{result of call to 'Identity' 
aliases the storage of local variable 'a'}} \
+                              // expected-note {{result of call to 'Identity' 
aliases the storage of local variable 'b'}}
                       : &b);  // expected-warning {{local variable 'b' does 
not live long enough}}
   }  // expected-note {{local variable 'b' is destroyed here}} expected-note 
{{local variable 'a' is destroyed here}}
   (void)*p;  // expected-note 2 {{later used here}}
@@ -1233,8 +1248,11 @@ void conditional_operator_lifetimebound_nested(bool 
cond) {
   MyObj* p;
   {
     MyObj a, b;
-    p = Identity(cond ? Identity(&a)    // expected-warning {{local variable 
'a' does not live long enough}}
-                      : Identity(&b));  // expected-warning {{local variable 
'b' does not live long enough}}
+    p = Identity(cond ? Identity(&a)    // expected-warning {{local variable 
'a' does not live long enough}} \
+                                        // expected-note 2 {{result of call to 
'Identity' aliases the storage of local variable 'a'}} \
+                                        // expected-note {{result of call to 
'Identity' aliases the storage of local variable 'b'}}
+                      : Identity(&b));  // expected-warning {{local variable 
'b' does not live long enough}} \
+                                        // expected-note {{result of call to 
'Identity' aliases the storage of local variable 'b'}}
   }  // expected-note {{local variable 'b' is destroyed here}} expected-note 
{{local variable 'a' is destroyed here}}
   (void)*p;  // expected-note 2 {{later used here}}
 }
@@ -1243,9 +1261,15 @@ void conditional_operator_lifetimebound_nested_deep(bool 
cond) {
   MyObj* p;
   {
     MyObj a, b, c, d;
-    p = Identity(cond ? Identity(cond ? &a     // expected-warning {{local 
variable 'a' does not live long enough}}
+    p = Identity(cond ? Identity(cond ? &a     // expected-warning {{local 
variable 'a' does not live long enough}} \
+                                               // expected-note 2 {{result of 
call to 'Identity' aliases the storage of local variable 'a'}} \
+                                               // expected-note 2 {{result of 
call to 'Identity' aliases the storage of local variable 'b'}} \
+                                               // expected-note {{result of 
call to 'Identity' aliases the storage of local variable 'c'}} \
+                                               // expected-note {{result of 
call to 'Identity' aliases the storage of local variable 'd'}}
                                       : &b)    // expected-warning {{local 
variable 'b' does not live long enough}}
-                      : Identity(cond ? &c     // expected-warning {{local 
variable 'c' does not live long enough}}
+                      : Identity(cond ? &c     // expected-warning {{local 
variable 'c' does not live long enough}} \
+                                               // expected-note {{result of 
call to 'Identity' aliases the storage of local variable 'c'}} \
+                                               // expected-note {{result of 
call to 'Identity' aliases the storage of local variable 'd'}}
                                       : &d));  // expected-warning {{local 
variable 'd' does not live long enough}}
   }  // expected-note {{local variable 'a' is destroyed here}} expected-note 
{{local variable 'd' is destroyed here}} expected-note {{local variable 'b' is 
destroyed here}} expected-note {{local variable 'c' is destroyed here}}
   (void)*p;  // expected-note 4 {{later used here}}
@@ -1586,7 +1610,8 @@ void range_based_for_use_after_scope() {
   View v;
   {
     MyObjStorage s;
-    for (const MyObj &o : s) { // expected-warning {{local variable 's' does 
not live long enough}}
+    for (const MyObj &o : s) { // expected-warning {{local variable 's' does 
not live long enough}} \
+                               // expected-note {{local variable '__range2' 
aliases the storage of local variable 's'}}
       v = o;
     }
   } // expected-note {{local variable 's' is destroyed here}}
@@ -2772,7 +2797,7 @@ void chained_defaulted_assignment_propagation() {
     std::string str{"abc"};
     S a = getS(str); // expected-warning {{local variable 'str' does not live 
long enough}} \
                      // expected-note {{result of call to 'getS' aliases the 
storage of local variable 'str'}}
-    c = b = a;       // expected-note {{local variable 'a' aliases the storage 
of local variable 'str'}}\
+    c = b = a;       // expected-note {{local variable 'a' aliases the storage 
of local variable 'str'}} \
                      // expected-note {{expression aliases the storage of 
local variable 'str'}}
   }                  // expected-note {{local variable 'str' is destroyed 
here}}
   use(c);            // expected-note {{later used here}}
@@ -4132,3 +4157,116 @@ void test_loop_cond_bind(bool cond) {
     consume_loop_cond_bind(cond ? &x : &y); // no-warning
   }
 }
+
+//===----------------------------------------------------------------------===//
+// buildOriginFlowChain
+//===----------------------------------------------------------------------===//
+
+void used_variable_reassigned() {
+  View p, q, r;
+  {
+    MyObj a;
+    p = a; // expected-warning {{local variable 'a' does not live long enough}}
+    q = p; // expected-note {{local variable 'p' aliases the storage of local 
variable 'a'}}
+    r = q; // expected-note {{local variable 'q' aliases the storage of local 
variable 'a'}}
+  }        // expected-note {{destroyed here}}
+  r.use(); // expected-note {{later used here}}
+
+  MyObj b;
+  r = b;
+  r.use();
+}
+
+void multi_reassigned(bool condition) {
+  MyObj v1, v2, v3;
+  View p1, p2, p3, p4;
+  {
+    MyObj v4;
+
+    p1 = v1;
+    p2 = v2;
+    p3 = v3;
+    p4 = v4;    // expected-warning {{local variable 'v4' does not live long 
enough}}
+
+    while (condition) {
+      View temp = p1;
+      p1 = p2;  // expected-note {{local variable 'p2' aliases the storage of 
local variable 'v4'}}
+      p2 = p3;  // expected-note {{local variable 'p3' aliases the storage of 
local variable 'v4'}}
+      p3 = p4;  // expected-note {{local variable 'p4' aliases the storage of 
local variable 'v4'}}
+      p4 = temp;
+    }
+  }  // expected-note {{destroyed here}}
+
+  p1.use();  // expected-note {{later used here}}
+}
+
+#define BRANCH(con) \
+  if (con) {} else {}
+
+#define BRANCH10(con) \
+  BRANCH(con); \
+  BRANCH(con); \
+  BRANCH(con); \
+  BRANCH(con); \
+  BRANCH(con); \
+  BRANCH(con); \
+  BRANCH(con); \
+  BRANCH(con); \
+  BRANCH(con); \
+  BRANCH(con)
+
+#define BRANCH100(con) \
+  BRANCH10(con); \
+  BRANCH10(con); \
+  BRANCH10(con); \
+  BRANCH10(con); \
+  BRANCH10(con); \
+  BRANCH10(con); \
+  BRANCH10(con); \
+  BRANCH10(con); \
+  BRANCH10(con); \
+  BRANCH10(con)
+
+void test_exponential_paths(bool c) {
+  View v;
+  {
+    MyObj a;
+    View p = a;  // expected-warning{{local variable 'a' does not live long 
enough}}
+    BRANCH100(c);
+    v = p;  // expected-note {{local variable 'p' aliases the storage of local 
variable 'a'}}
+  }         // expected-note {{local variable 'a' is destroyed here}}
+  v.use();  // expected-note {{later used here}}
+}
+
+void test_multiple_paths(bool cond) {
+  View v;
+  {
+    MyObj a;
+    View p1, p2, p3;
+    p1 = a;     // expected-warning {{local variable 'a' does not live long 
enough}}
+    p2 = p1;
+    if (cond) {
+      p3 = p1;  // expected-note {{local variable 'p1' aliases the storage of 
local variable 'a'}}
+    } else {
+      p3 = p2;
+    }
+
+    v = p3; // expected-note {{local variable 'p3' aliases the storage of 
local variable 'a'}}
+  }         // expected-note {{local variable 'a' is destroyed here}}
+  v.use();  // expected-note {{later used here}}
+}
+
+void test_cyclic_cfg(int n) {
+  View v;
+  {
+    MyObj a;
+    View p;
+    p = a;  // expected-warning {{local variable 'a' does not live long 
enough}}
+    while (n > 0) {
+      p = p;
+      n--;
+    }
+    v = p;  // expected-note {{local variable 'p' aliases the storage of local 
variable 'a'}}
+  }         // expected-note {{local variable 'a' is destroyed here}}
+  v.use();  // expected-note {{later used here}}
+}

diff  --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp 
b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index 78b7449958140..57cf7068affae 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -206,16 +206,16 @@ class LifetimeTestHelper {
   }
 
   llvm::SmallVector<OriginID>
-  buildOriginFlowChainInOneBlock(llvm::StringRef StartOriginVar,
-                                 llvm::StringRef EndLoanVar,
-                                 llvm::StringRef Annotation) {
+  buildOriginFlowChain(llvm::StringRef StartOriginVar,
+                       llvm::StringRef EndLoanVar, llvm::StringRef Annotation) 
{
     std::optional<OriginID> StartOriginID = getOriginForDecl(StartOriginVar);
     std::vector<LoanID> EndLoanIDs = getLoansForVar(EndLoanVar);
 
     for (LoanID LID : EndLoanIDs) {
       llvm::SmallVector<OriginID> OriginFlowChain =
           Runner.getAnalysis().getLoanPropagation().buildOriginFlowChain(
-              getProgramPoint(Annotation), *StartOriginID, LID);
+              getProgramPoint(Annotation), *StartOriginID, LID,
+              Runner.getAnalysisContext().getCFG());
       if (!OriginFlowChain.empty())
         return OriginFlowChain;
     }
@@ -1975,6 +1975,52 @@ TEST_F(LifetimeAnalysisTest, 
LambdaInitCaptureViewByValue) {
 //                    Tests for buildOriginFlowChain
 // ========================================================================= //
 
+TEST_F(LifetimeAnalysisTest, BuildOriginFlowChain) {
+  SetupTest(R"(
+    void target(bool c1, bool c2) {
+      int *s;
+      int *a, *b, *c;
+
+      {
+        int tgta, tgtb, tgtc;
+        a = &tgta;
+        b = &tgtb;
+        c = &tgtc;
+      }
+
+      if (c1) {
+        s = c2 ? a : b;
+      } else {
+        s = c;
+      }
+
+      POINT(after_nested_merge);
+      (void)*s;
+      int reset;
+      s = &reset;
+    }
+  )");
+
+  llvm::SmallVector<OriginID> ChainForTgtA =
+      Helper->buildOriginFlowChain("s", "tgta", "after_nested_merge");
+  llvm::SmallVector<OriginID> ChainForTgtB =
+      Helper->buildOriginFlowChain("s", "tgtb", "after_nested_merge");
+  llvm::SmallVector<OriginID> ChainForTgtC =
+      Helper->buildOriginFlowChain("s", "tgtc", "after_nested_merge");
+
+  EXPECT_THAT(ChainForTgtA, Contains(*Helper->getOriginForDecl("a")));
+  EXPECT_THAT(ChainForTgtA, Not(Contains(*Helper->getOriginForDecl("b"))));
+  EXPECT_THAT(ChainForTgtA, Not(Contains(*Helper->getOriginForDecl("c"))));
+
+  EXPECT_THAT(ChainForTgtB, Not(Contains(*Helper->getOriginForDecl("a"))));
+  EXPECT_THAT(ChainForTgtB, Contains(*Helper->getOriginForDecl("b")));
+  EXPECT_THAT(ChainForTgtB, Not(Contains(*Helper->getOriginForDecl("c"))));
+
+  EXPECT_THAT(ChainForTgtC, Not(Contains(*Helper->getOriginForDecl("a"))));
+  EXPECT_THAT(ChainForTgtC, Not(Contains(*Helper->getOriginForDecl("b"))));
+  EXPECT_THAT(ChainForTgtC, Contains(*Helper->getOriginForDecl("c")));
+}
+
 TEST_F(LifetimeAnalysisTest, BuildOriginFlowChainWithErrorTargetLoan) {
   SetupTest(R"(
     void target() {
@@ -1986,7 +2032,7 @@ TEST_F(LifetimeAnalysisTest, 
BuildOriginFlowChainWithErrorTargetLoan) {
   )");
 
 #if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST
-  EXPECT_DEATH(Helper->buildOriginFlowChainInOneBlock("s", "a", "after_use"),
+  EXPECT_DEATH(Helper->buildOriginFlowChain("s", "a", "after_use"),
                "TargetLoan must be present in the StartOID at the StartPoint");
 #endif
 }
@@ -2005,7 +2051,7 @@ TEST_F(LifetimeAnalysisTest, 
BuildOriginFlowChainWithSelfAssignment) {
   )");
 
   const llvm::SmallVector<OriginID> OriginFlowChain =
-      Helper->buildOriginFlowChainInOneBlock("s", "tgt", "after_use");
+      Helper->buildOriginFlowChain("s", "tgt", "after_use");
 
   EXPECT_THAT(OriginFlowChain, Contains(*Helper->getOriginForDecl("a")));
 }
@@ -2022,7 +2068,7 @@ TEST_F(LifetimeAnalysisTest, 
BuildOriginFlowChainWithMultiAssignInSameStmt) {
   )");
 
   const llvm::SmallVector<OriginID> OriginFlowChain =
-      Helper->buildOriginFlowChainInOneBlock("s", "tgt", "after_use");
+      Helper->buildOriginFlowChain("s", "tgt", "after_use");
 
   EXPECT_THAT(OriginFlowChain, Contains(*Helper->getOriginForDecl("a")));
   EXPECT_THAT(OriginFlowChain, Contains(*Helper->getOriginForDecl("b")));
@@ -2043,7 +2089,7 @@ TEST_F(LifetimeAnalysisTest, 
BuildOriginFlowChainWithOverwritingAssignments) {
   )");
 
   const llvm::SmallVector<OriginID> OriginFlowChain =
-      Helper->buildOriginFlowChainInOneBlock("s", "tgt1", "after_use");
+      Helper->buildOriginFlowChain("s", "tgt1", "after_use");
 
   EXPECT_THAT(OriginFlowChain, Contains(*Helper->getOriginForDecl("a")));
   EXPECT_THAT(OriginFlowChain, Contains(*Helper->getOriginForDecl("b")));
@@ -2065,9 +2111,9 @@ TEST_F(LifetimeAnalysisTest, 
BuildOriginFlowChainWithLifetimeBound) {
   )");
 
   llvm::SmallVector<OriginID> ChainForTgtA =
-      Helper->buildOriginFlowChainInOneBlock("s", "tgta", "after_use");
+      Helper->buildOriginFlowChain("s", "tgta", "after_use");
   llvm::SmallVector<OriginID> ChainForTgtB =
-      Helper->buildOriginFlowChainInOneBlock("s", "tgtb", "after_use");
+      Helper->buildOriginFlowChain("s", "tgtb", "after_use");
 
   EXPECT_THAT(ChainForTgtA, Contains(*Helper->getOriginForDecl("a")));
   EXPECT_THAT(ChainForTgtA, Contains(*Helper->getOriginForDecl("result")));


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to