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