https://github.com/kashika0112 created 
https://github.com/llvm/llvm-project/pull/174178

Add functionality to analyze functions in the post-order of the call graph.

The PR includes the following changes:

1.  **Call Graph Generation**: Uses existing `CallableVisitor` to collect all 
function definitions within the Translation Unit that are not in system headers 
and have a body. Builds a `clang::CallGraph` to model the relationships between 
these functions. A custom `CallGraphBuilder` (a RecursiveASTVisitor) traverses 
the body of each function to find direct `CallExpr` nodes and add directed 
edges from caller to callee.
2. **Topological Traversal**: Uses `llvm::post_order` to iterate through the 
CallGraph.
3. **New Frontend Flag**: The post-order analysis is enabled via a new frontend 
flag `-fexperimental-lifetime-safety-inference-post-order`

Example:
```
#include <iostream>
#include <string>

  std::string_view f_1(std::string_view a);
  std::string_view f_2(std::string_view a);
  std::string_view f_f(std::string_view a);

  std::string_view f_f(std::string_view a) {
    std::string stack = "something on stack";
    std::string_view res = f_2(stack);
    return res;
  }

 std::string_view f_2(std::string_view a) {
    return f_1(a);
  }

 std::string_view f_1(std::string_view a) {
    return a;
  }
```

Ouput:
```
s.cpp:20:26: warning: parameter in intra-TU function should be marked 
[[clang::lifetimebound]] [-Wexperimental-lifetime-safety-intra-tu-suggestions]
   20 |     std::string_view f_1(std::string_view a)
      |                          ^~~~~~~~~~~~~~~~~~
      |                                             [[clang::lifetimebound]]
s.cpp:22:12: note: param returned here
   22 |     return a;
      |            ^
s.cpp:15:26: warning: parameter in intra-TU function should be marked 
[[clang::lifetimebound]] [-Wexperimental-lifetime-safety-intra-tu-suggestions]
   15 |     std::string_view f_2(std::string_view a)
      |                          ^~~~~~~~~~~~~~~~~~
      |                                             [[clang::lifetimebound]]
s.cpp:17:12: note: param returned here
   17 |     return f_1(a);
      |            ^~~~~~
s.cpp:11:32: warning: address of stack memory is returned later 
[-Wexperimental-lifetime-safety-permissive]
   11 |     std::string_view res = f_2(stack);
      |                                ^~~~~
s.cpp:12:12: note: returned here
   12 |     return res;
```

>From f0c7f3d76972790470a867326fdbd7311c2c92f6 Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <[email protected]>
Date: Fri, 2 Jan 2026 05:48:33 +0000
Subject: [PATCH] Add post-order analysis using call graph

---
 clang/include/clang/Basic/LangOptions.def     |  2 +
 clang/include/clang/Options/Options.td        |  9 ++
 clang/lib/Analysis/LifetimeSafety/Checker.cpp | 19 +++--
 clang/lib/Sema/AnalysisBasedWarnings.cpp      | 84 ++++++++++++++++++-
 .../Sema/warn-lifetime-safety-suggestions.cpp | 19 ++---
 5 files changed, 116 insertions(+), 17 deletions(-)

diff --git a/clang/include/clang/Basic/LangOptions.def 
b/clang/include/clang/Basic/LangOptions.def
index 8cba1dbaee24e..8b1f76a0a8382 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -505,6 +505,8 @@ LANGOPT(EnableLifetimeSafety, 1, 0, NotCompatible, 
"Experimental lifetime safety
 
 LANGOPT(EnableLifetimeSafetyInference, 1, 0, NotCompatible, "Experimental 
lifetime safety inference analysis for C++")
 
+LANGOPT(EnableLifetimeSafetyInferencePostOrder, 1, 0, NotCompatible, 
"Experimental lifetime safety inference analysis in post-order for C++")
+
 LANGOPT(PreserveVec3Type, 1, 0, NotCompatible, "Preserve 3-component vector 
type")
 
 #undef LANGOPT
diff --git a/clang/include/clang/Options/Options.td 
b/clang/include/clang/Options/Options.td
index 04756ce486eaf..5bc3e6a6b1e7a 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -1972,6 +1972,15 @@ defm lifetime_safety_inference
                   BothFlags<[], [CC1Option],
                             " experimental lifetime safety inference for 
C++">>;
 
+defm lifetime_safety_inference_post_order
+    : BoolFOption<"experimental-lifetime-safety-inference-post-order",
+                  LangOpts<"EnableLifetimeSafetyInferencePostOrder">,
+                  DefaultFalse, PosFlag<SetTrue, [], [CC1Option], "Enable">,
+                  NegFlag<SetFalse, [], [CC1Option], "Disable">,
+                  BothFlags<[], [CC1Option],
+                            " experimental lifetime safety inference in "
+                            "post-order for C++">>;
+
 defm addrsig : BoolFOption<"addrsig",
   CodeGenOpts<"Addrsig">, DefaultFalse,
   PosFlag<SetTrue, [], [ClangOption, CC1Option], "Emit">,
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp 
b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index ff2650be594f5..9d93c837495e5 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -194,16 +194,23 @@ class LifetimeChecker {
   }
 
   void inferAnnotations() {
-    // FIXME: To maximise inference propagation, functions should be analyzed 
in
-    // post-order of the call graph, allowing inferred annotations to propagate
-    // through the call chain
-    // FIXME: Add the inferred attribute to all redeclarations of the function,
-    // not just the definition being analyzed.
     for (const auto &[ConstPVD, EscapeExpr] : AnnotationWarningsMap) {
       ParmVarDecl *PVD = const_cast<ParmVarDecl *>(ConstPVD);
-      if (!PVD->hasAttr<LifetimeBoundAttr>())
+      if (!PVD->hasAttr<LifetimeBoundAttr>()) {
         PVD->addAttr(
             LifetimeBoundAttr::CreateImplicit(AST, PVD->getLocation()));
+        if (const FunctionDecl *FD =
+                dyn_cast<FunctionDecl>(PVD->getDeclContext())) {
+          for (const FunctionDecl *Redecl : FD->redecls()) {
+            if (Redecl != FD)
+              if (const ParmVarDecl *RedeclPVD =
+                      Redecl->getParamDecl(PVD->getFunctionScopeIndex());
+                  RedeclPVD)
+                const_cast<ParmVarDecl *>(RedeclPVD)->addAttr(
+                    LifetimeBoundAttr::CreateImplicit(AST, 
PVD->getLocation()));
+          }
+        }
+      }
     }
   }
 };
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp 
b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 7b08648080710..85d3c5fb4caff 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -23,6 +23,7 @@
 #include "clang/AST/ExprObjC.h"
 #include "clang/AST/OperationKinds.h"
 #include "clang/AST/ParentMap.h"
+#include "clang/AST/RecursiveASTVisitor.h"
 #include "clang/AST/StmtCXX.h"
 #include "clang/AST/StmtObjC.h"
 #include "clang/AST/Type.h"
@@ -36,6 +37,7 @@
 #include "clang/Analysis/Analyses/UnsafeBufferUsage.h"
 #include "clang/Analysis/AnalysisDeclContext.h"
 #include "clang/Analysis/CFG.h"
+#include "clang/Analysis/CallGraph.h"
 #include "clang/Analysis/FlowSensitive/DataflowWorklist.h"
 #include "clang/Basic/Diagnostic.h"
 #include "clang/Basic/DiagnosticSema.h"
@@ -48,6 +50,7 @@
 #include "llvm/ADT/BitVector.h"
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/MapVector.h"
+#include "llvm/ADT/PostOrderIterator.h"
 #include "llvm/ADT/STLFunctionalExtras.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
@@ -2915,6 +2918,43 @@ class LifetimeSafetyReporterImpl : public 
LifetimeSafetyReporter {
 } // namespace
 } // namespace clang::lifetimes
 
+class CallGraphBuilder : public clang::RecursiveASTVisitor<CallGraphBuilder> {
+  clang::CallGraph &CG;
+  clang::Sema &S;
+  clang::FunctionDecl *CurrentFD = nullptr;
+
+public:
+  explicit CallGraphBuilder(clang::CallGraph &CG, clang::Sema &S)
+      : CG(CG), S(S) {}
+
+  void addCallsFrom(clang::FunctionDecl *FD) {
+    CurrentFD = FD;
+    if (FD->hasBody())
+      TraverseStmt(FD->getBody());
+  }
+
+  // Visitor for call expressions.
+  bool VisitCallExpr(clang::CallExpr *CE) {
+    if (clang::FunctionDecl *CalleeFD = CE->getDirectCallee()) {
+      const clang::FunctionDecl *Def = nullptr;
+      if (CalleeFD->hasBody(Def) && Def) {
+        if (!S.getSourceManager().isInSystemHeader(Def->getLocation())) {
+          const FunctionDecl *CanonicalCaller = CurrentFD->getCanonicalDecl();
+          const FunctionDecl *CanonicalCallee = Def->getCanonicalDecl();
+
+          clang::CallGraphNode *CallerNode = CG.getOrInsertNode(
+              const_cast<clang::FunctionDecl *>(CanonicalCaller));
+          clang::CallGraphNode *CalleeNode = CG.getOrInsertNode(
+              const_cast<clang::FunctionDecl *>(CanonicalCallee));
+          // Add an edge from caller to callee.
+          CallerNode->addCallee({CalleeNode, CE});
+        }
+      }
+    }
+    return true;
+  }
+};
+
 void clang::sema::AnalysisBasedWarnings::IssueWarnings(
      TranslationUnitDecl *TU) {
   if (!TU)
@@ -2969,6 +3009,46 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
     CallableVisitor(CallAnalyzers, TU->getOwningModule())
         .TraverseTranslationUnitDecl(TU);
   }
+
+  if (S.getLangOpts().EnableLifetimeSafety && S.getLangOpts().CPlusPlus &&
+      S.getLangOpts().EnableLifetimeSafetyInferencePostOrder) {
+    llvm::SmallVector<const FunctionDecl *, 64> AllFunctions;
+    auto AddFunctionToList = [&](const Decl *D) -> void {
+      if (const auto *FD = dyn_cast<FunctionDecl>(D))
+        if (FD->doesThisDeclarationHaveABody() &&
+            !S.getSourceManager().isInSystemHeader(FD->getLocation()))
+          AllFunctions.push_back(FD);
+    };
+    CallableVisitor(AddFunctionToList, TU->getOwningModule())
+        .TraverseTranslationUnitDecl(TU);
+
+    if (AllFunctions.empty())
+      return;
+
+    clang::CallGraph CG;
+    for (const clang::FunctionDecl *FD : AllFunctions)
+      CG.getOrInsertNode(const_cast<clang::FunctionDecl *>(FD));
+
+    CallGraphBuilder Builder(CG, S);
+    for (const clang::FunctionDecl *FD : AllFunctions)
+      Builder.addCallsFrom(const_cast<clang::FunctionDecl *>(FD));
+
+    lifetimes::LifetimeSafetyReporterImpl Reporter(S);
+    for (auto *Node : llvm::post_order(&CG)) {
+      if (const clang::FunctionDecl *CanonicalFD =
+              dyn_cast_or_null<clang::FunctionDecl>(Node->getDecl())) {
+        const FunctionDecl *FD = CanonicalFD->getDefinition();
+        if (!FD)
+          continue;
+
+        AnalysisDeclContext AC(nullptr, FD);
+        AC.getCFGBuildOptions().PruneTriviallyFalseEdges = false;
+        AC.getCFGBuildOptions().AddLifetime = true;
+        AC.getCFGBuildOptions().setAllAlwaysAdd();
+        runLifetimeSafetyAnalysis(AC, &Reporter, LSStats, S.CollectStats);
+      }
+    }
+  }
 }
 
 void clang::sema::AnalysisBasedWarnings::IssueWarnings(
@@ -3015,7 +3095,9 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
   AC.getCFGBuildOptions().AddCXXNewAllocator = false;
   AC.getCFGBuildOptions().AddCXXDefaultInitExprInCtors = true;
 
-  bool EnableLifetimeSafetyAnalysis = S.getLangOpts().EnableLifetimeSafety;
+  bool EnableLifetimeSafetyAnalysis =
+      S.getLangOpts().EnableLifetimeSafety &&
+      !S.getLangOpts().EnableLifetimeSafetyInferencePostOrder;
 
   if (EnableLifetimeSafetyAnalysis)
     AC.getCFGBuildOptions().AddLifetime = true;
diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp 
b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
index 8a328dfbc8d9e..5aeb76e530bad 100644
--- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
@@ -1,6 +1,6 @@
 // RUN: rm -rf %t
 // RUN: split-file %s %t
-// RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety 
-fexperimental-lifetime-safety-inference 
-Wexperimental-lifetime-safety-suggestions -Wexperimental-lifetime-safety 
-Wno-dangling -I%t -verify %t/test_source.cpp
+// RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety 
-fexperimental-lifetime-safety-inference 
-fexperimental-lifetime-safety-inference-post-order 
-Wexperimental-lifetime-safety-suggestions -Wexperimental-lifetime-safety 
-Wno-dangling -I%t -verify %t/test_source.cpp
 
 View definition_before_header(View a);
 
@@ -204,9 +204,8 @@ MyObj* return_pointer_by_func(MyObj* a) {         // 
expected-warning {{paramete
 namespace incorrect_order_inference_view {
 View return_view_callee(View a);
 
-// FIXME: No lifetime annotation suggestion when functions are not present in 
the callee-before-caller pattern
-View return_view_caller(View a) {
-  return return_view_callee(a);
+View return_view_caller(View a) {     // expected-warning {{parameter in 
intra-TU function should be marked [[clang::lifetimebound]]}}.
+  return return_view_callee(a);       // expected-note {{param returned here}}
 }
 
 View return_view_callee(View a) {     // expected-warning {{parameter in 
intra-TU function should be marked [[clang::lifetimebound]]}}.
@@ -218,8 +217,8 @@ namespace incorrect_order_inference_object {
 MyObj* return_object_callee(MyObj* a);
 
 // FIXME: No lifetime annotation suggestion warning when functions are not 
present in the callee-before-caller pattern
-MyObj* return_object_caller(MyObj* a) {
-  return return_object_callee(a);
+MyObj* return_object_caller(MyObj* a) {      // expected-warning {{parameter 
in intra-TU function should be marked [[clang::lifetimebound]]}}.
+  return return_object_callee(a);            // expected-note {{param returned 
here}}
 }
 
 MyObj* return_object_callee(MyObj* a) {      // expected-warning {{parameter 
in intra-TU function should be marked [[clang::lifetimebound]]}}.
@@ -268,14 +267,14 @@ T* template_identity(T* a) {            // 
expected-warning {{parameter in intra
 }
 
 template<typename T>
-T* template_caller(T* a) {
-  return template_identity(a);          // expected-note {{in instantiation of 
function template specialization 
'inference_with_templates::template_identity<MyObj>' requested here}}
+T* template_caller(T* a) {              // expected-warning {{parameter in 
intra-TU function should be marked [[clang::lifetimebound]]}}.
+  return template_identity(a);          // expected-note {{param returned 
here}}
 }
 
-// FIXME: Fails to detect UAR as template instantiations are deferred to the 
end of the Translation Unit.
 MyObj* test_template_inference_with_stack() {
   MyObj local_stack;
-  return template_caller(&local_stack); // expected-note {{in instantiation of 
function template specialization 
'inference_with_templates::template_caller<MyObj>' requested here}}             
                                 
+  return template_caller(&local_stack);   // expected-warning {{address of 
stack memory is returned later}}
+                                          // expected-note@-1 {{returned 
here}}                                       
 }
 } // namespace inference_with_templates
 

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

Reply via email to