[clang] 6362bc8 - [LifetimeSafety] Run analysis in post-order of CallGraph for better annotation propagation (#174178)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Jan 16 08:33:28 PST 2026
Author: Kashika Akhouri
Date: 2026-01-16T16:33:24Z
New Revision: 6362bc81a0a7269e062550f37070a0dcac3ad399
URL: https://github.com/llvm/llvm-project/commit/6362bc81a0a7269e062550f37070a0dcac3ad399
DIFF: https://github.com/llvm/llvm-project/commit/6362bc81a0a7269e062550f37070a0dcac3ad399.diff
LOG: [LifetimeSafety] Run analysis in post-order of CallGraph for better annotation propagation (#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 `clang::CallGraph` and
`addToCallGraph` to generate a call graph.
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:
```css
#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;
```
Fixes https://github.com/llvm/llvm-project/issues/172862
---------
Co-authored-by: Utkarsh Saxena <usx at google.com>
Added:
Modified:
clang/include/clang/Basic/LangOptions.def
clang/include/clang/Options/Options.td
clang/lib/Analysis/LifetimeSafety/Checker.cpp
clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
clang/lib/Sema/AnalysisBasedWarnings.cpp
clang/test/Sema/warn-lifetime-safety-suggestions.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index 8cba1dbaee24e..36fec24638363 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -505,6 +505,9 @@ LANGOPT(EnableLifetimeSafety, 1, 0, NotCompatible, "Experimental lifetime safety
LANGOPT(EnableLifetimeSafetyInference, 1, 0, NotCompatible, "Experimental lifetime safety inference analysis for C++")
+// TODO: Remove flag and default to end-of-TU analysis for lifetime safety after performance validation.
+LANGOPT(EnableLifetimeSafetyTUAnalysis, 1, 0, NotCompatible, "Experimental lifetime safety at translation-unit end, analyzing functions in call graph 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 bffc111d1cf35..188739e72434a 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -1976,6 +1976,16 @@ defm lifetime_safety_inference
BothFlags<[], [CC1Option],
" experimental lifetime safety inference for C++">>;
+defm lifetime_safety_tu_analysis
+ : BoolFOption<"experimental-lifetime-safety-tu-analysis",
+ LangOpts<"EnableLifetimeSafetyTUAnalysis">, DefaultFalse,
+ PosFlag<SetTrue, [], [CC1Option], "Enable">,
+ NegFlag<SetFalse, [], [CC1Option], "Disable">,
+ BothFlags<[], [CC1Option],
+ " run lifetime safety analysis at translation-unit "
+ "end, analyzing functions in call graph post-order "
+ "to best propagate inferred annotations">>;
+
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..f7383126fac38 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -14,6 +14,7 @@
#include "clang/Analysis/Analyses/LifetimeSafety/Checker.h"
#include "clang/AST/Expr.h"
#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
+#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
#include "clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h"
#include "clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h"
#include "clang/Analysis/Analyses/LifetimeSafety/Loans.h"
@@ -194,15 +195,18 @@ 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>())
- PVD->addAttr(
+ const auto *FD = dyn_cast<FunctionDecl>(PVD->getDeclContext());
+ if (!FD)
+ continue;
+ // Propagates inferred attributes via the most recent declaration to
+ // ensure visibility for callers in post-order analysis.
+ FD = getDeclWithMergedLifetimeBoundAttrs(FD);
+ ParmVarDecl *InferredPVD = const_cast<ParmVarDecl *>(
+ FD->getParamDecl(PVD->getFunctionScopeIndex()));
+ if (!InferredPVD->hasAttr<LifetimeBoundAttr>())
+ InferredPVD->addAttr(
LifetimeBoundAttr::CreateImplicit(AST, PVD->getLocation()));
}
}
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index bb82f09fa8457..24e5479923946 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -472,6 +472,7 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
bool IsGslConstruction) {
OriginList *CallList = getOriginsList(*Call);
// Ignore functions returning values with no origin.
+ FD = getDeclWithMergedLifetimeBoundAttrs(FD);
if (!FD || !CallList)
return;
auto IsArgLifetimeBound = [FD](unsigned I) -> bool {
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 7b08648080710..56d7db649afbe 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -36,6 +36,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,10 +49,12 @@
#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"
#include "llvm/Support/Debug.h"
+#include "llvm/Support/TimeProfiler.h"
#include <algorithm>
#include <deque>
#include <iterator>
@@ -2915,6 +2918,30 @@ class LifetimeSafetyReporterImpl : public LifetimeSafetyReporter {
} // namespace
} // namespace clang::lifetimes
+static void
+LifetimeSafetyTUAnalysis(Sema &S, TranslationUnitDecl *TU,
+ clang::lifetimes::LifetimeSafetyStats &LSStats) {
+ llvm::TimeTraceScope TimeProfile("LifetimeSafetyTUAnalysis");
+ CallGraph CG;
+ CG.addToCallGraph(TU);
+ lifetimes::LifetimeSafetyReporterImpl Reporter(S);
+ for (auto *Node : llvm::post_order(&CG)) {
+ const clang::FunctionDecl *CanonicalFD =
+ dyn_cast_or_null<clang::FunctionDecl>(Node->getDecl());
+ if (!CanonicalFD)
+ continue;
+ const FunctionDecl *FD = CanonicalFD->getDefinition();
+ if (!FD)
+ continue;
+ AnalysisDeclContext AC(nullptr, FD);
+ AC.getCFGBuildOptions().PruneTriviallyFalseEdges = false;
+ AC.getCFGBuildOptions().AddLifetime = true;
+ AC.getCFGBuildOptions().setAllAlwaysAdd();
+ if (AC.getCFG())
+ runLifetimeSafetyAnalysis(AC, &Reporter, LSStats, S.CollectStats);
+ }
+}
+
void clang::sema::AnalysisBasedWarnings::IssueWarnings(
TranslationUnitDecl *TU) {
if (!TU)
@@ -2969,6 +2996,10 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
CallableVisitor(CallAnalyzers, TU->getOwningModule())
.TraverseTranslationUnitDecl(TU);
}
+
+ if (S.getLangOpts().EnableLifetimeSafety && S.getLangOpts().CPlusPlus &&
+ S.getLangOpts().EnableLifetimeSafetyTUAnalysis)
+ LifetimeSafetyTUAnalysis(S, TU, LSStats);
}
void clang::sema::AnalysisBasedWarnings::IssueWarnings(
@@ -3015,7 +3046,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().EnableLifetimeSafetyTUAnalysis;
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 68107b45c34b1..6e3a6f1fd9117 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-tu-analysis -Wexperimental-lifetime-safety-suggestions -Wexperimental-lifetime-safety -Wno-dangling -I%t -verify %t/test_source.cpp
View definition_before_header(View a);
@@ -207,9 +207,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]]}}.
@@ -220,9 +219,8 @@ View return_view_callee(View a) { // expected-warning {{parameter in intra-T
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]]}}.
@@ -271,14 +269,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 at -1 {{returned here}}
}
} // namespace inference_with_templates
More information about the cfe-commits
mailing list