Skip to content

Conversation

usx95
Copy link
Contributor

@usx95 usx95 commented Aug 14, 2025

This PR fixes a bug in the lifetime safety analysis where ImplicitCastExpr nodes were causing duplicate loan generation. The changes:

  1. Remove the recursive Visit(ICE->getSubExpr()) call in VisitImplicitCastExpr to prevent duplicate processing of the same expression
  2. Ensure the CFG build options are properly configured for lifetime safety analysis by moving the flag check earlier
  3. Enhance the unit test infrastructure to properly handle multiple loans per variable
  4. Add a test case that verifies implicit casts to const don't create duplicate loans
  5. Add a test case for ternary operators with a FIXME note about origin propagation

These changes prevent the analysis from generating duplicate loans when expressions are wrapped in implicit casts, which improves the accuracy of the lifetime safety analysis.

Copy link
Contributor Author

usx95 commented Aug 14, 2025

@usx95 usx95 force-pushed the users/usx95/08-14-_lifetimesafety_track_view_types_gsl_pointer branch from e61817a to 43d28dc Compare August 14, 2025 20:07
@usx95 usx95 force-pushed the users/usx95/08-14-_lifetimesafety_track_view_types_gsl_pointer branch from 43d28dc to 1877937 Compare August 14, 2025 20:33
Copy link

github-actions bot commented Aug 14, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@usx95 usx95 force-pushed the users/usx95/08-14-_lifetimesafety_track_view_types_gsl_pointer branch from 1877937 to f3ff755 Compare August 14, 2025 21:16
@usx95 usx95 changed the title [LifetimeSafety] Track view types/gsl::Pointer. [LifetimeSafety] Prevent duplicate loans and statement visits Aug 14, 2025
@usx95 usx95 force-pushed the users/usx95/08-14-_lifetimesafety_track_view_types_gsl_pointer branch from f3ff755 to cdc9849 Compare August 14, 2025 21:17
@usx95 usx95 force-pushed the users/usx95/07-20-basic_error_report_for_use_after_free branch from f590db6 to 894fa21 Compare August 14, 2025 21:17
@usx95 usx95 requested review from Xazax-hun and ymand August 14, 2025 21:19
@usx95 usx95 force-pushed the users/usx95/08-14-_lifetimesafety_track_view_types_gsl_pointer branch from cdc9849 to 3bb7fca Compare August 14, 2025 21:20
@usx95 usx95 marked this pull request as ready for review August 14, 2025 21:21
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:analysis labels Aug 14, 2025
@llvmbot
Copy link
Member

llvmbot commented Aug 14, 2025

@llvm/pr-subscribers-clang-analysis
@llvm/pr-subscribers-clang-temporal-safety

Author: Utkarsh Saxena (usx95)

Changes

This change adds a VisitedStmts set to avoid processing the same statement multiple times (specially through VisitImplicitCastExpr) leading to issuing multiple loans for the same expression!

Fixes #153592


Full diff: https://github.com/llvm/llvm-project/pull/153661.diff

2 Files Affected:

  • (modified) clang/lib/Analysis/LifetimeSafety.cpp (+8)
  • (modified) clang/unittests/Analysis/LifetimeSafetyTest.cpp (+32-21)
diff --git a/clang/lib/Analysis/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp
index c762f63c45e09..86d9517dde45c 100644
--- a/clang/lib/Analysis/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety.cpp
@@ -396,6 +396,7 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
     // initializations and destructions are processed in the correct sequence.
     for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) {
       CurrentBlockFacts.clear();
+      VisitedStmts.clear();
       for (unsigned I = 0; I < Block->size(); ++I) {
         const CFGElement &Element = Block->Elements[I];
         if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
@@ -408,6 +409,12 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
     }
   }
 
+  void Visit(const Stmt *S) {
+    if (!S || !VisitedStmts.insert(S).second)
+      return;
+    Base::Visit(S);
+  }
+
   void VisitDeclStmt(const DeclStmt *DS) {
     for (const Decl *D : DS->decls())
       if (const auto *VD = dyn_cast<VarDecl>(D))
@@ -551,6 +558,7 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
   FactManager &FactMgr;
   AnalysisDeclContext &AC;
   llvm::SmallVector<Fact *> CurrentBlockFacts;
+  llvm::DenseSet<const Stmt *> VisitedStmts;
 };
 
 // ========================================================================= //
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index c8d88b4ea2277..13e5832d70050 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -11,6 +11,7 @@
 #include "clang/ASTMatchers/ASTMatchers.h"
 #include "clang/Testing/TestAST.h"
 #include "llvm/ADT/StringMap.h"
+#include "llvm/Testing/Support/Error.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include <optional>
@@ -20,6 +21,7 @@ namespace clang::lifetimes::internal {
 namespace {
 
 using namespace ast_matchers;
+using ::testing::SizeIs;
 using ::testing::UnorderedElementsAreArray;
 
 // A helper class to run the full lifetime analysis on a piece of code
@@ -96,21 +98,18 @@ class LifetimeTestHelper {
     return OID;
   }
 
-  std::optional<LoanID> getLoanForVar(llvm::StringRef VarName) {
+  std::vector<LoanID> getLoansForVar(llvm::StringRef VarName) {
     auto *VD = findDecl<VarDecl>(VarName);
-    if (!VD)
-      return std::nullopt;
+    if (!VD) {
+      ADD_FAILURE() << "No VarDecl found for '" << VarName << "'";
+      return {};
+    }
     std::vector<LoanID> LID = Analysis.getLoanIDForVar(VD);
     if (LID.empty()) {
       ADD_FAILURE() << "Loan for '" << VarName << "' not found.";
-      return std::nullopt;
-    }
-    // TODO: Support retrieving more than one loans to a var.
-    if (LID.size() > 1) {
-      ADD_FAILURE() << "More than 1 loans found for '" << VarName;
-      return std::nullopt;
+      return {};
     }
-    return LID[0];
+    return LID;
   }
 
   std::optional<LoanSet> getLoansAtPoint(OriginID OID,
@@ -121,13 +120,12 @@ class LifetimeTestHelper {
     return Analysis.getLoansAtPoint(OID, PP);
   }
 
-  std::optional<llvm::DenseSet<LoanID>>
+  std::optional<std::vector<LoanID>>
   getExpiredLoansAtPoint(llvm::StringRef Annotation) {
     ProgramPoint PP = Runner.getProgramPoint(Annotation);
     if (!PP)
       return std::nullopt;
-    auto Expired = Analysis.getExpiredLoansAtPoint(PP);
-    return llvm::DenseSet<LoanID>{Expired.begin(), Expired.end()};
+    return Analysis.getExpiredLoansAtPoint(PP);
   }
 
 private:
@@ -197,12 +195,13 @@ MATCHER_P2(HasLoansToImpl, LoanVars, Annotation, "") {
 
   std::vector<LoanID> ExpectedLoans;
   for (const auto &LoanVar : LoanVars) {
-    std::optional<LoanID> ExpectedLIDOpt = Info.Helper.getLoanForVar(LoanVar);
-    if (!ExpectedLIDOpt) {
+    std::vector<LoanID> ExpectedLIDs = Info.Helper.getLoansForVar(LoanVar);
+    if (ExpectedLIDs.empty()) {
       *result_listener << "could not find loan for var '" << LoanVar << "'";
       return false;
     }
-    ExpectedLoans.push_back(*ExpectedLIDOpt);
+    ExpectedLoans.insert(ExpectedLoans.end(), ExpectedLIDs.begin(),
+                         ExpectedLIDs.end());
   }
 
   return ExplainMatchResult(UnorderedElementsAreArray(ExpectedLoans),
@@ -221,17 +220,17 @@ MATCHER_P(AreExpiredAt, Annotation, "") {
                      << Annotation << "'";
     return false;
   }
-  std::vector<LoanID> ActualExpiredLoans(ActualExpiredSetOpt->begin(),
-                                         ActualExpiredSetOpt->end());
+  std::vector<LoanID> ActualExpiredLoans = *ActualExpiredSetOpt;
   std::vector<LoanID> ExpectedExpiredLoans;
   for (const auto &VarName : Info.LoanVars) {
-    auto LoanIDOpt = Helper.getLoanForVar(VarName);
-    if (!LoanIDOpt) {
+    auto LoanIDs = Helper.getLoansForVar(VarName);
+    if (LoanIDs.empty()) {
       *result_listener << "could not find a loan for variable '" << VarName
                        << "'";
       return false;
     }
-    ExpectedExpiredLoans.push_back(*LoanIDOpt);
+    ExpectedExpiredLoans.insert(ExpectedExpiredLoans.end(), LoanIDs.begin(),
+                                LoanIDs.end());
   }
   return ExplainMatchResult(UnorderedElementsAreArray(ExpectedExpiredLoans),
                             ActualExpiredLoans, result_listener);
@@ -730,5 +729,17 @@ TEST_F(LifetimeAnalysisTest, ReassignedPointerThenOriginalExpires) {
   EXPECT_THAT(LoansTo({"s1", "s2"}), AreExpiredAt("p_after_s1_expires"));
 }
 
+TEST_F(LifetimeAnalysisTest, NoDuplicateLoansForImplicitCastToConst) {
+  SetupTest(R"(
+    void target() {
+      MyObj a;
+      const MyObj* p = &a;
+      const MyObj* q = &a;
+      POINT(at_end);
+    }
+  )");
+  EXPECT_THAT(Helper->getLoansForVar("a"), SizeIs(2));
+}
+
 } // anonymous namespace
 } // namespace clang::lifetimes::internal

@llvmbot
Copy link
Member

llvmbot commented Aug 14, 2025

@llvm/pr-subscribers-clang

Author: Utkarsh Saxena (usx95)

Changes

This change adds a VisitedStmts set to avoid processing the same statement multiple times (specially through VisitImplicitCastExpr) leading to issuing multiple loans for the same expression!

Fixes #153592


Full diff: https://github.com/llvm/llvm-project/pull/153661.diff

2 Files Affected:

  • (modified) clang/lib/Analysis/LifetimeSafety.cpp (+8)
  • (modified) clang/unittests/Analysis/LifetimeSafetyTest.cpp (+32-21)
diff --git a/clang/lib/Analysis/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp
index c762f63c45e09..86d9517dde45c 100644
--- a/clang/lib/Analysis/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety.cpp
@@ -396,6 +396,7 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
     // initializations and destructions are processed in the correct sequence.
     for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) {
       CurrentBlockFacts.clear();
+      VisitedStmts.clear();
       for (unsigned I = 0; I < Block->size(); ++I) {
         const CFGElement &Element = Block->Elements[I];
         if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
@@ -408,6 +409,12 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
     }
   }
 
+  void Visit(const Stmt *S) {
+    if (!S || !VisitedStmts.insert(S).second)
+      return;
+    Base::Visit(S);
+  }
+
   void VisitDeclStmt(const DeclStmt *DS) {
     for (const Decl *D : DS->decls())
       if (const auto *VD = dyn_cast<VarDecl>(D))
@@ -551,6 +558,7 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
   FactManager &FactMgr;
   AnalysisDeclContext &AC;
   llvm::SmallVector<Fact *> CurrentBlockFacts;
+  llvm::DenseSet<const Stmt *> VisitedStmts;
 };
 
 // ========================================================================= //
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index c8d88b4ea2277..13e5832d70050 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -11,6 +11,7 @@
 #include "clang/ASTMatchers/ASTMatchers.h"
 #include "clang/Testing/TestAST.h"
 #include "llvm/ADT/StringMap.h"
+#include "llvm/Testing/Support/Error.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include <optional>
@@ -20,6 +21,7 @@ namespace clang::lifetimes::internal {
 namespace {
 
 using namespace ast_matchers;
+using ::testing::SizeIs;
 using ::testing::UnorderedElementsAreArray;
 
 // A helper class to run the full lifetime analysis on a piece of code
@@ -96,21 +98,18 @@ class LifetimeTestHelper {
     return OID;
   }
 
-  std::optional<LoanID> getLoanForVar(llvm::StringRef VarName) {
+  std::vector<LoanID> getLoansForVar(llvm::StringRef VarName) {
     auto *VD = findDecl<VarDecl>(VarName);
-    if (!VD)
-      return std::nullopt;
+    if (!VD) {
+      ADD_FAILURE() << "No VarDecl found for '" << VarName << "'";
+      return {};
+    }
     std::vector<LoanID> LID = Analysis.getLoanIDForVar(VD);
     if (LID.empty()) {
       ADD_FAILURE() << "Loan for '" << VarName << "' not found.";
-      return std::nullopt;
-    }
-    // TODO: Support retrieving more than one loans to a var.
-    if (LID.size() > 1) {
-      ADD_FAILURE() << "More than 1 loans found for '" << VarName;
-      return std::nullopt;
+      return {};
     }
-    return LID[0];
+    return LID;
   }
 
   std::optional<LoanSet> getLoansAtPoint(OriginID OID,
@@ -121,13 +120,12 @@ class LifetimeTestHelper {
     return Analysis.getLoansAtPoint(OID, PP);
   }
 
-  std::optional<llvm::DenseSet<LoanID>>
+  std::optional<std::vector<LoanID>>
   getExpiredLoansAtPoint(llvm::StringRef Annotation) {
     ProgramPoint PP = Runner.getProgramPoint(Annotation);
     if (!PP)
       return std::nullopt;
-    auto Expired = Analysis.getExpiredLoansAtPoint(PP);
-    return llvm::DenseSet<LoanID>{Expired.begin(), Expired.end()};
+    return Analysis.getExpiredLoansAtPoint(PP);
   }
 
 private:
@@ -197,12 +195,13 @@ MATCHER_P2(HasLoansToImpl, LoanVars, Annotation, "") {
 
   std::vector<LoanID> ExpectedLoans;
   for (const auto &LoanVar : LoanVars) {
-    std::optional<LoanID> ExpectedLIDOpt = Info.Helper.getLoanForVar(LoanVar);
-    if (!ExpectedLIDOpt) {
+    std::vector<LoanID> ExpectedLIDs = Info.Helper.getLoansForVar(LoanVar);
+    if (ExpectedLIDs.empty()) {
       *result_listener << "could not find loan for var '" << LoanVar << "'";
       return false;
     }
-    ExpectedLoans.push_back(*ExpectedLIDOpt);
+    ExpectedLoans.insert(ExpectedLoans.end(), ExpectedLIDs.begin(),
+                         ExpectedLIDs.end());
   }
 
   return ExplainMatchResult(UnorderedElementsAreArray(ExpectedLoans),
@@ -221,17 +220,17 @@ MATCHER_P(AreExpiredAt, Annotation, "") {
                      << Annotation << "'";
     return false;
   }
-  std::vector<LoanID> ActualExpiredLoans(ActualExpiredSetOpt->begin(),
-                                         ActualExpiredSetOpt->end());
+  std::vector<LoanID> ActualExpiredLoans = *ActualExpiredSetOpt;
   std::vector<LoanID> ExpectedExpiredLoans;
   for (const auto &VarName : Info.LoanVars) {
-    auto LoanIDOpt = Helper.getLoanForVar(VarName);
-    if (!LoanIDOpt) {
+    auto LoanIDs = Helper.getLoansForVar(VarName);
+    if (LoanIDs.empty()) {
       *result_listener << "could not find a loan for variable '" << VarName
                        << "'";
       return false;
     }
-    ExpectedExpiredLoans.push_back(*LoanIDOpt);
+    ExpectedExpiredLoans.insert(ExpectedExpiredLoans.end(), LoanIDs.begin(),
+                                LoanIDs.end());
   }
   return ExplainMatchResult(UnorderedElementsAreArray(ExpectedExpiredLoans),
                             ActualExpiredLoans, result_listener);
@@ -730,5 +729,17 @@ TEST_F(LifetimeAnalysisTest, ReassignedPointerThenOriginalExpires) {
   EXPECT_THAT(LoansTo({"s1", "s2"}), AreExpiredAt("p_after_s1_expires"));
 }
 
+TEST_F(LifetimeAnalysisTest, NoDuplicateLoansForImplicitCastToConst) {
+  SetupTest(R"(
+    void target() {
+      MyObj a;
+      const MyObj* p = &a;
+      const MyObj* q = &a;
+      POINT(at_end);
+    }
+  )");
+  EXPECT_THAT(Helper->getLoansForVar("a"), SizeIs(2));
+}
+
 } // anonymous namespace
 } // namespace clang::lifetimes::internal

Copy link
Collaborator

@Xazax-hun Xazax-hun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if this is the right approach. If everything works out well, every time we call Visit on an expression, there should be a guarantee we already visited all the subexpressions of it (modulo some corner cases with short circuiting operators, ternaries and trivially false branches). So, we might be able to structure the code in a way that we never need to call Visit recursively for a subexpression, and we do not need to keep a VisitedStmts set.

But in case that does not work out for some reason I am also fine with this approach.

@usx95 usx95 force-pushed the users/usx95/08-14-_lifetimesafety_track_view_types_gsl_pointer branch from 3bb7fca to 546483e Compare August 18, 2025 10:30
Base automatically changed from users/usx95/07-20-basic_error_report_for_use_after_free to main August 18, 2025 11:46
@usx95 usx95 force-pushed the users/usx95/08-14-_lifetimesafety_track_view_types_gsl_pointer branch from 546483e to f11f942 Compare August 18, 2025 11:48
Copy link
Contributor Author

usx95 commented Aug 18, 2025

If everything works out well, every time we call Visit on an expression, there should be a guarantee we already visited all the subexpressions of it

Yes this is the ideal scenario. But we visit the statements given by the CFG and this can, more often than not, skip many sub expressions. I don't have a great solution. An alternate could be to use a Recursive AST visitor (RAV) along that drives our FactGenerator stmt visitor. The RAV would still need to maintain a Visited set but FactGenerator visitor can now assume that all subexpressions have been visited without needing for each Visit* Method in FactGenerator to explicitly revisit subexpressions. WDYT about this alternative of using a RAV driver ?

@Xazax-hun
Copy link
Collaborator

The RAV would still need to maintain a Visited set

Could you elaborate on why?

WDYT about this alternative of using a RAV driver ?

Hmm, there is a change that this could be a bit simpler than the current solution. If you can give this a try to see where does that lead it would be awesome.

@usx95 usx95 force-pushed the users/usx95/08-14-_lifetimesafety_track_view_types_gsl_pointer branch 2 times, most recently from 5ccc210 to dbb99ed Compare August 18, 2025 15:56
Copy link
Contributor Author

usx95 commented Aug 18, 2025

The RAV would still need to maintain a Visited set

Could you elaborate on why?

The CFG can contain sub expressions as individual CFG statements. Later parent expressions can again appear as independent CFGStmt. It is not guaranteed that all subexpressions of all expr types appear as CFGStmt. So we recursively visit each CFGStmt and skip already visited Stmt which might have appeared before in CFG.

@usx95 usx95 requested a review from Xazax-hun August 18, 2025 15:59
@usx95 usx95 force-pushed the users/usx95/08-14-_lifetimesafety_track_view_types_gsl_pointer branch from 3b3b93b to 440c3fe Compare August 19, 2025 12:16
@Xazax-hun
Copy link
Collaborator

Thanks! I am not actually sure if this one is simpler than the original version. Sorry about that. I don't have a strong feeling which one to pick. I guess the complexity here is an inevitable consequence of our CFGs being just an overlay on top of the AST and it is not entirely clear when should we use one or the other for traversals because it depends on what we are processing.

Let me know which one do you prefer (this or the previous), and I am happy to approve either of them.

Copy link
Contributor Author

usx95 commented Aug 19, 2025

This does introduce an extra layer of orchestration but I like the current approach. The FactGeneratorVisitor can now safely assume that all sub expressions must have been visited and does not have to case-by-case handle it during each Visit* methods. I think this becomes more and more important as we begin to handle more expression types.

I guess the complexity here is an inevitable consequence of our CFGs being just an overlay on top of the AST and it is not entirely clear when should we use one or the other for traversals because it depends on what we are processing.

+1

@Xazax-hun
Copy link
Collaborator

Thanks!

I have one more question. So we need the reverse post order property of the CFG to ensure that we visit everything in the right order. The only reason why I am a bit unsure here, is because I wonder if it can ever cause some trouble that the RAV visiting the subexpressions is crossing a basic block boundary and venturing into that block too early, before all of its predecessors have been visited.

If this cannot happen, I think the current approach should be fine.

@usx95 usx95 added the clang:temporal-safety Issue/FR relating to the lifetime analysis in Clang (-Wdangling, -Wreturn-local-addr) label Aug 22, 2025
@usx95 usx95 force-pushed the users/usx95/08-14-_lifetimesafety_track_view_types_gsl_pointer branch from 440c3fe to 577d963 Compare September 2, 2025 13:00
Copy link
Contributor Author

usx95 commented Sep 2, 2025

Thanks!

I have one more question. So we need the reverse post order property of the CFG to ensure that we visit everything in the right order. The only reason why I am a bit unsure here, is because I wonder if it can ever cause some trouble that the RAV visiting the subexpressions is crossing a basic block boundary and venturing into that block too early, before all of its predecessors have been visited.

If this cannot happen, I think the current approach should be fine.

In principle, if the CFG is correct, this should not happen. Inner parts of a larger expression belonging to different blocks should be visited before outer parts of the expression. While visiting the outer expression, this would "try" to go into subexpressions but this should be skipped as we must have seen the subexpressions "before" the outer expressions (we check to avoid duplicate traversal in TraverseStmt)

Added a test for a ternary operator where this could have happened. We verify that the loans are created in different blocks.

@usx95 usx95 requested review from Xazax-hun and removed request for Xazax-hun September 2, 2025 13:01
@usx95 usx95 force-pushed the users/usx95/08-14-_lifetimesafety_track_view_types_gsl_pointer branch from 577d963 to 28730f8 Compare September 2, 2025 14:22
@Xazax-hun
Copy link
Collaborator

One last question, I am sort of wondering if working on a CFG with AC.getCFGBuildOptions().setAllAlwaysAdd(); would make this simpler. Would that have the properties we want?

@usx95 usx95 force-pushed the users/usx95/08-14-_lifetimesafety_track_view_types_gsl_pointer branch from 28730f8 to 4576fa7 Compare September 2, 2025 16:46
Copy link
Contributor Author

usx95 commented Sep 2, 2025

Seems to work fine. It is already added for a couple of other analyses so should be fine for us.

@usx95 usx95 requested a review from Xazax-hun September 3, 2025 11:12
@usx95 usx95 force-pushed the users/usx95/08-14-_lifetimesafety_track_view_types_gsl_pointer branch from 4576fa7 to 27e8bfc Compare September 3, 2025 13:47
@llvmbot llvmbot added the clang:frontend Language frontend issues, e.g. anything involving "Sema" label Sep 3, 2025
@usx95 usx95 force-pushed the users/usx95/08-14-_lifetimesafety_track_view_types_gsl_pointer branch from 27e8bfc to 6c42309 Compare September 3, 2025 13:48
Copy link
Contributor Author

usx95 commented Sep 3, 2025

@Xazax-hun AC.getCFGBuildOptions().setAllAlwaysAdd(); worked just fine. Thanks for the suggestion.

@usx95 usx95 changed the title [LifetimeSafety] Refactor FactGenerator to use RecursiveASTVisitor [LifetimeSafety] Fix duplicate loan generation for ImplicitCastExpr Sep 3, 2025
Copy link
Collaborator

@Xazax-hun Xazax-hun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very clean now, thanks!

Copy link
Contributor Author

usx95 commented Sep 3, 2025

Merge activity

  • Sep 3, 2:28 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Sep 3, 2:31 PM UTC: @usx95 merged this pull request with Graphite.

@usx95 usx95 merged commit 0f032f1 into main Sep 3, 2025
9 checks passed
@usx95 usx95 deleted the users/usx95/08-14-_lifetimesafety_track_view_types_gsl_pointer branch September 3, 2025 14:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:analysis clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:temporal-safety Issue/FR relating to the lifetime analysis in Clang (-Wdangling, -Wreturn-local-addr) clang Clang issues not falling into any other category
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

4 participants