Repository: llvm/llvm-project
Pull request #136323: Control analysis-based diagnostics with #pragma
Author: login: AaronBallman
Labels: name: clang
description: Clang issues not falling into any other category
name: clang:frontend
description: Language frontend issues, e.g. anything involving "Sema"
name: clang:static analyzer
name: quality-of-implementation
name: clang:analysis
Created: 2025-04-18T16:32:26Z | Merged: 2025-04-23T10:55:10Z

Previously, analysis-based diagnostics (like -Wconsumed) had to be enabled at file scope in order to be run at the end of each function body. This meant that they did not respect #pragma clang diagnostic enabling or disabling the diagnostic.

Now, these pragmas can control the diagnostic emission.

Fixes #42199

Linked issue #42199: Can't fully activate `-Wconsumed` using `#pragma clang diagnostic`
|  |  |
| --- | --- |
| Bugzilla Link | [42854](https://llvm.org/bz42854) |
| Version | trunk |
| OS | All |
| CC | @DougGregor,@zygoloid |

## Extended Description 
If you activate `-Wconsumed` using a pragma rather than on the command line, it seemingly doesn't trigger the consumed analysis pass.

Given the following example code:
```cpp
#pragma clang diagnostic error "-Wconsumed"

struct [[clang::consumable(unconsumed)]] Linear {
    [[clang::return_typestate(unconsumed)]]
    Linear() {}
    [[clang::callable_when(consumed)]]
    ~Linear() {}
};

void a() {
    Linear l;
}
```


Compiling the code as is, without passing `-Wconsumed` on the command line, I get no warnings or errors.

The pragma does enable `-Wconsumed` warnings about malformed declarations: if I e.g. comment out the consumable attribute on Linear, I get:
```
error: consumed analysis attribute is attached to member of class 'Linear' which isn't marked as consumable [-Werror,-Wconsumed]
```
However, without commenting anything out, I expect this diagnostic from `a()`:
```
error: invalid invocation of method '~Linear' on object 'l' while it is in the 'unconsumed' state [-Werror,-Wconsumed]
```
I get this if and only if I pass `-Wconsumed` on the command line.

Tested on Compiler Explorer's "x86-64 clang (trunk)".
Linked issue comments: user: login: llvmbot
created_at: 2025-04-18T12:21:42Z
body: 
@llvm/issue-subscribers-clang-frontend

Author: None (comex)

<details>
|  |  |
| --- | --- |
| Bugzilla Link | [42854](https://llvm.org/bz42854) |
| Version | trunk |
| OS | All |
| CC | @<!-- -->DougGregor,@<!-- -->zygoloid |

## Extended Description 
If you activate `-Wconsumed` using a pragma rather than on the command line, it seemingly doesn't trigger the consumed analysis pass.

Given the following example code:
```cpp
#pragma clang diagnostic error "-Wconsumed"

struct [[clang::consumable(unconsumed)]] Linear {
    [[clang::return_typestate(unconsumed)]]
    Linear() {}
    [[clang::callable_when(consumed)]]
    ~Linear() {}
};

void a() {
    Linear l;
}
```


Compiling the code as is, without passing `-Wconsumed` on the command line, I get no warnings or errors.

The pragma does enable `-Wconsumed` warnings about malformed declarations: if I e.g. comment out the consumable attribute on Linear, I get:
```
error: consumed analysis attribute is attached to member of class 'Linear' which isn't marked as consumable [-Werror,-Wconsumed]
```
However, without commenting anything out, I expect this diagnostic from `a()`:
```
error: invalid invocation of method '~Linear' on object 'l' while it is in the 'unconsumed' state [-Werror,-Wconsumed]
```
I get this if and only if I pass `-Wconsumed` on the command line.

Tested on Compiler Explorer's "x86-64 clang (trunk)".
</details>

author_association: MEMBER
user: login: AaronBallman
created_at: 2025-04-18T12:24:50Z
body: The issue is that analysis-based warnings do not correctly check for whether the diagnostic is enabled or not. The default policy is set here https://github.com/llvm/llvm-project/blob/3ed83630b263f667bacd1ef1a65f26d8665c282e/clang/lib/Sema/AnalysisBasedWarnings.cpp#L2497 but it's using an invalid source location for the check because it's a default policy.

Everything else uses the default policy. I think `getDefaultPolicy()` perhaps needs to take a `SourceLocation` of some kind so we can properly set the flags based on the state of the compiler at the time the policy is needed.
author_association: COLLABORATOR

Diff:
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 86d37f5616356..112e641ad6827 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -398,6 +398,8 @@ Improvements to Clang's diagnostics
   constructors to initialize their non-modifiable members. The diagnostic is
   not new; being controlled via a warning group is what's new. Fixes #GH41104
 
+- Analysis-based diagnostics (like ``-Wconsumed`` or ``-Wunreachable-code``)
+  can now be correctly controlled by ``#pragma clang diagnostic``. #GH42199
 
 - Improved Clang's error recovery for invalid function calls.
 
diff --git a/clang/include/clang/Sema/AnalysisBasedWarnings.h b/clang/include/clang/Sema/AnalysisBasedWarnings.h
index aafe227b84084..4103c3f006a8f 100644
--- a/clang/include/clang/Sema/AnalysisBasedWarnings.h
+++ b/clang/include/clang/Sema/AnalysisBasedWarnings.h
@@ -25,6 +25,7 @@ class QualType;
 class Sema;
 namespace sema {
   class FunctionScopeInfo;
+  class SemaPPCallbacks;
 }
 
 namespace sema {
@@ -33,6 +34,7 @@ class AnalysisBasedWarnings {
 public:
   class Policy {
     friend class AnalysisBasedWarnings;
+    friend class SemaPPCallbacks;
     // The warnings to run.
     LLVM_PREFERRED_TYPE(bool)
     unsigned enableCheckFallThrough : 1;
@@ -49,7 +51,6 @@ class AnalysisBasedWarnings {
 
 private:
   Sema &S;
-  Policy DefaultPolicy;
 
   class InterProceduralData;
   std::unique_ptr<InterProceduralData> IPData;
@@ -57,6 +58,9 @@ class AnalysisBasedWarnings {
   enum VisitFlag { NotVisited = 0, Visited = 1, Pending = 2 };
   llvm::DenseMap<const FunctionDecl*, VisitFlag> VisitedFD;
 
+  Policy PolicyOverrides;
+  void clearOverrides();
+
   /// \name Statistics
   /// @{
 
@@ -103,7 +107,13 @@ class AnalysisBasedWarnings {
   // Issue warnings that require whole-translation-unit analysis.
   void IssueWarnings(TranslationUnitDecl *D);
 
-  Policy getDefaultPolicy() { return DefaultPolicy; }
+  // Gets the default policy which is in effect at the given source location.
+  Policy getPolicyInEffectAt(SourceLocation Loc);
+
+  // Get the policies we may want to override due to things like #pragma clang
+  // diagnostic handling. If a caller sets any of these policies to true, that
+  // will override the policy used to issue warnings.
+  Policy &getPolicyOverrides() { return PolicyOverrides; }
 
   void PrintStats() const;
 };
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 32c7ee92466ad..3d8eaf035186b 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2492,9 +2492,11 @@ class sema::AnalysisBasedWarnings::InterProceduralData {
   CalledOnceInterProceduralData CalledOnceData;
 };
 
-static unsigned isEnabled(DiagnosticsEngine &D, unsigned diag) {
-  return (unsigned)!D.isIgnored(diag, SourceLocation());
-}
+template <typename... Ts>
+static bool areAnyEnabled(DiagnosticsEngine &D, SourceLocation Loc,
+                          Ts... Diags) {
+  return (!D.isIgnored(Diags, Loc) || ...);
+};
 
 sema::AnalysisBasedWarnings::AnalysisBasedWarnings(Sema &s)
     : S(s), IPData(std::make_unique<InterProceduralData>()),
@@ -2503,23 +2505,37 @@ sema::AnalysisBasedWarnings::AnalysisBasedWarnings(Sema &s)
       NumUninitAnalysisVariables(0), MaxUninitAnalysisVariablesPerFunction(0),
       NumUninitAnalysisBlockVisits(0),
       MaxUninitAnalysisBlockVisitsPerFunction(0) {
+}
+
+// We need this here for unique_ptr with forward declared class.
+sema::AnalysisBasedWarnings::~AnalysisBasedWarnings() = default;
 
+sema::AnalysisBasedWarnings::Policy
+sema::AnalysisBasedWarnings::getPolicyInEffectAt(SourceLocation Loc) {
   using namespace diag;
   DiagnosticsEngine &D = S.getDiagnostics();
+  Policy P;
 
-  DefaultPolicy.enableCheckUnreachable =
-      isEnabled(D, warn_unreachable) || isEnabled(D, warn_unreachable_break) ||
-      isEnabled(D, warn_unreachable_return) ||
-      isEnabled(D, warn_unreachable_loop_increment);
+  // Note: The enabled checks should be kept in sync with the switch in
+  // SemaPPCallbacks::PragmaDiagnostic().
+  P.enableCheckUnreachable =
+      PolicyOverrides.enableCheckUnreachable ||
+      areAnyEnabled(D, Loc, warn_unreachable, warn_unreachable_break,
+                    warn_unreachable_return, warn_unreachable_loop_increment);
 
-  DefaultPolicy.enableThreadSafetyAnalysis = isEnabled(D, warn_double_lock);
+  P.enableThreadSafetyAnalysis = PolicyOverrides.enableThreadSafetyAnalysis ||
+                                 areAnyEnabled(D, Loc, warn_double_lock);
 
-  DefaultPolicy.enableConsumedAnalysis =
-      isEnabled(D, warn_use_in_invalid_state);
+  P.enableConsumedAnalysis = PolicyOverrides.enableConsumedAnalysis ||
+                             areAnyEnabled(D, Loc, warn_use_in_invalid_state);
+  return P;
 }
 
-// We need this here for unique_ptr with forward declared class.
-sema::AnalysisBasedWarnings::~AnalysisBasedWarnings() = default;
+void sema::AnalysisBasedWarnings::clearOverrides() {
+  PolicyOverrides.enableCheckUnreachable = false;
+  PolicyOverrides.enableConsumedAnalysis = false;
+  PolicyOverrides.enableThreadSafetyAnalysis = false;
+}
 
 static void flushDiagnostics(Sema &S, const sema::FunctionScopeInfo *fscope) {
   for (const auto &D : fscope->PossiblyUnreachableDiags)
@@ -2870,6 +2886,9 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
     AC.getCFG();
   }
 
+  // Clear any of our policy overrides.
+  clearOverrides();
+
   // Collect statistics about the CFG if it was built.
   if (S.CollectStats && AC.isCFGBuilt()) {
     ++NumFunctionsAnalyzed;
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index d2da9cd1201c2..4039601612c62 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -202,6 +202,43 @@ class SemaPPCallbacks : public PPCallbacks {
       break;
     }
   }
+  void PragmaDiagnostic(SourceLocation Loc, StringRef Namespace,
+                        diag::Severity Mapping, StringRef Str) override {
+    // If one of the analysis-based diagnostics was enabled while processing
+    // a function, we want to note it in the analysis-based warnings so they
+    // can be run at the end of the function body even if the analysis warnings
+    // are disabled at that point.
+    SmallVector<diag::kind, 256> GroupDiags;
+    diag::Flavor Flavor =
+        Str[1] == 'W' ? diag::Flavor::WarningOrError : diag::Flavor::Remark;
+    StringRef Group = Str.substr(2);
+
+    if (S->PP.getDiagnostics().getDiagnosticIDs()->getDiagnosticsInGroup(
+            Flavor, Group, GroupDiags))
+      return;
+
+    for (diag::kind K : GroupDiags) {
+      // Note: the cases in this switch should be kept in sync with the
+      // diagnostics in AnalysisBasedWarnings::getPolicyInEffectAt().
+      AnalysisBasedWarnings::Policy &Override =
+          S->AnalysisWarnings.getPolicyOverrides();
+      switch (K) {
+      default: break;
+      case diag::warn_unreachable:
+      case diag::warn_unreachable_break:
+      case diag::warn_unreachable_return:
+      case diag::warn_unreachable_loop_increment:
+        Override.enableCheckUnreachable = true;
+        break;
+      case diag::warn_double_lock:
+        Override.enableThreadSafetyAnalysis = true;
+        break;
+      case diag::warn_use_in_invalid_state:
+        Override.enableConsumedAnalysis = true;
+        break;
+      }
+    }
+  }
 };
 
 } // end namespace sema
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 46933c5c43168..d28a2107d58a9 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -16150,7 +16150,13 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body,
   if (FSI->UsesFPIntrin && FD && !FD->hasAttr<StrictFPAttr>())
     FD->addAttr(StrictFPAttr::CreateImplicit(Context));
 
-  sema::AnalysisBasedWarnings::Policy WP = AnalysisWarnings.getDefaultPolicy();
+  SourceLocation AnalysisLoc;
+  if (Body)
+    AnalysisLoc = Body->getEndLoc();
+  else if (FD)
+    AnalysisLoc = FD->getEndLoc();
+  sema::AnalysisBasedWarnings::Policy WP =
+      AnalysisWarnings.getPolicyInEffectAt(AnalysisLoc);
   sema::AnalysisBasedWarnings::Policy *ActivePolicy = nullptr;
 
   // If we skip function body, we can't tell if a function is a coroutine.
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 01a021443c94f..2e6ce17f8bf91 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -16597,7 +16597,8 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc,
   BD->setCaptures(Context, Captures, BSI->CXXThisCaptureIndex != 0);
 
   // Pop the block scope now but keep it alive to the end of this function.
-  AnalysisBasedWarnings::Policy WP = AnalysisWarnings.getDefaultPolicy();
+  AnalysisBasedWarnings::Policy WP =
+      AnalysisWarnings.getPolicyInEffectAt(Body->getEndLoc());
   PoppedFunctionScopePtr ScopeRAII = PopFunctionScopeInfo(&WP, BD, BlockTy);
 
   BlockExpr *Result = new (Context)
diff --git a/clang/test/Analysis/pragma-diag-control.cpp b/clang/test/Analysis/pragma-diag-control.cpp
new file mode 100644
index 0000000000000..470960c030d0f
--- /dev/null
+++ b/clang/test/Analysis/pragma-diag-control.cpp
@@ -0,0 +1,83 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Werror=unreachable-code-aggressive %s
+
+// Test that analysis-based warnings honor #pragma diagnostic controls.
+
+struct [[clang::consumable(unconsumed)]] Linear {
+  [[clang::return_typestate(unconsumed)]]
+  Linear() {}
+  [[clang::callable_when(consumed)]]
+  ~Linear() {}
+};
+
+int a() {	
+  Linear l;
+  return 0; // No -Wconsumed diagnostic, analysis is not enabled.
+  return 1; // expected-error {{'return' will never be executed}}
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wconsumed"
+int b() {
+  Linear l;
+  return 0;  // expected-error {{invalid invocation of method '~Linear' on object 'l' while it is in the 'unconsumed' state}}
+  return 1;  // expected-error {{'return' will never be executed}}
+}
+#pragma clang diagnostic pop
+
+int c() {
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wconsumed"
+  Linear l;
+  return 0; // expected-error {{invalid invocation of method '~Linear' on object 'l' while it is in the 'unconsumed' state}}
+  return 1; // expected-error {{'return' will never be executed}}
+#pragma clang diagnostic pop
+}
+
+int d() {
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wconsumed"
+#pragma clang diagnostic ignored "-Wunreachable-code-aggressive"
+  Linear l;
+  return 0; // expected-error {{invalid invocation of method '~Linear' on object 'l' while it is in the 'unconsumed' state}}
+  return 1; // Diagnostic is ignored
+}
+#pragma clang diagnostic pop
+
+int e() {
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wconsumed"
+#pragma clang diagnostic ignored "-Wunreachable-code-aggressive"
+  Linear l;
+  return 0; // expected-error {{invalid invocation of method '~Linear' on object 'l' while it is in the 'unconsumed' state}}
+  return 1; // Diagnostic is ignored
+#pragma clang diagnostic pop
+}
+
+int f() {
+  Linear l;
+  return 0; // No -Wconsumed diagnostic, analysis is not enabled
+  return 1; // expected-error {{'return' will never be executed}}
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunreachable-code-aggressive"
+}
+#pragma clang diagnostic pop	
+
+int g() {
+  Linear l;
+  return 0; // No -Wconsumed diagnostic, the diagnostic generated at } is not enabled on this line.
+  return 1; // expected-error {{'return' will never be executed}}
+#pragma clang diagnostic push
+#pragma clang diagnostic warning "-Wconsumed"
+}
+#pragma clang diagnostic pop
+
+int h() {
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wconsumed"
+#pragma clang diagnostic ignored "-Wunreachable-code-aggressive"
+#pragma clang diagnostic pop
+
+  Linear l;
+  return 0; // No -Wconsumed diagnostic, the diagnostic generated at } is not enabled on this line.
+  return 1; // expected-error {{'return' will never be executed}}
+}


Reviews:
user: login: erichkeane
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-18T16:35:55Z
user: login: Sirraide
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-18T16:43:26Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-18T16:43:40Z
user: login: Sirraide
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-18T16:43:56Z
user: login: Sirraide
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-18T16:44:34Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-18T16:44:49Z
user: login: Sirraide
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-18T16:44:57Z
user: login: erichkeane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-18T16:55:32Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-18T17:11:53Z
user: login: cor3ntin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T17:36:38Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-18T17:47:41Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-18T17:48:06Z
user: login: erichkeane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-18T18:04:23Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-18T18:13:08Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-18T18:14:15Z
user: login: haoNoQ
body: So from the user's perspective we're checking the flag twice: once at the `}` to see if we need to ramp up the analysis, then again at the diagnostic location to see if the diagnostic should be discarded. The warning will only be emitted if it's enabled at both source locations.

I like this. I think this is a very good practical tradeoff.

It behaves very counter-intuitively if you try to enable the diagnostic at one line with a pragma but it doesn't show up until you wrap your entire function in a pragma. But this is a very unlikely situation to occur naturally. Even if you do use the pragma to enable the diagnostic, why would you minimize the scope this way? (I also really like @cor3ntin's idea of emitting an explanatory warning if we can detect this case.)

The opposite situation, where the diagnostic is disabled on an individual line but enabled globally, is significantly more likely. And it is also the one that acts in an intuitive manner!

@ziqingluo-90 @jkorous-apple You might want to take a look at how this interacts with `-Wunsafe-buffer-usage`. If you still need to analyze the entire TU at once, the approach proposed in this patch probably won't work for you.
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-18T21:46:42Z
user: login: erichkeane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-18T21:52:31Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-21T11:03:50Z
user: login: erichkeane
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-22T13:15:48Z
user: login: Sirraide
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-22T16:38:25Z
user: login: shafik
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-06-05T21:31:36Z
user: login: steakhal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-06-06T09:28:29Z
user: login: shafik
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-06-06T14:45:52Z

Review comments:
diff_hunk: @@ -1202,6 +1202,30 @@ Clang also allows you to push and pop the current warning state. This is
 particularly useful when writing a header file that will be compiled by
 other people, because you don't know what warning flags they build with.
 
+Note that the following diagnostic groups, which are ones based on analyzing
path: clang/docs/UsersManual.rst
user: login: erichkeane
body: Ooof... this is a shocking/bad behavior.  I wish we could do better, but I of course know we probably couldn't?  UNLESS... we set the 'state' of the warning on the function during evaluation of the pragma, then checked that flag AS WELL at the end of TU to enable them?

Not sure if we want to do that in THIS patch, but an issue report with this idea isn't perhaps a terribad idea.
created_at: 2025-04-18T16:34:50Z
author_association: COLLABORATOR
diff_hunk: @@ -2504,24 +2505,28 @@ sema::AnalysisBasedWarnings::AnalysisBasedWarnings(Sema &s)
       NumUninitAnalysisVariables(0), MaxUninitAnalysisVariablesPerFunction(0),
       NumUninitAnalysisBlockVisits(0),
       MaxUninitAnalysisBlockVisitsPerFunction(0) {
+}
+
+// We need this here for unique_ptr with forward declared class.
+sema::AnalysisBasedWarnings::~AnalysisBasedWarnings() = default;
 
+sema::AnalysisBasedWarnings::Policy
+sema::AnalysisBasedWarnings::getPolicyInEffectAt(SourceLocation Loc) {
   using namespace diag;
   DiagnosticsEngine &D = S.getDiagnostics();
+  Policy P;
 
-  DefaultPolicy.enableCheckUnreachable =
-      isEnabled(D, warn_unreachable) || isEnabled(D, warn_unreachable_break) ||
-      isEnabled(D, warn_unreachable_return) ||
-      isEnabled(D, warn_unreachable_loop_increment);
+  P.enableCheckUnreachable = isEnabled(D, warn_unreachable, Loc) ||
path: clang/lib/Sema/AnalysisBasedWarnings.cpp
user: login: erichkeane
body: variadic--`isEnabled` would be pretty sweet for this...
created_at: 2025-04-18T16:35:35Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,77 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Werror=unreachable-code-aggressive %s
+
+// Test that analysis-based warnings honor #pragma diagnostic controls. These
+// diagnostics are triggered at the end of a function body, so the pragma needs
+// to be enabled through to the closing curly brace in order for the diagnostic
+// to be emitted.
+
+struct [[clang::consumable(unconsumed)]] Linear {
+  [[clang::return_typestate(unconsumed)]]
+  Linear() {}
+  [[clang::callable_when(consumed)]]
+  ~Linear() {}
+};
+
+int a() {	
+  Linear l;
+  return 0; // No -Wconsumed diagnostic, analysis is not enabled.
+  return 1; // expected-error {{'return' will never be executed}}
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wconsumed"
+int b() {
+  Linear l;
+  return 0;  // expected-error {{invalid invocation of method '~Linear' on object 'l' while it is in the 'unconsumed' state}}
+  return 1;  // expected-error {{'return' will never be executed}}
+}
+#pragma clang diagnostic pop
+
+int c() {
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wconsumed"
+  Linear l;
+  return 0; // No -Wconsumed diagnostic, analysis is disabled before the closing brace
+  return 1; // expected-error {{'return' will never be executed}}
+#pragma clang diagnostic pop
+}
+
+int d() {
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wconsumed"
+#pragma clang diagnostic ignored "-Wunreachable-code-aggressive"
+  Linear l;
+  return 0; // expected-error {{invalid invocation of method '~Linear' on object 'l' while it is in the 'unconsumed' state}}
+  return 1; // Diagnostic is ignored
+}
+#pragma clang diagnostic pop
+
+int e() {
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wconsumed"
+#pragma clang diagnostic ignored "-Wunreachable-code-aggressive"
+  Linear l;
+  return 0;
+  return 1;
+  // Both diagnostics are ignored because analysis is disabled before the
+  // closing brace.
+#pragma clang diagnostic pop
+}
+
+int f() {
+  Linear l;
+  return 0; // No -Wconsumed diagnostic, analysis is not enabled at } so it never runs to produce the diagnostic
+  return 1; // Diagnostic ignores because it was disabled at the }
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunreachable-code-aggressive"
+}
+#pragma clang diagnostic pop	
+
+int g() {
+  Linear l;
+  return 0; // No -Wconsumed diagnostic, the diagnostic generated at } is not enabled on this line.
path: clang/test/Analysis/pragma-diag-control.cpp
user: login: Sirraide
body: Is that mentioned anywhere in the documentation, because that wasn’t obvious to me (I would have thought that *just* enabling it for the `}` would have enabled it for the entire function because of how much emphasis the documentation is placing on the `}`).
created_at: 2025-04-18T16:43:19Z
author_association: MEMBER
diff_hunk: @@ -1202,6 +1202,30 @@ Clang also allows you to push and pop the current warning state. This is
 particularly useful when writing a header file that will be compiled by
 other people, because you don't know what warning flags they build with.
 
+Note that the following diagnostic groups, which are ones based on analyzing
path: clang/docs/UsersManual.rst
user: login: AaronBallman
body: I may be misunderstanding your suggestion, but I think that would end up in the same place. We'd see the push, set the state on the function, see the pop, clear the state on the function (which we'd need to do for correctness since these can be placed arbitrarily, including nesting), then get to the `}` and have missed the fact that the pragma was enabled for the lines we wanted to diagnose.

Really, the only way around this behavior that I think works is to not skip analysis-based warnings if the diagnostic is disabled at the end of the function. But that has some significant compile-time overhead because these are CFG-based analyses.
created_at: 2025-04-18T16:43:40Z
author_association: COLLABORATOR
diff_hunk: @@ -2504,24 +2505,28 @@ sema::AnalysisBasedWarnings::AnalysisBasedWarnings(Sema &s)
       NumUninitAnalysisVariables(0), MaxUninitAnalysisVariablesPerFunction(0),
       NumUninitAnalysisBlockVisits(0),
       MaxUninitAnalysisBlockVisitsPerFunction(0) {
+}
+
+// We need this here for unique_ptr with forward declared class.
+sema::AnalysisBasedWarnings::~AnalysisBasedWarnings() = default;
 
+sema::AnalysisBasedWarnings::Policy
+sema::AnalysisBasedWarnings::getPolicyInEffectAt(SourceLocation Loc) {
   using namespace diag;
   DiagnosticsEngine &D = S.getDiagnostics();
+  Policy P;
 
-  DefaultPolicy.enableCheckUnreachable =
-      isEnabled(D, warn_unreachable) || isEnabled(D, warn_unreachable_break) ||
-      isEnabled(D, warn_unreachable_return) ||
-      isEnabled(D, warn_unreachable_loop_increment);
+  P.enableCheckUnreachable = isEnabled(D, warn_unreachable, Loc) ||
path: clang/lib/Sema/AnalysisBasedWarnings.cpp
user: login: Sirraide
body: Definitely
created_at: 2025-04-18T16:43:56Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,77 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Werror=unreachable-code-aggressive %s
+
+// Test that analysis-based warnings honor #pragma diagnostic controls. These
+// diagnostics are triggered at the end of a function body, so the pragma needs
+// to be enabled through to the closing curly brace in order for the diagnostic
+// to be emitted.
+
+struct [[clang::consumable(unconsumed)]] Linear {
+  [[clang::return_typestate(unconsumed)]]
+  Linear() {}
+  [[clang::callable_when(consumed)]]
+  ~Linear() {}
+};
+
+int a() {	
+  Linear l;
+  return 0; // No -Wconsumed diagnostic, analysis is not enabled.
+  return 1; // expected-error {{'return' will never be executed}}
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wconsumed"
+int b() {
+  Linear l;
+  return 0;  // expected-error {{invalid invocation of method '~Linear' on object 'l' while it is in the 'unconsumed' state}}
+  return 1;  // expected-error {{'return' will never be executed}}
+}
+#pragma clang diagnostic pop
+
+int c() {
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wconsumed"
+  Linear l;
+  return 0; // No -Wconsumed diagnostic, analysis is disabled before the closing brace
+  return 1; // expected-error {{'return' will never be executed}}
+#pragma clang diagnostic pop
+}
+
+int d() {
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wconsumed"
+#pragma clang diagnostic ignored "-Wunreachable-code-aggressive"
+  Linear l;
+  return 0; // expected-error {{invalid invocation of method '~Linear' on object 'l' while it is in the 'unconsumed' state}}
+  return 1; // Diagnostic is ignored
+}
+#pragma clang diagnostic pop
+
+int e() {
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wconsumed"
+#pragma clang diagnostic ignored "-Wunreachable-code-aggressive"
+  Linear l;
+  return 0;
+  return 1;
+  // Both diagnostics are ignored because analysis is disabled before the
+  // closing brace.
+#pragma clang diagnostic pop
+}
+
+int f() {
+  Linear l;
+  return 0; // No -Wconsumed diagnostic, analysis is not enabled at } so it never runs to produce the diagnostic
+  return 1; // Diagnostic ignores because it was disabled at the }
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunreachable-code-aggressive"
+}
+#pragma clang diagnostic pop	
+
+int g() {
+  Linear l;
+  return 0; // No -Wconsumed diagnostic, the diagnostic generated at } is not enabled on this line.
path: clang/test/Analysis/pragma-diag-control.cpp
user: login: Sirraide
body: > in addition to requiring the diagnostic group to be enabled at the line being diagnosed

Ok yes, it is, I missed that the first time round.
created_at: 2025-04-18T16:44:34Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,77 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Werror=unreachable-code-aggressive %s
+
+// Test that analysis-based warnings honor #pragma diagnostic controls. These
+// diagnostics are triggered at the end of a function body, so the pragma needs
+// to be enabled through to the closing curly brace in order for the diagnostic
+// to be emitted.
+
+struct [[clang::consumable(unconsumed)]] Linear {
+  [[clang::return_typestate(unconsumed)]]
+  Linear() {}
+  [[clang::callable_when(consumed)]]
+  ~Linear() {}
+};
+
+int a() {	
+  Linear l;
+  return 0; // No -Wconsumed diagnostic, analysis is not enabled.
+  return 1; // expected-error {{'return' will never be executed}}
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wconsumed"
+int b() {
+  Linear l;
+  return 0;  // expected-error {{invalid invocation of method '~Linear' on object 'l' while it is in the 'unconsumed' state}}
+  return 1;  // expected-error {{'return' will never be executed}}
+}
+#pragma clang diagnostic pop
+
+int c() {
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wconsumed"
+  Linear l;
+  return 0; // No -Wconsumed diagnostic, analysis is disabled before the closing brace
+  return 1; // expected-error {{'return' will never be executed}}
+#pragma clang diagnostic pop
+}
+
+int d() {
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wconsumed"
+#pragma clang diagnostic ignored "-Wunreachable-code-aggressive"
+  Linear l;
+  return 0; // expected-error {{invalid invocation of method '~Linear' on object 'l' while it is in the 'unconsumed' state}}
+  return 1; // Diagnostic is ignored
+}
+#pragma clang diagnostic pop
+
+int e() {
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wconsumed"
+#pragma clang diagnostic ignored "-Wunreachable-code-aggressive"
+  Linear l;
+  return 0;
+  return 1;
+  // Both diagnostics are ignored because analysis is disabled before the
+  // closing brace.
+#pragma clang diagnostic pop
+}
+
+int f() {
+  Linear l;
+  return 0; // No -Wconsumed diagnostic, analysis is not enabled at } so it never runs to produce the diagnostic
+  return 1; // Diagnostic ignores because it was disabled at the }
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunreachable-code-aggressive"
+}
+#pragma clang diagnostic pop	
+
+int g() {
+  Linear l;
+  return 0; // No -Wconsumed diagnostic, the diagnostic generated at } is not enabled on this line.
path: clang/test/Analysis/pragma-diag-control.cpp
user: login: AaronBallman
body: It's in the docs: "... require the diagnostic group to be enabled at the end of the function body (after the closing ``}``) in order to run the analysis, in addition to requiring the diagnostic group to be enabled at the line being diagnosed:"
created_at: 2025-04-18T16:44:49Z
author_association: COLLABORATOR
diff_hunk: @@ -1202,6 +1202,30 @@ Clang also allows you to push and pop the current warning state. This is
 particularly useful when writing a header file that will be compiled by
 other people, because you don't know what warning flags they build with.
 
+Note that the following diagnostic groups, which are ones based on analyzing
path: clang/docs/UsersManual.rst
user: login: erichkeane
body: Ah, no my suggestion is "see the push(actually the enable), set the state on the function".  "See the pop, do nothing other than change the per-line state".  The state is "was this EVER enabled in this function", not "IS this enabled now".

Then at end of function, you check "was ever enabled" kinda thing, then do the analysis.  THEN only emit the warnings that were actually enabled based on effective-line.
created_at: 2025-04-18T16:55:32Z
author_association: COLLABORATOR
diff_hunk: @@ -1202,6 +1202,30 @@ Clang also allows you to push and pop the current warning state. This is
 particularly useful when writing a header file that will be compiled by
 other people, because you don't know what warning flags they build with.
 
+Note that the following diagnostic groups, which are ones based on analyzing
path: clang/docs/UsersManual.rst
user: login: AaronBallman
body: Ah thanks for the explanation, I'll play around with that idea real quick
created_at: 2025-04-18T17:11:53Z
author_association: COLLABORATOR
diff_hunk: @@ -1202,6 +1202,30 @@ Clang also allows you to push and pop the current warning state. This is
 particularly useful when writing a header file that will be compiled by
 other people, because you don't know what warning flags they build with.
 
+Note that the following diagnostic groups, which are ones based on analyzing
path: clang/docs/UsersManual.rst
user: login: cor3ntin
body: Alternatively, maybe we can detect and diagnose `#pragma clang diagnostic` inside a function being analyzed and warn that it's ignored
created_at: 2025-04-18T17:33:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2493,35 +2493,44 @@ class sema::AnalysisBasedWarnings::InterProceduralData {
   CalledOnceInterProceduralData CalledOnceData;
 };
 
-static unsigned isEnabled(DiagnosticsEngine &D, unsigned diag) {
-  return (unsigned)!D.isIgnored(diag, SourceLocation());
+static bool isEnabledImpl(DiagnosticsEngine &D, SourceLocation Loc,
+                          unsigned Diag) {
+  return !D.isIgnored(Diag, Loc);
 }
 
+template <typename... Ts>
+static bool isEnabled(DiagnosticsEngine &D, SourceLocation Loc, Ts... Diags) {
+  return (isEnabledImpl(D, Loc, Diags) || ...);
path: clang/lib/Sema/AnalysisBasedWarnings.cpp
user: login: cor3ntin
body: ```suggestion
  return (!D.isIgnored(Diags, Loc) || ...);
```

I don't think using another function gives us much given how simple the expression is
created_at: 2025-04-18T17:35:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2493,35 +2493,44 @@ class sema::AnalysisBasedWarnings::InterProceduralData {
   CalledOnceInterProceduralData CalledOnceData;
 };
 
-static unsigned isEnabled(DiagnosticsEngine &D, unsigned diag) {
-  return (unsigned)!D.isIgnored(diag, SourceLocation());
+static bool isEnabledImpl(DiagnosticsEngine &D, SourceLocation Loc,
+                          unsigned Diag) {
+  return !D.isIgnored(Diag, Loc);
 }
 
+template <typename... Ts>
+static bool isEnabled(DiagnosticsEngine &D, SourceLocation Loc, Ts... Diags) {
+  return (isEnabledImpl(D, Loc, Diags) || ...);
+};
+
 sema::AnalysisBasedWarnings::AnalysisBasedWarnings(Sema &s)
     : S(s), IPData(std::make_unique<InterProceduralData>()),
       NumFunctionsAnalyzed(0), NumFunctionsWithBadCFGs(0), NumCFGBlocks(0),
       MaxCFGBlocksPerFunction(0), NumUninitAnalysisFunctions(0),
       NumUninitAnalysisVariables(0), MaxUninitAnalysisVariablesPerFunction(0),
       NumUninitAnalysisBlockVisits(0),
       MaxUninitAnalysisBlockVisitsPerFunction(0) {
+}
 
+// We need this here for unique_ptr with forward declared class.
+sema::AnalysisBasedWarnings::~AnalysisBasedWarnings() = default;
+
+sema::AnalysisBasedWarnings::Policy
+sema::AnalysisBasedWarnings::getPolicyInEffectAt(SourceLocation Loc) {
   using namespace diag;
   DiagnosticsEngine &D = S.getDiagnostics();
+  Policy P;
 
-  DefaultPolicy.enableCheckUnreachable =
-      isEnabled(D, warn_unreachable) || isEnabled(D, warn_unreachable_break) ||
-      isEnabled(D, warn_unreachable_return) ||
-      isEnabled(D, warn_unreachable_loop_increment);
+  P.enableCheckUnreachable =
+      isEnabled(D, Loc, warn_unreachable, warn_unreachable_break,
+                warn_unreachable_return, warn_unreachable_loop_increment);
path: clang/lib/Sema/AnalysisBasedWarnings.cpp
user: login: cor3ntin
body: no `diag::` namespace?
created_at: 2025-04-18T17:36:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2493,35 +2493,44 @@ class sema::AnalysisBasedWarnings::InterProceduralData {
   CalledOnceInterProceduralData CalledOnceData;
 };
 
-static unsigned isEnabled(DiagnosticsEngine &D, unsigned diag) {
-  return (unsigned)!D.isIgnored(diag, SourceLocation());
+static bool isEnabledImpl(DiagnosticsEngine &D, SourceLocation Loc,
+                          unsigned Diag) {
+  return !D.isIgnored(Diag, Loc);
 }
 
+template <typename... Ts>
+static bool isEnabled(DiagnosticsEngine &D, SourceLocation Loc, Ts... Diags) {
+  return (isEnabledImpl(D, Loc, Diags) || ...);
+};
+
 sema::AnalysisBasedWarnings::AnalysisBasedWarnings(Sema &s)
     : S(s), IPData(std::make_unique<InterProceduralData>()),
       NumFunctionsAnalyzed(0), NumFunctionsWithBadCFGs(0), NumCFGBlocks(0),
       MaxCFGBlocksPerFunction(0), NumUninitAnalysisFunctions(0),
       NumUninitAnalysisVariables(0), MaxUninitAnalysisVariablesPerFunction(0),
       NumUninitAnalysisBlockVisits(0),
       MaxUninitAnalysisBlockVisitsPerFunction(0) {
+}
 
+// We need this here for unique_ptr with forward declared class.
+sema::AnalysisBasedWarnings::~AnalysisBasedWarnings() = default;
+
+sema::AnalysisBasedWarnings::Policy
+sema::AnalysisBasedWarnings::getPolicyInEffectAt(SourceLocation Loc) {
   using namespace diag;
   DiagnosticsEngine &D = S.getDiagnostics();
+  Policy P;
 
-  DefaultPolicy.enableCheckUnreachable =
-      isEnabled(D, warn_unreachable) || isEnabled(D, warn_unreachable_break) ||
-      isEnabled(D, warn_unreachable_return) ||
-      isEnabled(D, warn_unreachable_loop_increment);
+  P.enableCheckUnreachable =
+      isEnabled(D, Loc, warn_unreachable, warn_unreachable_break,
+                warn_unreachable_return, warn_unreachable_loop_increment);
path: clang/lib/Sema/AnalysisBasedWarnings.cpp
user: login: AaronBallman
body: `using namespace diag;` is slightly above (preexisting)
created_at: 2025-04-18T17:47:41Z
author_association: COLLABORATOR
diff_hunk: @@ -2493,35 +2493,44 @@ class sema::AnalysisBasedWarnings::InterProceduralData {
   CalledOnceInterProceduralData CalledOnceData;
 };
 
-static unsigned isEnabled(DiagnosticsEngine &D, unsigned diag) {
-  return (unsigned)!D.isIgnored(diag, SourceLocation());
+static bool isEnabledImpl(DiagnosticsEngine &D, SourceLocation Loc,
+                          unsigned Diag) {
+  return !D.isIgnored(Diag, Loc);
 }
 
+template <typename... Ts>
+static bool isEnabled(DiagnosticsEngine &D, SourceLocation Loc, Ts... Diags) {
+  return (isEnabledImpl(D, Loc, Diags) || ...);
path: clang/lib/Sema/AnalysisBasedWarnings.cpp
user: login: AaronBallman
body: Heh fair point, I'll adjust this.
created_at: 2025-04-18T17:48:05Z
author_association: COLLABORATOR
diff_hunk: @@ -2493,9 +2493,10 @@ class sema::AnalysisBasedWarnings::InterProceduralData {
   CalledOnceInterProceduralData CalledOnceData;
 };
 
-static unsigned isEnabled(DiagnosticsEngine &D, unsigned diag) {
-  return (unsigned)!D.isIgnored(diag, SourceLocation());
-}
+template <typename... Ts>
+static bool isEnabled(DiagnosticsEngine &D, SourceLocation Loc, Ts... Diags) {
path: clang/lib/Sema/AnalysisBasedWarnings.cpp
user: login: erichkeane
body: Do we want to rename this to something like `areAnyEnabled` or something?  
created_at: 2025-04-18T18:01:43Z
author_association: COLLABORATOR
diff_hunk: @@ -2504,23 +2505,37 @@ sema::AnalysisBasedWarnings::AnalysisBasedWarnings(Sema &s)
       NumUninitAnalysisVariables(0), MaxUninitAnalysisVariablesPerFunction(0),
       NumUninitAnalysisBlockVisits(0),
       MaxUninitAnalysisBlockVisitsPerFunction(0) {
+}
+
+// We need this here for unique_ptr with forward declared class.
+sema::AnalysisBasedWarnings::~AnalysisBasedWarnings() = default;
 
+sema::AnalysisBasedWarnings::Policy
+sema::AnalysisBasedWarnings::getPolicyInEffectAt(SourceLocation Loc) {
   using namespace diag;
   DiagnosticsEngine &D = S.getDiagnostics();
+  Policy P;
 
-  DefaultPolicy.enableCheckUnreachable =
-      isEnabled(D, warn_unreachable) || isEnabled(D, warn_unreachable_break) ||
-      isEnabled(D, warn_unreachable_return) ||
-      isEnabled(D, warn_unreachable_loop_increment);
+  // Note: The enabled checks should be kept in sync with the switch in
+  // SemaPPCallbacks::PragmaDiagnostic().
+  P.enableCheckUnreachable =
+      PolicyOverrides.enableCheckUnreachable ||
+      isEnabled(D, Loc, warn_unreachable, warn_unreachable_break,
+                warn_unreachable_return, warn_unreachable_loop_increment);
 
-  DefaultPolicy.enableThreadSafetyAnalysis = isEnabled(D, warn_double_lock);
+  P.enableThreadSafetyAnalysis = PolicyOverrides.enableThreadSafetyAnalysis ||
+                                 isEnabled(D, Loc, warn_double_lock);
 
-  DefaultPolicy.enableConsumedAnalysis =
-      isEnabled(D, warn_use_in_invalid_state);
+  P.enableConsumedAnalysis = PolicyOverrides.enableConsumedAnalysis ||
+                             isEnabled(D, Loc, warn_use_in_invalid_state);
+  return P;
 }
 
-// We need this here for unique_ptr with forward declared class.
-sema::AnalysisBasedWarnings::~AnalysisBasedWarnings() = default;
+void sema::AnalysisBasedWarnings::clearOverrides() {
+  PolicyOverrides.enableCheckUnreachable = false;
path: clang/lib/Sema/AnalysisBasedWarnings.cpp
user: login: erichkeane
body: Tests show this working but.... where on earth are these set?  
created_at: 2025-04-18T18:04:18Z
author_association: COLLABORATOR
diff_hunk: @@ -2504,23 +2505,37 @@ sema::AnalysisBasedWarnings::AnalysisBasedWarnings(Sema &s)
       NumUninitAnalysisVariables(0), MaxUninitAnalysisVariablesPerFunction(0),
       NumUninitAnalysisBlockVisits(0),
       MaxUninitAnalysisBlockVisitsPerFunction(0) {
+}
+
+// We need this here for unique_ptr with forward declared class.
+sema::AnalysisBasedWarnings::~AnalysisBasedWarnings() = default;
 
+sema::AnalysisBasedWarnings::Policy
+sema::AnalysisBasedWarnings::getPolicyInEffectAt(SourceLocation Loc) {
   using namespace diag;
   DiagnosticsEngine &D = S.getDiagnostics();
+  Policy P;
 
-  DefaultPolicy.enableCheckUnreachable =
-      isEnabled(D, warn_unreachable) || isEnabled(D, warn_unreachable_break) ||
-      isEnabled(D, warn_unreachable_return) ||
-      isEnabled(D, warn_unreachable_loop_increment);
+  // Note: The enabled checks should be kept in sync with the switch in
+  // SemaPPCallbacks::PragmaDiagnostic().
+  P.enableCheckUnreachable =
+      PolicyOverrides.enableCheckUnreachable ||
+      isEnabled(D, Loc, warn_unreachable, warn_unreachable_break,
+                warn_unreachable_return, warn_unreachable_loop_increment);
 
-  DefaultPolicy.enableThreadSafetyAnalysis = isEnabled(D, warn_double_lock);
+  P.enableThreadSafetyAnalysis = PolicyOverrides.enableThreadSafetyAnalysis ||
+                                 isEnabled(D, Loc, warn_double_lock);
 
-  DefaultPolicy.enableConsumedAnalysis =
-      isEnabled(D, warn_use_in_invalid_state);
+  P.enableConsumedAnalysis = PolicyOverrides.enableConsumedAnalysis ||
+                             isEnabled(D, Loc, warn_use_in_invalid_state);
+  return P;
 }
 
-// We need this here for unique_ptr with forward declared class.
-sema::AnalysisBasedWarnings::~AnalysisBasedWarnings() = default;
+void sema::AnalysisBasedWarnings::clearOverrides() {
+  PolicyOverrides.enableCheckUnreachable = false;
path: clang/lib/Sema/AnalysisBasedWarnings.cpp
user: login: AaronBallman
body: The overrides are set in `SemaPPCallback::PragmaDiagnostic()`, the others come from `AnalysisBasedWarnings::getPolicyInEffectAt()`
created_at: 2025-04-18T18:13:08Z
author_association: COLLABORATOR
diff_hunk: @@ -2493,9 +2493,10 @@ class sema::AnalysisBasedWarnings::InterProceduralData {
   CalledOnceInterProceduralData CalledOnceData;
 };
 
-static unsigned isEnabled(DiagnosticsEngine &D, unsigned diag) {
-  return (unsigned)!D.isIgnored(diag, SourceLocation());
-}
+template <typename... Ts>
+static bool isEnabled(DiagnosticsEngine &D, SourceLocation Loc, Ts... Diags) {
path: clang/lib/Sema/AnalysisBasedWarnings.cpp
user: login: AaronBallman
body: Good call, renamed
created_at: 2025-04-18T18:14:15Z
author_association: COLLABORATOR
diff_hunk: @@ -202,6 +202,43 @@ class SemaPPCallbacks : public PPCallbacks {
       break;
     }
   }
+  void PragmaDiagnostic(SourceLocation Loc, StringRef Namespace,
+                        diag::Severity Mapping, StringRef Str) override {
+    // If one of the analysis-based diagnostics was enabled while processing
+    // a function, we want to note it in the analysis-based warnings so they
+    // can be run at the end of the function body even if the analysis warnings
+    // are disabled at that point.
+    SmallVector<diag::kind, 256> GroupDiags;
+    diag::Flavor Flavor =
+        Str[1] == 'W' ? diag::Flavor::WarningOrError : diag::Flavor::Remark;
+    StringRef Group = Str.substr(2);
+
+    if (S->PP.getDiagnostics().getDiagnosticIDs()->getDiagnosticsInGroup(
+            Flavor, Group, GroupDiags))
+      return;
+
+    for (diag::kind K : GroupDiags) {
+      // Note: the cases in this switch should be kept in sync with the
+      // diagnostics in AnalysisBasedWarnings::getPolicyInEffectAt().
+      AnalysisBasedWarnings::Policy &Override =
+          S->AnalysisWarnings.getPolicyOverrides();
+      switch (K) {
+      default: break;
path: clang/lib/Sema/Sema.cpp
user: login: haoNoQ
body: So every new analysis-based warning needs to be added to this list right? How weird are the consequences of forgetting it?

It might be a good idea to make a unified list of analysis-based warnings. (Or like, make it a flag in `Diagnostic...Kinds.td`.) Then it may be possible to build a runtime assertion to confirm that as long as `AnalysisBasedWarnings::IssueWarnings()` is on the stack, all warnings emitted by clang are on that list.

(Probably out of scope for this patch. Sounds like a lot of work. There could be easier ways to check this. Could also be too restrictive in practice.)
created_at: 2025-04-18T21:25:55Z
author_association: COLLABORATOR
diff_hunk: @@ -202,6 +202,43 @@ class SemaPPCallbacks : public PPCallbacks {
       break;
     }
   }
+  void PragmaDiagnostic(SourceLocation Loc, StringRef Namespace,
+                        diag::Severity Mapping, StringRef Str) override {
+    // If one of the analysis-based diagnostics was enabled while processing
+    // a function, we want to note it in the analysis-based warnings so they
+    // can be run at the end of the function body even if the analysis warnings
+    // are disabled at that point.
+    SmallVector<diag::kind, 256> GroupDiags;
+    diag::Flavor Flavor =
+        Str[1] == 'W' ? diag::Flavor::WarningOrError : diag::Flavor::Remark;
+    StringRef Group = Str.substr(2);
+
+    if (S->PP.getDiagnostics().getDiagnosticIDs()->getDiagnosticsInGroup(
+            Flavor, Group, GroupDiags))
+      return;
+
+    for (diag::kind K : GroupDiags) {
+      // Note: the cases in this switch should be kept in sync with the
+      // diagnostics in AnalysisBasedWarnings::getPolicyInEffectAt().
+      AnalysisBasedWarnings::Policy &Override =
+          S->AnalysisWarnings.getPolicyOverrides();
+      switch (K) {
+      default: break;
path: clang/lib/Sema/Sema.cpp
user: login: erichkeane
body: At the moment, yes.  As a follow-up, I would wonder if we could auto-generate this from tablegen.  

Though, in this patch, we could perhaps instead of doing this override, have the policy store a LIST of override diagnostics, that we then check are in the list.  So this would instead of this whole switch do:

`Override.DiagOverrideList.push_back(K);`

That would work for ALL diagnostics, at the expense of a somewhat significant size increase.
created_at: 2025-04-18T21:52:31Z
author_association: COLLABORATOR
diff_hunk: @@ -202,6 +202,43 @@ class SemaPPCallbacks : public PPCallbacks {
       break;
     }
   }
+  void PragmaDiagnostic(SourceLocation Loc, StringRef Namespace,
+                        diag::Severity Mapping, StringRef Str) override {
+    // If one of the analysis-based diagnostics was enabled while processing
+    // a function, we want to note it in the analysis-based warnings so they
+    // can be run at the end of the function body even if the analysis warnings
+    // are disabled at that point.
+    SmallVector<diag::kind, 256> GroupDiags;
+    diag::Flavor Flavor =
+        Str[1] == 'W' ? diag::Flavor::WarningOrError : diag::Flavor::Remark;
+    StringRef Group = Str.substr(2);
+
+    if (S->PP.getDiagnostics().getDiagnosticIDs()->getDiagnosticsInGroup(
+            Flavor, Group, GroupDiags))
+      return;
+
+    for (diag::kind K : GroupDiags) {
+      // Note: the cases in this switch should be kept in sync with the
+      // diagnostics in AnalysisBasedWarnings::getPolicyInEffectAt().
+      AnalysisBasedWarnings::Policy &Override =
+          S->AnalysisWarnings.getPolicyOverrides();
+      switch (K) {
+      default: break;
path: clang/lib/Sema/Sema.cpp
user: login: AaronBallman
body: > So every new analysis-based warning needs to be added to this list right? 

Correct, but thankfully we don't add those very often.

> How weird are the consequences of forgetting it?

If you add it to AnalysisBasedDiagnostics.cpp and not to Sema.cpp, the analysis will work everywhere except for pragma regions like this. If you add it to Sema.cpp but not AnalysisBasicDiagnostics.cpp, the only time the analysis will work will be within pragma regions. So either is weird, but one seems more likely to slip through the cracks than the other.

> It might be a good idea to make a unified list of analysis-based warnings. (Or like, make it a flag in Diagnostic...Kinds.td.) 

Yeah, it would be nice if this could be tablegenned. But there are so few analysis-based warnings and there's no good mechanism to know when you forget to add the correct marking to the .td file that I can think of so far.

> Probably out of scope for this patch.

Yeah, I'd prefer to punt on that. :-)
created_at: 2025-04-21T11:03:50Z
author_association: COLLABORATOR
diff_hunk: @@ -16597,7 +16597,8 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc,
   BD->setCaptures(Context, Captures, BSI->CXXThisCaptureIndex != 0);
 
   // Pop the block scope now but keep it alive to the end of this function.
-  AnalysisBasedWarnings::Policy WP = AnalysisWarnings.getDefaultPolicy();
+  AnalysisBasedWarnings::Policy WP =
+      AnalysisWarnings.getPolicyInEffectAt(Body->getEndLoc());
path: clang/lib/Sema/SemaExpr.cpp
user: login: shafik
body: Static analysis points out that `Body` is nullptr checked above and so we should not be unconditionally dereferencing it here. 
created_at: 2025-06-05T21:31:32Z
author_association: COLLABORATOR
diff_hunk: @@ -16597,7 +16597,8 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc,
   BD->setCaptures(Context, Captures, BSI->CXXThisCaptureIndex != 0);
 
   // Pop the block scope now but keep it alive to the end of this function.
-  AnalysisBasedWarnings::Policy WP = AnalysisWarnings.getDefaultPolicy();
+  AnalysisBasedWarnings::Policy WP =
+      AnalysisWarnings.getPolicyInEffectAt(Body->getEndLoc());
path: clang/lib/Sema/SemaExpr.cpp
user: login: steakhal
body: I think the statement `BD->setBody(cast<CompoundStmt>(Body));`  dominates all checks to `Body`, consequently, I don't think `Body` can be null. I'd say, the checks for `Body` are redundant.

EDIT: `cast<CompoundStmt>(Body)` should assert that `Body` is nonnull.
created_at: 2025-06-06T09:28:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -16597,7 +16597,8 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc,
   BD->setCaptures(Context, Captures, BSI->CXXThisCaptureIndex != 0);
 
   // Pop the block scope now but keep it alive to the end of this function.
-  AnalysisBasedWarnings::Policy WP = AnalysisWarnings.getDefaultPolicy();
+  AnalysisBasedWarnings::Policy WP =
+      AnalysisWarnings.getPolicyInEffectAt(Body->getEndLoc());
path: clang/lib/Sema/SemaExpr.cpp
user: login: shafik
body: I missed the `cast`, yeah not bug then.
created_at: 2025-06-06T14:45:52Z
author_association: COLLABORATOR

PR comments:
user: login: llvmbot
created_at: 2025-04-18T16:32:52Z
body: 
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-static-analyzer-1
@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-analysis

Author: Aaron Ballman (AaronBallman)

<details>
<summary>Changes</summary>

Previously, analysis-based diagnostics (like -Wconsumed) had to be enabled at file scope in order to be run at the end of each function body. This meant that they did not respect #pragma clang diagnostic enabling or disabling the diagnostic.

Now, these pragmas can control the diagnostic emission. However, the behavior requires some explanation (which is why the documentation is being updated). Analysis-based warnings run at the end of a function body, so the diagnostic needs to be enabled after the closing } for the function in order to run the analysis at all. However, if the analysis emits a diagnostic for line in the function, the diagnostic still needs to be enabled on that specific line in order to be shown to the user.

Fixes #<!-- -->42199

---
Full diff: https://github.com/llvm/llvm-project/pull/136323.diff


7 Files Affected:

- (modified) clang/docs/ReleaseNotes.rst (+3) 
- (modified) clang/docs/UsersManual.rst (+24) 
- (modified) clang/include/clang/Sema/AnalysisBasedWarnings.h (+2-2) 
- (modified) clang/lib/Sema/AnalysisBasedWarnings.cpp (+17-12) 
- (modified) clang/lib/Sema/SemaDecl.cpp (+7-1) 
- (modified) clang/lib/Sema/SemaExpr.cpp (+2-1) 
- (added) clang/test/Analysis/pragma-diag-control.cpp (+77) 


``````````diff
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index f5cd1fbeabcfe..8d139123b47ce 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -395,6 +395,9 @@ Improvements to Clang's diagnostics
   constructors to initialize their non-modifiable members. The diagnostic is
   not new; being controlled via a warning group is what's new. Fixes #GH41104
 
+- Analysis-based diagnostics (like ``-Wconsumed`` or ``-Wunreachable-code``)
+  can now be correctly controlled by ``#pragma clang diagnostic``. #GH42199
+
 Improvements to Clang's time-trace
 ----------------------------------
 
diff --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst
index 69256527f40c9..0ba4e2dedd4eb 100644
--- a/clang/docs/UsersManual.rst
+++ b/clang/docs/UsersManual.rst
@@ -1202,6 +1202,30 @@ Clang also allows you to push and pop the current warning state. This is
 particularly useful when writing a header file that will be compiled by
 other people, because you don't know what warning flags they build with.
 
+Note that the following diagnostic groups, which are ones based on analyzing
+the control flow graph for a function, require the diagnostic group to be
+enabled at the end of the function body (after the closing ``}``) in order to
+run the analysis, in addition to requiring the diagnostic group to be enabled
+at the line being diagnosed:
+
+  * ``-Wconsumed``
+  * ``-Wthread-safety-analysis``
+  * ``-Wunreachable-code``, ``-Wunreachable-code-aggressive``, or warnings
+    controlled by either of those flags
+
+thus, it is generally better for a ``push`` and ``pop`` pair of pragmas
+controlling behavior for an entire function be placed outside of the function
+body rather than within it. e.g.,
+
+.. code-block:: c
+
+  #pragma clang diagnostic push
+  #pragma clang diagnostic warning "-Wwhatever"
+  int d() {
+    // Better to put the pragmas outside of the function rather than within it.
+  }
+  #pragma clang diagnostic pop
+
 In the below example :option:`-Wextra-tokens` is ignored for only a single line
 of code, after which the diagnostics return to whatever state had previously
 existed.
diff --git a/clang/include/clang/Sema/AnalysisBasedWarnings.h b/clang/include/clang/Sema/AnalysisBasedWarnings.h
index aafe227b84084..49023863f4503 100644
--- a/clang/include/clang/Sema/AnalysisBasedWarnings.h
+++ b/clang/include/clang/Sema/AnalysisBasedWarnings.h
@@ -49,7 +49,6 @@ class AnalysisBasedWarnings {
 
 private:
   Sema &S;
-  Policy DefaultPolicy;
 
   class InterProceduralData;
   std::unique_ptr<InterProceduralData> IPData;
@@ -103,7 +102,8 @@ class AnalysisBasedWarnings {
   // Issue warnings that require whole-translation-unit analysis.
   void IssueWarnings(TranslationUnitDecl *D);
 
-  Policy getDefaultPolicy() { return DefaultPolicy; }
+  // Gets the default policy which is in effect at the given source location.
+  Policy getPolicyInEffectAt(SourceLocation Loc);
 
   void PrintStats() const;
 };
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 34045a7274021..72962acee66d3 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2493,8 +2493,9 @@ class sema::AnalysisBasedWarnings::InterProceduralData {
   CalledOnceInterProceduralData CalledOnceData;
 };
 
-static unsigned isEnabled(DiagnosticsEngine &D, unsigned diag) {
-  return (unsigned)!D.isIgnored(diag, SourceLocation());
+static bool isEnabled(DiagnosticsEngine &D, unsigned diag,
+                          SourceLocation Loc) {
+  return !D.isIgnored(diag, Loc);
 }
 
 sema::AnalysisBasedWarnings::AnalysisBasedWarnings(Sema &s)
@@ -2504,24 +2505,28 @@ sema::AnalysisBasedWarnings::AnalysisBasedWarnings(Sema &s)
       NumUninitAnalysisVariables(0), MaxUninitAnalysisVariablesPerFunction(0),
       NumUninitAnalysisBlockVisits(0),
       MaxUninitAnalysisBlockVisitsPerFunction(0) {
+}
+
+// We need this here for unique_ptr with forward declared class.
+sema::AnalysisBasedWarnings::~AnalysisBasedWarnings() = default;
 
+sema::AnalysisBasedWarnings::Policy
+sema::AnalysisBasedWarnings::getPolicyInEffectAt(SourceLocation Loc) {
   using namespace diag;
   DiagnosticsEngine &D = S.getDiagnostics();
+  Policy P;
 
-  DefaultPolicy.enableCheckUnreachable =
-      isEnabled(D, warn_unreachable) || isEnabled(D, warn_unreachable_break) ||
-      isEnabled(D, warn_unreachable_return) ||
-      isEnabled(D, warn_unreachable_loop_increment);
+  P.enableCheckUnreachable = isEnabled(D, warn_unreachable, Loc) ||
+                             isEnabled(D, warn_unreachable_break, Loc) ||
+                             isEnabled(D, warn_unreachable_return, Loc) ||
+                             isEnabled(D, warn_unreachable_loop_increment, Loc);
 
-  DefaultPolicy.enableThreadSafetyAnalysis = isEnabled(D, warn_double_lock);
+  P.enableThreadSafetyAnalysis = isEnabled(D, warn_double_lock, Loc);
 
-  DefaultPolicy.enableConsumedAnalysis =
-      isEnabled(D, warn_use_in_invalid_state);
+  P.enableConsumedAnalysis = isEnabled(D, warn_use_in_invalid_state, Loc);
+  return P;
 }
 
-// We need this here for unique_ptr with forward declared class.
-sema::AnalysisBasedWarnings::~AnalysisBasedWarnings() = default;
-
 static void flushDiagnostics(Sema &S, const sema::FunctionScopeInfo *fscope) {
   for (const auto &D : fscope->PossiblyUnreachableDiags)
     S.Diag(D.Loc, D.PD);
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 46933c5c43168..d28a2107d58a9 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -16150,7 +16150,13 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body,
   if (FSI->UsesFPIntrin && FD && !FD->hasAttr<StrictFPAttr>())
     FD->addAttr(StrictFPAttr::CreateImplicit(Context));
 
-  sema::AnalysisBasedWarnings::Policy WP = AnalysisWarnings.getDefaultPolicy();
+  SourceLocation AnalysisLoc;
+  if (Body)
+    AnalysisLoc = Body->getEndLoc();
+  else if (FD)
+    AnalysisLoc = FD->getEndLoc();
+  sema::AnalysisBasedWarnings::Policy WP =
+      AnalysisWarnings.getPolicyInEffectAt(AnalysisLoc);
   sema::AnalysisBasedWarnings::Policy *ActivePolicy = nullptr;
 
   // If we skip function body, we can't tell if a function is a coroutine.
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 9cd1a7e45fb64..a9f353be07ad2 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -16574,7 +16574,8 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc,
   BD->setCaptures(Context, Captures, BSI->CXXThisCaptureIndex != 0);
 
   // Pop the block scope now but keep it alive to the end of this function.
-  AnalysisBasedWarnings::Policy WP = AnalysisWarnings.getDefaultPolicy();
+  AnalysisBasedWarnings::Policy WP =
+      AnalysisWarnings.getPolicyInEffectAt(Body->getEndLoc());
   PoppedFunctionScopePtr ScopeRAII = PopFunctionScopeInfo(&WP, BD, BlockTy);
 
   BlockExpr *Result = new (Context)
diff --git a/clang/test/Analysis/pragma-diag-control.cpp b/clang/test/Analysis/pragma-diag-control.cpp
new file mode 100644
index 0000000000000..c41793c15bbcc
--- /dev/null
+++ b/clang/test/Analysis/pragma-diag-control.cpp
@@ -0,0 +1,77 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Werror=unreachable-code-aggressive %s
+
+// Test that analysis-based warnings honor #pragma diagnostic controls. These
+// diagnostics are triggered at the end of a function body, so the pragma needs
+// to be enabled through to the closing curly brace in order for the diagnostic
+// to be emitted.
+
+struct [[clang::consumable(unconsumed)]] Linear {
+  [[clang::return_typestate(unconsumed)]]
+  Linear() {}
+  [[clang::callable_when(consumed)]]
+  ~Linear() {}
+};
+
+int a() {	
+  Linear l;
+  return 0; // No -Wconsumed diagnostic, analysis is not enabled.
+  return 1; // expected-error {{'return' will never be executed}}
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wconsumed"
+int b() {
+  Linear l;
+  return 0;  // expected-error {{invalid invocation of method '~Linear' on object 'l' while it is in the 'unconsumed' state}}
+  return 1;  // expected-error {{'return' will never be executed}}
+}
+#pragma clang diagnostic pop
+
+int c() {
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wconsumed"
+  Linear l;
+  return 0; // No -Wconsumed diagnostic, analysis is disabled before the closing brace
+  return 1; // expected-error {{'return' will never be executed}}
+#pragma clang diagnostic pop
+}
+
+int d() {
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wconsumed"
+#pragma clang diagnostic ignored "-Wunreachable-code-aggressive"
+  Linear l;
+  return 0; // expected-error {{invalid invocation of method '~Linear' on object 'l' while it is in the 'unconsumed' state}}
+  return 1; // Diagnostic is ignored
+}
+#pragma clang diagnostic pop
+
+int e() {
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wconsumed"
+#pragma clang diagnostic ignored "-Wunreachable-code-aggressive"
+  Linear l;
+  return 0;
+  return 1;
+  // Both diagnostics are ignored because analysis is disabled before the
+  // closing brace.
+#pragma clang diagnostic pop
+}
+
+int f() {
+  Linear l;
+  return 0; // No -Wconsumed diagnostic, analysis is not enabled at } so it never runs to produce the diagnostic
+  return 1; // Diagnostic ignores because it was disabled at the }
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunreachable-code-aggressive"
+}
+#pragma clang diagnostic pop	
+
+int g() {
+  Linear l;
+  return 0; // No -Wconsumed diagnostic, the diagnostic generated at } is not enabled on this line.
+  return 1; // expected-error {{'return' will never be executed}}
+#pragma clang diagnostic push
+#pragma clang diagnostic warning "-Wconsumed"
+}
+#pragma clang diagnostic pop

``````````

</details>

author_association: MEMBER
user: login: github-actions[bot]
created_at: 2025-04-18T16:34:57Z
body: <!--LLVM CODE FORMAT COMMENT: {clang-format}-->


:warning: C/C++ code formatter, clang-format found issues in your code. :warning:

<details>
<summary>
You can test this locally with the following command:
</summary>

``````````bash
git-clang-format --diff HEAD~1 HEAD --extensions cpp,h -- clang/test/Analysis/pragma-diag-control.cpp clang/include/clang/Sema/AnalysisBasedWarnings.h clang/lib/Sema/AnalysisBasedWarnings.cpp clang/lib/Sema/Sema.cpp clang/lib/Sema/SemaDecl.cpp clang/lib/Sema/SemaExpr.cpp
``````````

</details>

<details>
<summary>
View the diff from clang-format here.
</summary>

``````````diff
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 3d8eaf035..42c171570 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2504,8 +2504,7 @@ sema::AnalysisBasedWarnings::AnalysisBasedWarnings(Sema &s)
       MaxCFGBlocksPerFunction(0), NumUninitAnalysisFunctions(0),
       NumUninitAnalysisVariables(0), MaxUninitAnalysisVariablesPerFunction(0),
       NumUninitAnalysisBlockVisits(0),
-      MaxUninitAnalysisBlockVisitsPerFunction(0) {
-}
+      MaxUninitAnalysisBlockVisitsPerFunction(0) {}
 
 // We need this here for unique_ptr with forward declared class.
 sema::AnalysisBasedWarnings::~AnalysisBasedWarnings() = default;
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 403960161..3a2cb992f 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -223,7 +223,8 @@ public:
       AnalysisBasedWarnings::Policy &Override =
           S->AnalysisWarnings.getPolicyOverrides();
       switch (K) {
-      default: break;
+      default:
+        break;
       case diag::warn_unreachable:
       case diag::warn_unreachable_break:
       case diag::warn_unreachable_return:

``````````

</details>

author_association: NONE
user: login: erichkeane
created_at: 2025-04-18T21:50:07Z
body: > So from the user's perspective we're checking the flag twice: once at the `}` to see if we need to ramp up the analysis, then again at the diagnostic location to see if the diagnostic should be discarded. The warning will only be emitted if it's enabled at both source locations.
> 
> I like this. I think this is a very good practical tradeoff.
> 
> It behaves very counter-intuitively if you try to enable the diagnostic at one line with a pragma but it doesn't show up until you wrap your entire function in a pragma. But this is a very unlikely situation to occur naturally. Even if you do use the pragma to enable the diagnostic, why would you minimize the scope this way? (I also really like @cor3ntin's idea of emitting an explanatory warning if we can detect this case.)
>

The current patch actually WILL work with it ONLY enabled on the line inside of the function, which I think is much more intuitive.  The previous one (before Aaron implemented my tracking suggestion) did as you mentioned.  My proposal was to track throughout the function if it was EVER turned on, so start the analysis if it is EVER turned on, then emit it if it is in the source-lcoation at the place of diagnostic.
 
> The opposite situation, where the diagnostic is disabled on an individual line but enabled globally, is significantly more likely. And it is also the one that acts in an intuitive manner!
> 
> @ziqingluo-90 @jkorous-apple You might want to take a look at how this interacts with `-Wunsafe-buffer-usage`. If you still need to analyze the entire TU at once, the approach proposed in this patch probably won't work for you.


author_association: COLLABORATOR
user: login: haoNoQ
created_at: 2025-04-18T22:03:58Z
body: > The current patch actually WILL work with it ONLY enabled on the line inside of the function, which I think is much more intuitive.

Ooo whoa this is awesome!!
author_association: COLLABORATOR
user: login: AaronBallman
created_at: 2025-04-21T10:59:31Z
body: > > The current patch actually WILL work with it ONLY enabled on the line inside of the function, which I think is much more intuitive.
> 
> Ooo whoa this is awesome!!

Sorry for the confusion, I've updated the patch summary accordingly
author_association: COLLABORATOR
user: login: AaronBallman
created_at: 2025-04-22T11:00:24Z
body: Any other changes needed, or are you happy @haoNoQ? @erichkeane @Sirraide want to re-review given the significant reworking?
author_association: COLLABORATOR
user: login: nikic
created_at: 2025-04-23T13:24:35Z
body: FYI this introduces some overhead: https://llvm-compile-time-tracker.com/compare.php?from=2a9f77f6bd48d757b2d45aadcb6cf76ef4b4ef32&to=71ce9e26aec00e4af27a69ccfab8ca1773ed7018&stat=instructions:u About 0.3% on clang files.
author_association: CONTRIBUTOR
user: login: AaronBallman
created_at: 2025-04-23T13:30:17Z
body: > FYI this introduces some overhead: https://llvm-compile-time-tracker.com/compare.php?from=2a9f77f6bd48d757b2d45aadcb6cf76ef4b4ef32&to=71ce9e26aec00e4af27a69ccfab8ca1773ed7018&stat=instructions:u About 0.3% on clang files.

Thank you for the notification! Shoot. I'm not certain if there's a better way to do this or if this is just the cost of doing business, but that is unfortunate.
author_association: COLLABORATOR
user: login: AaronBallman
created_at: 2025-04-23T15:01:46Z
body: > > FYI this introduces some overhead: https://llvm-compile-time-tracker.com/compare.php?from=2a9f77f6bd48d757b2d45aadcb6cf76ef4b4ef32&to=71ce9e26aec00e4af27a69ccfab8ca1773ed7018&stat=instructions:u About 0.3% on clang files.
> 
> Thank you for the notification! Shoot. I'm not certain if there's a better way to do this or if this is just the cost of doing business, but that is unfortunate.

I tried a different approach:
```
diff --git a/clang/include/clang/Sema/AnalysisBasedWarnings.h b/clang/include/clang/Sema/AnalysisBasedWarnings.h
index 4103c3f006a8..32bfddec9894 100644
--- a/clang/include/clang/Sema/AnalysisBasedWarnings.h
+++ b/clang/include/clang/Sema/AnalysisBasedWarnings.h
@@ -58,7 +58,7 @@ private:
   enum VisitFlag { NotVisited = 0, Visited = 1, Pending = 2 };
   llvm::DenseMap<const FunctionDecl*, VisitFlag> VisitedFD;
 
-  Policy PolicyOverrides;
+  Policy DefaultPolicy, PolicyOverrides;
   void clearOverrides();
 
   /// \name Statistics
@@ -107,8 +107,8 @@ public:
   // Issue warnings that require whole-translation-unit analysis.
   void IssueWarnings(TranslationUnitDecl *D);
 
-  // Gets the default policy which is in effect at the given source location.
-  Policy getPolicyInEffectAt(SourceLocation Loc);
+  // Gets the policy which is currently in effect.
+  Policy getPolicyInEffect() const;
 
   // Get the policies we may want to override due to things like #pragma clang
   // diagnostic handling. If a caller sets any of these policies to true, that
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 2418aaf8de8e..8726389f43f1 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2493,9 +2493,8 @@ public:
 };
 
 template <typename... Ts>
-static bool areAnyEnabled(DiagnosticsEngine &D, SourceLocation Loc,
-                          Ts... Diags) {
-  return (!D.isIgnored(Diags, Loc) || ...);
+static bool areAnyGloballyEnabled(DiagnosticsEngine &D, Ts... Diags) {
+  return (!D.isIgnored(Diags, {}) || ...);
 }
 
 sema::AnalysisBasedWarnings::AnalysisBasedWarnings(Sema &s)
@@ -2505,29 +2504,35 @@ sema::AnalysisBasedWarnings::AnalysisBasedWarnings(Sema &s)
       NumUninitAnalysisVariables(0), MaxUninitAnalysisVariablesPerFunction(0),
       NumUninitAnalysisBlockVisits(0),
       MaxUninitAnalysisBlockVisitsPerFunction(0) {
+  using namespace diag;
+  DiagnosticsEngine &D = S.getDiagnostics();
+  // Note: The enabled checks should be kept in sync with the switch in
+  // SemaPPCallbacks::PragmaDiagnostic().
+  DefaultPolicy.enableCheckUnreachable = areAnyGloballyEnabled(
+      D, warn_unreachable, warn_unreachable_break, warn_unreachable_return,
+      warn_unreachable_loop_increment);
+
+  DefaultPolicy.enableThreadSafetyAnalysis =
+      areAnyGloballyEnabled(D, warn_double_lock);
+
+  DefaultPolicy.enableConsumedAnalysis =
+      areAnyGloballyEnabled(D, warn_use_in_invalid_state);
 }
 
 // We need this here for unique_ptr with forward declared class.
 sema::AnalysisBasedWarnings::~AnalysisBasedWarnings() = default;
 
 sema::AnalysisBasedWarnings::Policy
-sema::AnalysisBasedWarnings::getPolicyInEffectAt(SourceLocation Loc) {
-  using namespace diag;
-  DiagnosticsEngine &D = S.getDiagnostics();
+sema::AnalysisBasedWarnings::getPolicyInEffect() const {
   Policy P;
-
-  // Note: The enabled checks should be kept in sync with the switch in
-  // SemaPPCallbacks::PragmaDiagnostic().
-  P.enableCheckUnreachable =
-      PolicyOverrides.enableCheckUnreachable ||
-      areAnyEnabled(D, Loc, warn_unreachable, warn_unreachable_break,
-                    warn_unreachable_return, warn_unreachable_loop_increment);
+  P.enableCheckUnreachable = PolicyOverrides.enableCheckUnreachable ||
+                             DefaultPolicy.enableCheckUnreachable;
 
   P.enableThreadSafetyAnalysis = PolicyOverrides.enableThreadSafetyAnalysis ||
-                                 areAnyEnabled(D, Loc, warn_double_lock);
+                                 DefaultPolicy.enableThreadSafetyAnalysis;
 
   P.enableConsumedAnalysis = PolicyOverrides.enableConsumedAnalysis ||
-                             areAnyEnabled(D, Loc, warn_use_in_invalid_state);
+                             DefaultPolicy.enableConsumedAnalysis;
   return P;
 }
 
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 4039601612c6..5c0c17cfa5e4 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -219,7 +219,7 @@ public:
 
     for (diag::kind K : GroupDiags) {
       // Note: the cases in this switch should be kept in sync with the
-      // diagnostics in AnalysisBasedWarnings::getPolicyInEffectAt().
+      // diagnostics in the AnalysisBasedWarnings constructor.
       AnalysisBasedWarnings::Policy &Override =
           S->AnalysisWarnings.getPolicyOverrides();
       switch (K) {
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index d28a2107d58a..372ea1cfd29c 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -16150,13 +16150,7 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body,
   if (FSI->UsesFPIntrin && FD && !FD->hasAttr<StrictFPAttr>())
     FD->addAttr(StrictFPAttr::CreateImplicit(Context));
 
-  SourceLocation AnalysisLoc;
-  if (Body)
-    AnalysisLoc = Body->getEndLoc();
-  else if (FD)
-    AnalysisLoc = FD->getEndLoc();
-  sema::AnalysisBasedWarnings::Policy WP =
-      AnalysisWarnings.getPolicyInEffectAt(AnalysisLoc);
+  sema::AnalysisBasedWarnings::Policy WP = AnalysisWarnings.getPolicyInEffect();
   sema::AnalysisBasedWarnings::Policy *ActivePolicy = nullptr;
 
   // If we skip function body, we can't tell if a function is a coroutine.
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 2e6ce17f8bf9..6b8d1274969c 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -16597,8 +16597,7 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc,
   BD->setCaptures(Context, Captures, BSI->CXXThisCaptureIndex != 0);
 
   // Pop the block scope now but keep it alive to the end of this function.
-  AnalysisBasedWarnings::Policy WP =
-      AnalysisWarnings.getPolicyInEffectAt(Body->getEndLoc());
+  AnalysisBasedWarnings::Policy WP = AnalysisWarnings.getPolicyInEffect();
   PoppedFunctionScopePtr ScopeRAII = PopFunctionScopeInfo(&WP, BD, BlockTy);
 
   BlockExpr *Result = new (Context)
```
but this causes test case failures. We process the pragma when we consume the closing `}` from a preceding function body. Without checking for the diagnostic being enabled at a particular source location, we'll fail to catch the diagnostic here:
```
int a() {	
  Linear l;
  return 0; // No -Wconsumed diagnostic, analysis is not enabled.
}

#pragma clang diagnostic push
#pragma clang diagnostic error "-Wconsumed"
int b() {
  Linear l;
  return 0;  // expected-error {{invalid invocation of method '~Linear' on object 'l' while it is in the 'unconsumed' state}}
}
#pragma clang diagnostic pop
```
because we set the override at the end of `a`, reset the override at the end of `a`, and then miss that it's enabled at the end of `b`.
author_association: COLLABORATOR