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 <[email protected]> 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@-1 {{returned here}} } } // namespace inference_with_templates _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
