[clang] Implement pay-for-what-you-use warnings in LifetimeSafetyChecker (PR #221610)

Utkarsh Saxena via cfe-commits cfe-commits at lists.llvm.org
Sun Sep 6 15:05:59 PDT 2026


https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/221610

>From d9116eab4d6349e61c91c8cbf56bd10d3be523d3 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Sun, 6 Sep 2026 21:19:22 +0000
Subject: [PATCH] Implement pay-for-what-you-use warnings in
 LifetimeSafetyChecker

We split IsLifetimeSafetyEnabled into individual check functions
in SemaLifetimeSafety.h:
- ShouldCheckSafety
- ShouldCheckNoescapeViolations
- ShouldCheckLifetimeboundViolations
- ShouldCheckMisplacedLifetimebound
- ShouldCheckInapplicableLifetimebound
- ShouldSuggestLifetimeAnnotations

These properties are stored in LifetimeSafetyOpts, which are then checked
inside the Checker to avoid invoking expensive reporting functions mapping
to diagnostics that are disabled.

TAG=agy
CONV=a79c61c3-92c6-404e-83e3-3e3088f77c0e
---
 .../Analyses/LifetimeSafety/LifetimeSafety.h  |  5 +
 clang/lib/Analysis/LifetimeSafety/Checker.cpp | 17 +++-
 clang/lib/Sema/SemaLifetimeSafety.h           | 99 ++++++++++++-------
 3 files changed, 82 insertions(+), 39 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 101407e91aa65..68e84961010dd 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -41,6 +41,11 @@ struct LifetimeSafetyOpts {
 
   /// Whether to suggest lifetime annotations.
   bool SuggestAnnotations;
+
+  bool CheckNoescapeViolations;
+  bool CheckLifetimeboundViolations;
+  bool CheckMisplacedLifetimebound;
+  bool CheckInapplicableLifetimebound;
 };
 
 /// Enum to track functions visible across or within TU.
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index b136da486365a..a358215a295d1 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -105,10 +105,14 @@ class LifetimeChecker {
           checkAnnotations(OEF);
     issuePendingWarnings();
     suggestAnnotations();
-    reportNoescapeViolations();
-    reportLifetimeboundViolations();
-    reportMisplacedLifetimebound();
-    reportInapplicableLifetimebound();
+    if (LSOpts.CheckNoescapeViolations)
+      reportNoescapeViolations();
+    if (LSOpts.CheckLifetimeboundViolations)
+      reportLifetimeboundViolations();
+    if (LSOpts.CheckMisplacedLifetimebound)
+      reportMisplacedLifetimebound();
+    if (LSOpts.CheckInapplicableLifetimebound)
+      reportInapplicableLifetimebound();
     //  Annotation inference is currently guarded by a frontend flag. In the
     //  future, this might be replaced by a design that differentiates between
     //  explicit and inferred findings with separate warning groups.
@@ -254,6 +258,7 @@ class LifetimeChecker {
   }
 
   void issuePendingWarnings() {
+    llvm::TimeTraceScope TimeTrace("IssuePendingWarnings");
     if (!SemaHelper)
       return;
     for (const auto &[LID, Warning] : FinalWarningsMap) {
@@ -436,6 +441,7 @@ class LifetimeChecker {
   }
 
   void reportNoescapeViolations() {
+    llvm::TimeTraceScope TimeTrace("ReportNoescapeViolations");
     for (auto [PVD, EscapeTarget] : NoescapeWarningsMap) {
       if (const auto *E = EscapeTarget.dyn_cast<const Expr *>())
         SemaHelper->reportNoescapeViolation(PVD, E);
@@ -449,6 +455,7 @@ class LifetimeChecker {
   }
 
   void reportLifetimeboundViolations() {
+    llvm::TimeTraceScope TimeTrace("ReportLifetimeboundViolations");
     if (!isa<FunctionDecl>(FD))
       return;
     if (const auto *MD = dyn_cast<CXXMethodDecl>(FD);
@@ -471,6 +478,7 @@ class LifetimeChecker {
   // Reports lifetimebound attributes that are placed on a function definition
   // but not on the corresponding declaration.
   void reportMisplacedLifetimebound() {
+    llvm::TimeTraceScope TimeTrace("ReportMisplacedLifetimebound");
     const FunctionDecl *FDef = dyn_cast<FunctionDecl>(FD);
     if (!FDef)
       return;
@@ -501,6 +509,7 @@ class LifetimeChecker {
   }
 
   void reportInapplicableLifetimebound() {
+    llvm::TimeTraceScope TimeTrace("ReportInapplicableLifetimebound");
     const auto *FDef = dyn_cast<FunctionDecl>(FD);
     if (!FDef)
       return;
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h
index d87c9003e83cf..ca941375a003f 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -25,28 +25,7 @@
 
 namespace clang::lifetimes {
 
-inline bool IsLifetimeSafetyEnabled(Sema &S, const Decl *D) {
-  // TODO: Enable ObjectiveC later when we know it's stable enough.
-  if (S.getLangOpts().ObjC)
-    return false;
-
-  // TODO: Default this flag to on in the future.
-  if (!S.getLangOpts().CPlusPlus && !S.getLangOpts().EnableLifetimeSafetyInC)
-    return false;
-
-  // Translation-unit mode: whole-program analysis runs once on TU.
-  // Individual function analysis is disabled when TU mode is enabled.
-  if (S.getLangOpts().EnableLifetimeSafetyTUAnalysis)
-    return isa<TranslationUnitDecl>(D);
-
-  // Per-function mode: analysis runs on each function/method individually.
-  // Skip TU-level calls when per-function mode is enabled.
-  if (isa<TranslationUnitDecl>(D))
-    return false;
-
-  // Enable per-function mode via debug flag or specific diagnostics.
-  if (S.getLangOpts().DebugRunLifetimeSafety)
-    return true;
+inline bool ShouldCheckSafety(Sema &S, const Decl *D) {
   DiagnosticsEngine &Diags = S.getDiagnostics();
   constexpr unsigned DiagIDs[] = {
       diag::warn_lifetime_safety_use_after_scope,
@@ -59,25 +38,40 @@ inline bool IsLifetimeSafetyEnabled(Sema &S, const Decl *D) {
       diag::warn_lifetime_safety_dangling_field_moved,
       diag::warn_lifetime_safety_dangling_global,
       diag::warn_lifetime_safety_dangling_global_moved,
-      diag::warn_lifetime_safety_noescape_escapes,
-      diag::warn_lifetime_safety_lifetimebound_violation,
-      diag::warn_lifetime_safety_cross_tu_misplaced_lifetimebound,
-      diag::warn_lifetime_safety_intra_tu_misplaced_lifetimebound,
       diag::warn_lifetime_safety_invalidated_field,
-      diag::warn_lifetime_safety_invalidated_global,
-      diag::warn_lifetime_safety_cross_tu_param_suggestion,
-      diag::warn_lifetime_safety_intra_tu_param_suggestion,
-      diag::warn_lifetime_safety_cross_tu_ctor_param_suggestion,
-      diag::warn_lifetime_safety_intra_tu_ctor_param_suggestion,
-      diag::warn_lifetime_safety_cross_tu_this_suggestion,
-      diag::warn_lifetime_safety_intra_tu_this_suggestion,
-      diag::warn_lifetime_safety_inapplicable_lifetimebound};
+      diag::warn_lifetime_safety_invalidated_global};
   for (unsigned DiagID : DiagIDs)
     if (!Diags.isIgnored(DiagID, D->getBeginLoc()))
       return true;
   return false;
 }
 
+inline bool ShouldCheckNoescapeViolations(Sema &S, const Decl *D) {
+  return !S.getDiagnostics().isIgnored(
+      diag::warn_lifetime_safety_noescape_escapes, D->getBeginLoc());
+}
+
+inline bool ShouldCheckLifetimeboundViolations(Sema &S, const Decl *D) {
+  return !S.getDiagnostics().isIgnored(
+      diag::warn_lifetime_safety_lifetimebound_violation, D->getBeginLoc());
+}
+
+inline bool ShouldCheckMisplacedLifetimebound(Sema &S, const Decl *D) {
+  DiagnosticsEngine &Diags = S.getDiagnostics();
+  constexpr unsigned DiagIDs[] = {
+      diag::warn_lifetime_safety_cross_tu_misplaced_lifetimebound,
+      diag::warn_lifetime_safety_intra_tu_misplaced_lifetimebound};
+  for (unsigned DiagID : DiagIDs)
+    if (!Diags.isIgnored(DiagID, D->getBeginLoc()))
+      return true;
+  return false;
+}
+
+inline bool ShouldCheckInapplicableLifetimebound(Sema &S, const Decl *D) {
+  return !S.getDiagnostics().isIgnored(
+      diag::warn_lifetime_safety_inapplicable_lifetimebound, D->getBeginLoc());
+}
+
 inline bool ShouldSuggestLifetimeAnnotations(Sema &S, const Decl *D) {
   DiagnosticsEngine &Diags = S.getDiagnostics();
   constexpr unsigned DiagIDs[] = {
@@ -93,13 +87,48 @@ inline bool ShouldSuggestLifetimeAnnotations(Sema &S, const Decl *D) {
   return false;
 }
 
+inline bool IsLifetimeSafetyEnabled(Sema &S, const Decl *D) {
+  // TODO: Enable ObjectiveC later when we know it's stable enough.
+  if (S.getLangOpts().ObjC)
+    return false;
+
+  // TODO: Default this flag to on in the future.
+  if (!S.getLangOpts().CPlusPlus && !S.getLangOpts().EnableLifetimeSafetyInC)
+    return false;
+
+  // Translation-unit mode: whole-program analysis runs once on TU.
+  // Individual function analysis is disabled when TU mode is enabled.
+  if (S.getLangOpts().EnableLifetimeSafetyTUAnalysis)
+    return isa<TranslationUnitDecl>(D);
+
+  // Per-function mode: analysis runs on each function/method individually.
+  // Skip TU-level calls when per-function mode is enabled.
+  if (isa<TranslationUnitDecl>(D))
+    return false;
+
+  // Enable per-function mode via debug flag or specific diagnostics.
+  if (S.getLangOpts().DebugRunLifetimeSafety)
+    return true;
+
+  return ShouldCheckSafety(S, D) || ShouldCheckNoescapeViolations(S, D) ||
+         ShouldCheckLifetimeboundViolations(S, D) ||
+         ShouldCheckMisplacedLifetimebound(S, D) ||
+         ShouldCheckInapplicableLifetimebound(S, D) ||
+         ShouldSuggestLifetimeAnnotations(S, D);
+}
+
 inline LifetimeSafetyOpts GetLifetimeSafetyOpts(Sema &S, const Decl *D) {
   LifetimeSafetyOpts LSOpts;
   LSOpts.MaxCFGBlocks = S.getLangOpts().LifetimeSafetyMaxCFGBlocks;
   LSOpts.SuggestAnnotations = ShouldSuggestLifetimeAnnotations(S, D);
+  LSOpts.CheckNoescapeViolations = ShouldCheckNoescapeViolations(S, D);
+  LSOpts.CheckLifetimeboundViolations =
+      ShouldCheckLifetimeboundViolations(S, D);
+  LSOpts.CheckMisplacedLifetimebound = ShouldCheckMisplacedLifetimebound(S, D);
+  LSOpts.CheckInapplicableLifetimebound =
+      ShouldCheckInapplicableLifetimebound(S, D);
   return LSOpts;
 }
-
 class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
 
 public:



More information about the cfe-commits mailing list