https://github.com/jpjepko updated 
https://github.com/llvm/llvm-project/pull/178342

>From 4bae297c12fda0bb19a15fd6c460005bd6a608f6 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Wed, 28 Jan 2026 02:37:45 +0100
Subject: [PATCH 01/20] [clang] Extend -Wunused-but-set-variable to static
 globals

This change extends -Wunused-but-set-variable to diagnose static globals
within the translation unit that are assigned to within function bodies,
but whose values are never used.

Fixes #148361
---
 clang/lib/Sema/Sema.cpp                       | 32 +++++++
 clang/lib/Sema/SemaExpr.cpp                   |  6 +-
 clang/test/C/C2y/n3622.c                      |  8 +-
 .../Sema/warn-unused-but-set-static-global.c  | 84 +++++++++++++++++++
 4 files changed, 125 insertions(+), 5 deletions(-)
 create mode 100644 clang/test/Sema/warn-unused-but-set-static-global.c

diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 8b1d0398cf65d..7948655e5bdd2 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1635,6 +1635,38 @@ void Sema::ActOnEndOfTranslationUnit() {
   if (Context.hasAnyFunctionEffects())
     performFunctionEffectAnalysis(Context.getTranslationUnitDecl());
 
+  // diagnose unused-but-set static globals in a deterministic order
+  //
+  // not trackings shadowing info for static globals; there's nothing to shadow
+  struct LocAndDiag {
+    SourceLocation Loc;
+    PartialDiagnostic PD;
+  };
+  SmallVector<LocAndDiag, 16> DeclDiags;
+  auto addDiag = [&DeclDiags](SourceLocation Loc, PartialDiagnostic PD) {
+    DeclDiags.push_back(LocAndDiag{Loc, std::move(PD)});
+  };
+
+  // for -Wunused-but-set-variable we only care about variables that were
+  // referenced by the TU end
+  for (const auto &Ref : RefsMinusAssignments) {
+    const VarDecl *VD = Ref.first;
+    if (VD->isFileVarDecl() && VD->getStorageClass() == SC_Static) {
+      DiagnoseUnusedButSetDecl(VD, addDiag);
+      RefsMinusAssignments.erase(VD);
+    }
+  }
+
+  llvm::sort(DeclDiags,
+             [](const LocAndDiag &LHS, const LocAndDiag &RHS) -> bool {
+               // sorting purely for determinism; matches behavior in
+               // SemaDecl.cpp
+               return LHS.Loc.getRawEncoding() < RHS.Loc.getRawEncoding();
+             });
+  for (const LocAndDiag &D : DeclDiags) {
+    Diag(D.Loc, D.PD);
+  }
+
   // Check we've noticed that we're no longer parsing the initializer for every
   // variable. If we miss cases, then at best we have a performance issue and
   // at worst a rejects-valid bug.
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index db6d93ce54791..85eaccabedd20 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -20477,7 +20477,11 @@ static void DoMarkVarDeclReferenced(
   bool UsableInConstantExpr =
       Var->mightBeUsableInConstantExpressions(SemaRef.Context);
 
-  if (Var->isLocalVarDeclOrParm() && !Var->hasExternalStorage()) {
+  bool StaticGlobalReferenced = Var->isFileVarDecl() &&
+                                Var->getStorageClass() == SC_Static &&
+                                !Var->isStaticDataMember();
+  if ((Var->isLocalVarDeclOrParm() || StaticGlobalReferenced) &&
+      !Var->hasExternalStorage()) {
     RefsMinusAssignments.insert({Var, 0}).first->getSecond()++;
   }
 
diff --git a/clang/test/C/C2y/n3622.c b/clang/test/C/C2y/n3622.c
index 95b92e8f235a8..d90b0c51d3ccf 100644
--- a/clang/test/C/C2y/n3622.c
+++ b/clang/test/C/C2y/n3622.c
@@ -1,7 +1,7 @@
-// RUN: %clang_cc1 -verify=good -pedantic -Wall -std=c2y %s
-// RUN: %clang_cc1 -verify=compat,expected -pedantic -Wall -Wpre-c2y-compat 
-std=c2y %s
-// RUN: %clang_cc1 -verify=ped,expected -pedantic -Wall -std=c23 %s
-// RUN: %clang_cc1 -verify=ped,expected -pedantic -Wall -std=c17 %s
+// RUN: %clang_cc1 -verify=good -pedantic -Wall -Wno-unused-but-set-variable 
-std=c2y %s
+// RUN: %clang_cc1 -verify=compat,expected -pedantic -Wall 
-Wno-unused-but-set-variable -Wpre-c2y-compat -std=c2y %s
+// RUN: %clang_cc1 -verify=ped,expected -pedantic -Wall 
-Wno-unused-but-set-variable -std=c23 %s
+// RUN: %clang_cc1 -verify=ped,expected -pedantic -Wall 
-Wno-unused-but-set-variable -std=c17 %s
 // good-no-diagnostics
 
 /* WG14 N3622: Clang 22
diff --git a/clang/test/Sema/warn-unused-but-set-static-global.c 
b/clang/test/Sema/warn-unused-but-set-static-global.c
new file mode 100644
index 0000000000000..a68136749047d
--- /dev/null
+++ b/clang/test/Sema/warn-unused-but-set-static-global.c
@@ -0,0 +1,84 @@
+// RUN: %clang_cc1 -fsyntax-only -Wunused-but-set-variable -verify %s
+
+static int set_unused; // expected-warning {{variable 'set_unused' set but not 
used}}
+static int set_and_used;
+static int only_used;
+static int addr_taken;
+extern int external_var;  // no warning (external linkage)
+extern int global_var;  // no warning (not static)
+
+void f1() {
+  set_unused = 1;
+  set_and_used = 2;
+
+  int x = set_and_used;
+  (void)x;
+
+  int y = only_used;
+  (void)y;
+
+  int *p = &addr_taken;
+  (void)p;
+
+  external_var = 3;
+  global_var = 4;
+}
+
+// test across multiple functions
+static int set_used1;
+static int set_used2;
+
+static int set1; // expected-warning {{variable 'set1' set but not used}}
+static int set2; // expected-warning {{variable 'set2' set but not used}}
+
+void f2() {
+  set1 = 1;
+  set_used1 = 1;
+
+  int x = set_used2;
+  (void)x;
+}
+
+void f3() {
+  set2 = 2;
+  set_used2 = 2;
+
+  int x = set_used1;
+  (void)x;
+}
+
+static volatile int vol_set; // expected-warning {{variable 'vol_set' set but 
not used}}
+void f4() {
+  vol_set = 1;
+}
+
+// read and use
+static int compound; // expected-warning{{variable 'compound' set but not 
used}}
+static volatile int vol_compound;
+static int unary; // expected-warning{{variable 'unary' set but not used}}
+static volatile int vol_unary;
+void f5() {
+  compound += 1;
+  vol_compound += 1;
+  unary++;
+  vol_unary++;
+}
+
+struct S {
+  int i;
+};
+static struct S s_set;  // expected-warning{{variable 's_set' set but not 
used}}
+static struct S s_used;
+void f6() {
+  struct S t;
+  s_set = t;
+  t = s_used;
+}
+
+// multiple assignments
+static int multi; // expected-warning{{variable 'multi' set but not used}}
+void f7() {
+  multi = 1;
+  multi = 2;
+  multi = 3;
+}

>From 96aa029432924f76345ee41b142d9e3453f10600 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Wed, 28 Jan 2026 18:32:20 +0100
Subject: [PATCH 02/20] fix function ptr and namespace issues, add tests

---
 clang/lib/Sema/SemaExpr.cpp                   |  6 ++--
 .../Sema/warn-unused-but-set-static-global.c  | 35 +++++++++++++++++++
 .../warn-unused-but-set-static-global.cpp     | 28 +++++++++++++++
 3 files changed, 66 insertions(+), 3 deletions(-)
 create mode 100644 clang/test/Sema/warn-unused-but-set-static-global.cpp

diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 85eaccabedd20..282d01b209226 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -20477,9 +20477,9 @@ static void DoMarkVarDeclReferenced(
   bool UsableInConstantExpr =
       Var->mightBeUsableInConstantExpressions(SemaRef.Context);
 
-  bool StaticGlobalReferenced = Var->isFileVarDecl() &&
-                                Var->getStorageClass() == SC_Static &&
-                                !Var->isStaticDataMember();
+  bool StaticGlobalReferenced =
+      Var->isFileVarDecl() && Var->getStorageClass() == SC_Static &&
+      !Var->isStaticDataMember() && !Var->getType()->isFunctionPointerType();
   if ((Var->isLocalVarDeclOrParm() || StaticGlobalReferenced) &&
       !Var->hasExternalStorage()) {
     RefsMinusAssignments.insert({Var, 0}).first->getSecond()++;
diff --git a/clang/test/Sema/warn-unused-but-set-static-global.c 
b/clang/test/Sema/warn-unused-but-set-static-global.c
index a68136749047d..406b9af116646 100644
--- a/clang/test/Sema/warn-unused-but-set-static-global.c
+++ b/clang/test/Sema/warn-unused-but-set-static-global.c
@@ -1,5 +1,10 @@
 // RUN: %clang_cc1 -fsyntax-only -Wunused-but-set-variable -verify %s
 
+#define NULL (void*)0
+
+void *set(int size);
+void func_call(void *);
+
 static int set_unused; // expected-warning {{variable 'set_unused' set but not 
used}}
 static int set_and_used;
 static int only_used;
@@ -82,3 +87,33 @@ void f7() {
   multi = 2;
   multi = 3;
 }
+
+// unused pointers
+static int *unused_ptr; // expected-warning{{variable 'unused_ptr' set but not 
used}}
+static char *str_ptr; // expected-warning{{variable 'str_ptr' set but not 
used}}
+void f8() {
+  unused_ptr = set(5);
+  str_ptr = "hello";
+}
+
+// used pointers
+void a(void *);
+static int *used_ptr;
+static int *param_ptr;
+static int *null_check_ptr;
+void f9() {
+  used_ptr = set(5);
+  *used_ptr = 5;
+
+  param_ptr = set(5);
+  func_call(param_ptr);
+
+  null_check_ptr = set(5);
+  if (null_check_ptr == NULL) {}
+}
+
+// function pointers
+static void (*sandboxing_callback)();
+void SetSandboxingCallback(void (*f)()) {
+  sandboxing_callback = f;
+}
diff --git a/clang/test/Sema/warn-unused-but-set-static-global.cpp 
b/clang/test/Sema/warn-unused-but-set-static-global.cpp
new file mode 100644
index 0000000000000..ead7599d6cc78
--- /dev/null
+++ b/clang/test/Sema/warn-unused-but-set-static-global.cpp
@@ -0,0 +1,28 @@
+// RUN: %clang_cc1 -fsyntax-only -Wunused-but-set-variable -verify -std=c++11 
%s
+
+namespace test {
+  static int set_unused;  // expected-warning {{variable 'set_unused' set but 
not used}}
+  static int set_and_used;
+
+  void f1() {
+    set_unused = 1;
+    set_and_used = 2;
+    int x = set_and_used;
+    (void)x;
+  }
+
+  // function pointer in namespace
+  static void (*sandboxing_callback)();
+  void SetSandboxingCallback(void (*f)()) {
+    sandboxing_callback = f;
+  }
+}
+
+namespace outer {
+namespace inner {
+static int nested_unused; // expected-warning {{variable 'nested_unused' set 
but not used}}
+void f2() {
+  nested_unused = 5;
+}
+}
+}

>From 99072c63061644872d39a1cc3becd7562df21e49 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Thu, 29 Jan 2026 21:56:23 +0100
Subject: [PATCH 03/20] ensure static globals only diagnosed at end of TU

---
 clang/lib/Sema/SemaDecl.cpp | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 101d5085b980b..2836b00ed980d 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2305,8 +2305,13 @@ void Sema::ActOnPopScope(SourceLocation Loc, Scope *S) {
       if (const auto *RD = dyn_cast<RecordDecl>(D))
         DiagnoseUnusedNestedTypedefs(RD, addDiag);
       if (VarDecl *VD = dyn_cast<VarDecl>(D)) {
-        DiagnoseUnusedButSetDecl(VD, addDiag);
-        RefsMinusAssignments.erase(VD);
+        // wait until end of TU to diagnose static globals
+        bool isStaticGlobal =
+            VD->isFileVarDecl() && VD->getStorageClass() == SC_Static;
+        if (!isStaticGlobal) {
+          DiagnoseUnusedButSetDecl(VD, addDiag);
+          RefsMinusAssignments.erase(VD);
+        }
       }
     }
 

>From 16710426006ebd59fbd4087de7f546b49b4ae346 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Mon, 2 Feb 2026 23:05:06 +0100
Subject: [PATCH 04/20] skip header-defined static globals, add tests and
 refactor

---
 clang/include/clang/AST/Decl.h                            | 5 +++++
 clang/lib/Sema/Sema.cpp                                   | 4 +++-
 clang/lib/Sema/SemaDecl.cpp                               | 4 +---
 clang/lib/Sema/SemaExpr.cpp                               | 8 ++++----
 .../warn-unused-but-set-static-global-header-test.c       | 3 +++
 .../Inputs/warn-unused-but-set-static-global-header.h     | 3 +++
 clang/test/Sema/warn-unused-but-set-static-global.c       | 1 +
 7 files changed, 20 insertions(+), 8 deletions(-)
 create mode 100644 
clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c
 create mode 100644 
clang/test/Sema/Inputs/warn-unused-but-set-static-global-header.h

diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index c3cd74a5b34db..47db1ff8d5817 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -1212,6 +1212,11 @@ class VarDecl : public DeclaratorDecl, public 
Redeclarable<VarDecl> {
       && !isFileVarDecl();
   }
 
+  /// Returns true if a variable is a static file-scope variable.
+  bool isStaticFileVar() const {
+    return isFileVarDecl() && getStorageClass() == SC_Static;
+  }
+
   /// Returns true if a variable has extern or __private_extern__
   /// storage.
   bool hasExternalStorage() const {
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 7948655e5bdd2..a64e268e6ab35 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1651,7 +1651,9 @@ void Sema::ActOnEndOfTranslationUnit() {
   // referenced by the TU end
   for (const auto &Ref : RefsMinusAssignments) {
     const VarDecl *VD = Ref.first;
-    if (VD->isFileVarDecl() && VD->getStorageClass() == SC_Static) {
+    // only diagnose static file vars defined in the main file to match
+    // -Wunused-variable behavior and avoid false positives from header vars
+    if (VD->isStaticFileVar() && SourceMgr.isInMainFile(VD->getLocation())) {
       DiagnoseUnusedButSetDecl(VD, addDiag);
       RefsMinusAssignments.erase(VD);
     }
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 2836b00ed980d..467c06582299f 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2306,9 +2306,7 @@ void Sema::ActOnPopScope(SourceLocation Loc, Scope *S) {
         DiagnoseUnusedNestedTypedefs(RD, addDiag);
       if (VarDecl *VD = dyn_cast<VarDecl>(D)) {
         // wait until end of TU to diagnose static globals
-        bool isStaticGlobal =
-            VD->isFileVarDecl() && VD->getStorageClass() == SC_Static;
-        if (!isStaticGlobal) {
+        if (!VD->isStaticFileVar()) {
           DiagnoseUnusedButSetDecl(VD, addDiag);
           RefsMinusAssignments.erase(VD);
         }
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 282d01b209226..6013a0cad06e5 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -20477,10 +20477,10 @@ static void DoMarkVarDeclReferenced(
   bool UsableInConstantExpr =
       Var->mightBeUsableInConstantExpressions(SemaRef.Context);
 
-  bool StaticGlobalReferenced =
-      Var->isFileVarDecl() && Var->getStorageClass() == SC_Static &&
-      !Var->isStaticDataMember() && !Var->getType()->isFunctionPointerType();
-  if ((Var->isLocalVarDeclOrParm() || StaticGlobalReferenced) &&
+  bool ShouldTrackForUnusedButSet = Var->isStaticFileVar() &&
+                                    !Var->isStaticDataMember() &&
+                                    !Var->getType()->isFunctionPointerType();
+  if ((Var->isLocalVarDeclOrParm() || ShouldTrackForUnusedButSet) &&
       !Var->hasExternalStorage()) {
     RefsMinusAssignments.insert({Var, 0}).first->getSecond()++;
   }
diff --git 
a/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c 
b/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c
new file mode 100644
index 0000000000000..e4c316b37d15f
--- /dev/null
+++ b/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c
@@ -0,0 +1,3 @@
+// expected-no-diagnostics
+// test that header-defined static globals don't warn
+#include "warn-unused-but-set-static-global-header.h"
diff --git a/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header.h 
b/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header.h
new file mode 100644
index 0000000000000..a06e9e66a34f4
--- /dev/null
+++ b/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header.h
@@ -0,0 +1,3 @@
+// header file for testing that header-defined static globals don't warn
+static int header_set_unused = 0;
+static void header_init() { header_set_unused = 1; }
diff --git a/clang/test/Sema/warn-unused-but-set-static-global.c 
b/clang/test/Sema/warn-unused-but-set-static-global.c
index 406b9af116646..bf099d3c5759c 100644
--- a/clang/test/Sema/warn-unused-but-set-static-global.c
+++ b/clang/test/Sema/warn-unused-but-set-static-global.c
@@ -1,4 +1,5 @@
 // RUN: %clang_cc1 -fsyntax-only -Wunused-but-set-variable -verify %s
+// RUN: %clang_cc1 -fsyntax-only -Wunused-but-set-variable -I %S -verify 
%S/Inputs/warn-unused-but-set-static-global-header-test.c
 
 #define NULL (void*)0
 

>From f8db9c6e46d3979d96782bb713d2b27b54b06bff Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Tue, 3 Feb 2026 01:43:14 +0100
Subject: [PATCH 05/20] fix comment formatting

---
 clang/lib/Sema/Sema.cpp                         | 17 ++++++++---------
 clang/lib/Sema/SemaDecl.cpp                     |  2 +-
 ...n-unused-but-set-static-global-header-test.c |  2 +-
 .../warn-unused-but-set-static-global-header.h  |  2 +-
 .../Sema/warn-unused-but-set-static-global.c    | 16 ++++++++--------
 .../Sema/warn-unused-but-set-static-global.cpp  |  2 +-
 6 files changed, 20 insertions(+), 21 deletions(-)

diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index a64e268e6ab35..3fff5526158d0 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1635,9 +1635,8 @@ void Sema::ActOnEndOfTranslationUnit() {
   if (Context.hasAnyFunctionEffects())
     performFunctionEffectAnalysis(Context.getTranslationUnitDecl());
 
-  // diagnose unused-but-set static globals in a deterministic order
-  //
-  // not trackings shadowing info for static globals; there's nothing to shadow
+  // Diagnose unused-but-set static globals in a deterministic order.
+  // Not tracking shadowing info for static globals; there's nothing to shadow.
   struct LocAndDiag {
     SourceLocation Loc;
     PartialDiagnostic PD;
@@ -1647,12 +1646,12 @@ void Sema::ActOnEndOfTranslationUnit() {
     DeclDiags.push_back(LocAndDiag{Loc, std::move(PD)});
   };
 
-  // for -Wunused-but-set-variable we only care about variables that were
-  // referenced by the TU end
+  // For -Wunused-but-set-variable we only care about variables that were
+  // referenced by the TU end.
   for (const auto &Ref : RefsMinusAssignments) {
     const VarDecl *VD = Ref.first;
-    // only diagnose static file vars defined in the main file to match
-    // -Wunused-variable behavior and avoid false positives from header vars
+    // Only diagnose static file vars defined in the main file to match
+    // -Wunused-variable behavior and avoid false positives from header vars.
     if (VD->isStaticFileVar() && SourceMgr.isInMainFile(VD->getLocation())) {
       DiagnoseUnusedButSetDecl(VD, addDiag);
       RefsMinusAssignments.erase(VD);
@@ -1661,8 +1660,8 @@ void Sema::ActOnEndOfTranslationUnit() {
 
   llvm::sort(DeclDiags,
              [](const LocAndDiag &LHS, const LocAndDiag &RHS) -> bool {
-               // sorting purely for determinism; matches behavior in
-               // SemaDecl.cpp
+               // Sorting purely for determinism; matches behavior in
+               // SemaDecl.cpp.
                return LHS.Loc.getRawEncoding() < RHS.Loc.getRawEncoding();
              });
   for (const LocAndDiag &D : DeclDiags) {
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 467c06582299f..b0e37251a3058 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2305,7 +2305,7 @@ void Sema::ActOnPopScope(SourceLocation Loc, Scope *S) {
       if (const auto *RD = dyn_cast<RecordDecl>(D))
         DiagnoseUnusedNestedTypedefs(RD, addDiag);
       if (VarDecl *VD = dyn_cast<VarDecl>(D)) {
-        // wait until end of TU to diagnose static globals
+        // Wait until end of TU to diagnose static globals.
         if (!VD->isStaticFileVar()) {
           DiagnoseUnusedButSetDecl(VD, addDiag);
           RefsMinusAssignments.erase(VD);
diff --git 
a/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c 
b/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c
index e4c316b37d15f..f26c90733a2bd 100644
--- a/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c
+++ b/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c
@@ -1,3 +1,3 @@
 // expected-no-diagnostics
-// test that header-defined static globals don't warn
+// Test that header-defined static globals don't warn.
 #include "warn-unused-but-set-static-global-header.h"
diff --git a/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header.h 
b/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header.h
index a06e9e66a34f4..40637d644f717 100644
--- a/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header.h
+++ b/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header.h
@@ -1,3 +1,3 @@
-// header file for testing that header-defined static globals don't warn
+// Header file for testing that header-defined static globals don't warn.
 static int header_set_unused = 0;
 static void header_init() { header_set_unused = 1; }
diff --git a/clang/test/Sema/warn-unused-but-set-static-global.c 
b/clang/test/Sema/warn-unused-but-set-static-global.c
index bf099d3c5759c..12679a4de8a34 100644
--- a/clang/test/Sema/warn-unused-but-set-static-global.c
+++ b/clang/test/Sema/warn-unused-but-set-static-global.c
@@ -10,8 +10,8 @@ static int set_unused; // expected-warning {{variable 
'set_unused' set but not u
 static int set_and_used;
 static int only_used;
 static int addr_taken;
-extern int external_var;  // no warning (external linkage)
-extern int global_var;  // no warning (not static)
+extern int external_var;  // No warning (external linkage).
+extern int global_var;  // No warning (not static).
 
 void f1() {
   set_unused = 1;
@@ -30,7 +30,7 @@ void f1() {
   global_var = 4;
 }
 
-// test across multiple functions
+// Test across multiple functions.
 static int set_used1;
 static int set_used2;
 
@@ -58,7 +58,7 @@ void f4() {
   vol_set = 1;
 }
 
-// read and use
+// Read and use
 static int compound; // expected-warning{{variable 'compound' set but not 
used}}
 static volatile int vol_compound;
 static int unary; // expected-warning{{variable 'unary' set but not used}}
@@ -81,7 +81,7 @@ void f6() {
   t = s_used;
 }
 
-// multiple assignments
+// Multiple assignments
 static int multi; // expected-warning{{variable 'multi' set but not used}}
 void f7() {
   multi = 1;
@@ -89,7 +89,7 @@ void f7() {
   multi = 3;
 }
 
-// unused pointers
+// Unused pointers
 static int *unused_ptr; // expected-warning{{variable 'unused_ptr' set but not 
used}}
 static char *str_ptr; // expected-warning{{variable 'str_ptr' set but not 
used}}
 void f8() {
@@ -97,7 +97,7 @@ void f8() {
   str_ptr = "hello";
 }
 
-// used pointers
+// Used pointers
 void a(void *);
 static int *used_ptr;
 static int *param_ptr;
@@ -113,7 +113,7 @@ void f9() {
   if (null_check_ptr == NULL) {}
 }
 
-// function pointers
+// Function pointers
 static void (*sandboxing_callback)();
 void SetSandboxingCallback(void (*f)()) {
   sandboxing_callback = f;
diff --git a/clang/test/Sema/warn-unused-but-set-static-global.cpp 
b/clang/test/Sema/warn-unused-but-set-static-global.cpp
index ead7599d6cc78..66d20bc242ca0 100644
--- a/clang/test/Sema/warn-unused-but-set-static-global.cpp
+++ b/clang/test/Sema/warn-unused-but-set-static-global.cpp
@@ -11,7 +11,7 @@ namespace test {
     (void)x;
   }
 
-  // function pointer in namespace
+  // Function pointer in namespace.
   static void (*sandboxing_callback)();
   void SetSandboxingCallback(void (*f)()) {
     sandboxing_callback = f;

>From 9484cc6882f3e7a96290d22bdc92f503399afb9c Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Tue, 3 Feb 2026 02:10:11 +0100
Subject: [PATCH 06/20] wait after iterating to erase decls

---
 clang/lib/Sema/Sema.cpp | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 3fff5526158d0..580560afb296c 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1648,15 +1648,19 @@ void Sema::ActOnEndOfTranslationUnit() {
 
   // For -Wunused-but-set-variable we only care about variables that were
   // referenced by the TU end.
+  SmallVector<const VarDecl *, 16> DeclsToErase;
   for (const auto &Ref : RefsMinusAssignments) {
     const VarDecl *VD = Ref.first;
     // Only diagnose static file vars defined in the main file to match
     // -Wunused-variable behavior and avoid false positives from header vars.
     if (VD->isStaticFileVar() && SourceMgr.isInMainFile(VD->getLocation())) {
       DiagnoseUnusedButSetDecl(VD, addDiag);
-      RefsMinusAssignments.erase(VD);
+      DeclsToErase.push_back(VD);
     }
   }
+  for (const VarDecl *VD : DeclsToErase) {
+    RefsMinusAssignments.erase(VD);
+  }
 
   llvm::sort(DeclDiags,
              [](const LocAndDiag &LHS, const LocAndDiag &RHS) -> bool {

>From feeb8317c0b0afee53bd9083af3bb72983d92c13 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Tue, 3 Feb 2026 22:12:18 +0100
Subject: [PATCH 07/20] add test for static thread_local

---
 .../test/Sema/warn-unused-but-set-static-global.cpp | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/clang/test/Sema/warn-unused-but-set-static-global.cpp 
b/clang/test/Sema/warn-unused-but-set-static-global.cpp
index 66d20bc242ca0..a76b37f63eae2 100644
--- a/clang/test/Sema/warn-unused-but-set-static-global.cpp
+++ b/clang/test/Sema/warn-unused-but-set-static-global.cpp
@@ -1,5 +1,18 @@
 // RUN: %clang_cc1 -fsyntax-only -Wunused-but-set-variable -verify -std=c++11 
%s
 
+static thread_local int tl_set_unused;  // expected-warning {{variable 
'tl_set_unused' set but not used}}
+static thread_local int tl_set_and_used;
+thread_local int tl_no_static_set_unused;
+
+void f0() {
+  tl_set_unused = 1;
+  tl_set_and_used = 2;
+  int x = tl_set_and_used;
+  (void)x;
+
+  tl_no_static_set_unused = 3;
+}
+
 namespace test {
   static int set_unused;  // expected-warning {{variable 'set_unused' set but 
not used}}
   static int set_and_used;

>From 9d888ae9f31ab7251f9d3543a4a0bb3dfc2a97d8 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Mon, 16 Feb 2026 20:57:12 +0100
Subject: [PATCH 08/20] revert function ptr exclusion and test

---
 clang/lib/Sema/SemaExpr.cpp                   |  8 ++-
 .../Sema/warn-unused-but-set-static-global.c  | 18 ++++--
 .../warn-unused-but-set-static-global.cpp     | 58 +++++++++++++++++--
 3 files changed, 71 insertions(+), 13 deletions(-)

diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 6013a0cad06e5..513768f44b842 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -20477,9 +20477,11 @@ static void DoMarkVarDeclReferenced(
   bool UsableInConstantExpr =
       Var->mightBeUsableInConstantExpressions(SemaRef.Context);
 
-  bool ShouldTrackForUnusedButSet = Var->isStaticFileVar() &&
-                                    !Var->isStaticDataMember() &&
-                                    !Var->getType()->isFunctionPointerType();
+  // We skip static data members because they have external linkage.
+  // TODO: static data members in anonymous namespaces have internal linkage 
and
+  // should be diagnosed.
+  bool ShouldTrackForUnusedButSet =
+      Var->isStaticFileVar() && !Var->isStaticDataMember();
   if ((Var->isLocalVarDeclOrParm() || ShouldTrackForUnusedButSet) &&
       !Var->hasExternalStorage()) {
     RefsMinusAssignments.insert({Var, 0}).first->getSecond()++;
diff --git a/clang/test/Sema/warn-unused-but-set-static-global.c 
b/clang/test/Sema/warn-unused-but-set-static-global.c
index 12679a4de8a34..0a16486c45225 100644
--- a/clang/test/Sema/warn-unused-but-set-static-global.c
+++ b/clang/test/Sema/warn-unused-but-set-static-global.c
@@ -113,8 +113,18 @@ void f9() {
   if (null_check_ptr == NULL) {}
 }
 
-// Function pointers
-static void (*sandboxing_callback)();
-void SetSandboxingCallback(void (*f)()) {
-  sandboxing_callback = f;
+// Function pointers (unused)
+static void (*unused_func_ptr)(); // expected-warning {{variable 
'unused_func_ptr' set but not used}}
+void SetUnusedCallback(void (*f)()) {
+  unused_func_ptr = f;
+}
+
+// Function pointers (used)
+static void (*used_func_ptr)();
+void SetUsedCallback(void (*f)()) {
+  used_func_ptr = f;
+}
+void CallUsedCallback() {
+  if (used_func_ptr)
+    used_func_ptr();
 }
diff --git a/clang/test/Sema/warn-unused-but-set-static-global.cpp 
b/clang/test/Sema/warn-unused-but-set-static-global.cpp
index a76b37f63eae2..2ec870777e91e 100644
--- a/clang/test/Sema/warn-unused-but-set-static-global.cpp
+++ b/clang/test/Sema/warn-unused-but-set-static-global.cpp
@@ -23,12 +23,6 @@ namespace test {
     int x = set_and_used;
     (void)x;
   }
-
-  // Function pointer in namespace.
-  static void (*sandboxing_callback)();
-  void SetSandboxingCallback(void (*f)()) {
-    sandboxing_callback = f;
-  }
 }
 
 namespace outer {
@@ -39,3 +33,55 @@ void f2() {
 }
 }
 }
+
+// Anonymous namespace
+namespace {
+  static int anon_ns_static_unused; // expected-warning {{variable 
'anon_ns_static_unused' set but not used}}
+
+  // Should not warn on static data members in current implementation.
+  class AnonClass {
+  public:
+    static int unused_member;
+  };
+
+  int AnonClass::unused_member = 0;
+
+  void f3() {
+    anon_ns_static_unused = 1;
+    AnonClass::unused_member = 2;
+  }
+}
+
+// Function pointers at file scope (unused)
+static void (*unused_func_ptr)(); // expected-warning {{variable 
'unused_func_ptr' set but not used}}
+void SetUnusedCallback(void (*f)()) {
+  unused_func_ptr = f;
+}
+
+// Function pointers at file scope (used)
+static void (*used_func_ptr)();
+void SetUsedCallback(void (*f)()) {
+  used_func_ptr = f;
+}
+void CallUsedCallback() {
+  if (used_func_ptr)
+    used_func_ptr();
+}
+
+// Static data members (have external linkage so should not warn).
+class MyClass {
+public:
+  static int unused_static_member;
+  static int used_static_member;
+};
+
+int MyClass::unused_static_member = 0;
+int MyClass::used_static_member = 0;
+
+void f4() {
+  MyClass::unused_static_member = 10;
+
+  MyClass::used_static_member = 20;
+  int x = MyClass::used_static_member;
+  (void)x;
+}

>From 6307c8a7d563a59f7189a26f3cc0a7bdd5b6fb1c Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Wed, 18 Feb 2026 21:43:12 +0100
Subject: [PATCH 09/20] add release note

---
 clang/docs/ReleaseNotes.rst | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 6f89cc9a30603..4c5a904715712 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -215,6 +215,9 @@ Attribute Changes in Clang
 
 Improvements to Clang's diagnostics
 -----------------------------------
+- ``-Wunused-but-set-variable`` now diagnoses file-scope variables with
+  internal linkage (``static`` storage class) that are assigned but never used.
+
 - Added ``-Wlifetime-safety`` to enable lifetime safety analysis,
   a CFG-based intra-procedural analysis that detects use-after-free and related
   temporal safety bugs. See the

>From d6306ef8ccc73c23e750d2ad8ef825e19568d06e Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Thu, 19 Feb 2026 18:55:33 +0100
Subject: [PATCH 10/20] style changes, refactor, update rel notes

---
 clang/docs/ReleaseNotes.rst |  2 +-
 clang/lib/Sema/Sema.cpp     | 75 +++++++++++++++++++------------------
 2 files changed, 39 insertions(+), 38 deletions(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 4c5a904715712..9fd1d0ad0a1c6 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -216,7 +216,7 @@ Attribute Changes in Clang
 Improvements to Clang's diagnostics
 -----------------------------------
 - ``-Wunused-but-set-variable`` now diagnoses file-scope variables with
-  internal linkage (``static`` storage class) that are assigned but never used.
+  internal linkage (``static`` storage class) that are assigned but never 
used. (#GH148361)
 
 - Added ``-Wlifetime-safety`` to enable lifetime safety analysis,
   a CFG-based intra-procedural analysis that detects use-after-free and related
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 580560afb296c..3c6f54eccce1c 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1604,6 +1604,44 @@ void Sema::ActOnEndOfTranslationUnit() {
     emitAndClearUnusedLocalTypedefWarnings();
   }
 
+  if (!Diags.isIgnored(diag::warn_unused_but_set_variable, SourceLocation())) {
+    // Diagnose unused-but-set static globals in a deterministic order.
+    // Not tracking shadowing info for static globals; there's nothing to 
shadow.
+    struct LocAndDiag {
+      SourceLocation Loc;
+      PartialDiagnostic PD;
+    };
+    SmallVector<LocAndDiag, 16> DeclDiags;
+    auto addDiag = [&DeclDiags](SourceLocation Loc, PartialDiagnostic PD) {
+      DeclDiags.push_back(LocAndDiag{Loc, std::move(PD)});
+    };
+
+    // For -Wunused-but-set-variable we only care about variables that were
+    // referenced by the TU end.
+    SmallVector<const VarDecl *, 16> DeclsToErase;
+    for (const auto &Ref : RefsMinusAssignments) {
+      const VarDecl *VD = Ref.first;
+      // Only diagnose static file vars defined in the main file to match
+      // -Wunused-variable behavior and avoid false positives from header vars.
+      if (VD->isStaticFileVar() && SourceMgr.isInMainFile(VD->getLocation())) {
+        DiagnoseUnusedButSetDecl(VD, addDiag);
+        DeclsToErase.push_back(VD);
+      }
+    }
+    for (const VarDecl *VD : DeclsToErase) {
+      RefsMinusAssignments.erase(VD);
+    }
+
+    llvm::sort(DeclDiags,
+              [](const LocAndDiag &LHS, const LocAndDiag &RHS) -> bool {
+                // Sorting purely for determinism; matches behavior in
+                // SemaDecl.cpp.
+                return LHS.Loc.getRawEncoding() < RHS.Loc.getRawEncoding();
+              });
+    for (const LocAndDiag &D : DeclDiags)
+      Diag(D.Loc, D.PD);
+  }
+
   if (!Diags.isIgnored(diag::warn_unused_private_field, SourceLocation())) {
     // FIXME: Load additional unused private field candidates from the external
     // source.
@@ -1635,43 +1673,6 @@ void Sema::ActOnEndOfTranslationUnit() {
   if (Context.hasAnyFunctionEffects())
     performFunctionEffectAnalysis(Context.getTranslationUnitDecl());
 
-  // Diagnose unused-but-set static globals in a deterministic order.
-  // Not tracking shadowing info for static globals; there's nothing to shadow.
-  struct LocAndDiag {
-    SourceLocation Loc;
-    PartialDiagnostic PD;
-  };
-  SmallVector<LocAndDiag, 16> DeclDiags;
-  auto addDiag = [&DeclDiags](SourceLocation Loc, PartialDiagnostic PD) {
-    DeclDiags.push_back(LocAndDiag{Loc, std::move(PD)});
-  };
-
-  // For -Wunused-but-set-variable we only care about variables that were
-  // referenced by the TU end.
-  SmallVector<const VarDecl *, 16> DeclsToErase;
-  for (const auto &Ref : RefsMinusAssignments) {
-    const VarDecl *VD = Ref.first;
-    // Only diagnose static file vars defined in the main file to match
-    // -Wunused-variable behavior and avoid false positives from header vars.
-    if (VD->isStaticFileVar() && SourceMgr.isInMainFile(VD->getLocation())) {
-      DiagnoseUnusedButSetDecl(VD, addDiag);
-      DeclsToErase.push_back(VD);
-    }
-  }
-  for (const VarDecl *VD : DeclsToErase) {
-    RefsMinusAssignments.erase(VD);
-  }
-
-  llvm::sort(DeclDiags,
-             [](const LocAndDiag &LHS, const LocAndDiag &RHS) -> bool {
-               // Sorting purely for determinism; matches behavior in
-               // SemaDecl.cpp.
-               return LHS.Loc.getRawEncoding() < RHS.Loc.getRawEncoding();
-             });
-  for (const LocAndDiag &D : DeclDiags) {
-    Diag(D.Loc, D.PD);
-  }
-
   // Check we've noticed that we're no longer parsing the initializer for every
   // variable. If we miss cases, then at best we have a performance issue and
   // at worst a rejects-valid bug.

>From 1916e833e77ee6fdf1e4ec75017020f8d3347afd Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Thu, 19 Feb 2026 18:59:14 +0100
Subject: [PATCH 11/20] remove redundant erase

---
 clang/lib/Sema/Sema.cpp | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 3c6f54eccce1c..8fee6f3b8e641 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1618,18 +1618,12 @@ void Sema::ActOnEndOfTranslationUnit() {
 
     // For -Wunused-but-set-variable we only care about variables that were
     // referenced by the TU end.
-    SmallVector<const VarDecl *, 16> DeclsToErase;
     for (const auto &Ref : RefsMinusAssignments) {
       const VarDecl *VD = Ref.first;
       // Only diagnose static file vars defined in the main file to match
       // -Wunused-variable behavior and avoid false positives from header vars.
-      if (VD->isStaticFileVar() && SourceMgr.isInMainFile(VD->getLocation())) {
+      if (VD->isStaticFileVar() && SourceMgr.isInMainFile(VD->getLocation()))
         DiagnoseUnusedButSetDecl(VD, addDiag);
-        DeclsToErase.push_back(VD);
-      }
-    }
-    for (const VarDecl *VD : DeclsToErase) {
-      RefsMinusAssignments.erase(VD);
     }
 
     llvm::sort(DeclDiags,

>From 433fd8ee8afc1fc1648cde8140e5f46a6095036a Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Thu, 19 Feb 2026 19:45:01 +0100
Subject: [PATCH 12/20] properly organize tests and improve comment

---
 clang/lib/Sema/Sema.cpp                                     | 2 +-
 .../Inputs/warn-unused-but-set-static-global-header-test.c  | 3 ---
 clang/test/Sema/warn-unused-but-set-static-global.c         | 6 ++++--
 3 files changed, 5 insertions(+), 6 deletions(-)
 delete mode 100644 
clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c

diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 8fee6f3b8e641..a4775cf916a8c 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1629,7 +1629,7 @@ void Sema::ActOnEndOfTranslationUnit() {
     llvm::sort(DeclDiags,
               [](const LocAndDiag &LHS, const LocAndDiag &RHS) -> bool {
                 // Sorting purely for determinism; matches behavior in
-                // SemaDecl.cpp.
+                // Sema::ActOnPopScope.
                 return LHS.Loc.getRawEncoding() < RHS.Loc.getRawEncoding();
               });
     for (const LocAndDiag &D : DeclDiags)
diff --git 
a/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c 
b/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c
deleted file mode 100644
index f26c90733a2bd..0000000000000
--- a/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c
+++ /dev/null
@@ -1,3 +0,0 @@
-// expected-no-diagnostics
-// Test that header-defined static globals don't warn.
-#include "warn-unused-but-set-static-global-header.h"
diff --git a/clang/test/Sema/warn-unused-but-set-static-global.c 
b/clang/test/Sema/warn-unused-but-set-static-global.c
index 0a16486c45225..f37d389b8208d 100644
--- a/clang/test/Sema/warn-unused-but-set-static-global.c
+++ b/clang/test/Sema/warn-unused-but-set-static-global.c
@@ -1,5 +1,7 @@
-// RUN: %clang_cc1 -fsyntax-only -Wunused-but-set-variable -verify %s
-// RUN: %clang_cc1 -fsyntax-only -Wunused-but-set-variable -I %S -verify 
%S/Inputs/warn-unused-but-set-static-global-header-test.c
+// RUN: %clang_cc1 -fsyntax-only -Wunused-but-set-variable -I %S/Inputs 
-verify %s
+
+// Test that header-defined static globals don't warn.
+#include "warn-unused-but-set-static-global-header.h"
 
 #define NULL (void*)0
 

>From f6c649f698b72cd86550382d99f9d248fc076069 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Thu, 19 Feb 2026 20:30:31 +0100
Subject: [PATCH 13/20] diagnose all vars in anon namespaces

---
 clang/include/clang/AST/Decl.h                        | 5 +++++
 clang/lib/Sema/Sema.cpp                               | 8 +++++---
 clang/lib/Sema/SemaDecl.cpp                           | 4 ++--
 clang/lib/Sema/SemaExpr.cpp                           | 4 +---
 clang/test/Sema/warn-unused-but-set-static-global.cpp | 8 +++++---
 5 files changed, 18 insertions(+), 11 deletions(-)

diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 47db1ff8d5817..72732c1d7a0fb 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -1217,6 +1217,11 @@ class VarDecl : public DeclaratorDecl, public 
Redeclarable<VarDecl> {
     return isFileVarDecl() && getStorageClass() == SC_Static;
   }
 
+  /// Returns true if this is a file-scope variable with internal linkage.
+  bool hasInternalLinkageFileVar() const {
+    return isFileVarDecl() && !isExternallyVisible() && !isStaticDataMember();
+  }
+
   /// Returns true if a variable has extern or __private_extern__
   /// storage.
   bool hasExternalStorage() const {
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index a4775cf916a8c..1ee74ceb11cbb 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1620,9 +1620,11 @@ void Sema::ActOnEndOfTranslationUnit() {
     // referenced by the TU end.
     for (const auto &Ref : RefsMinusAssignments) {
       const VarDecl *VD = Ref.first;
-      // Only diagnose static file vars defined in the main file to match
-      // -Wunused-variable behavior and avoid false positives from header vars.
-      if (VD->isStaticFileVar() && SourceMgr.isInMainFile(VD->getLocation()))
+      // Only diagnose internal linkage file vars defined in the main file to
+      // match -Wunused-variable behavior and avoid false positives from
+      // headers.
+      if (VD->hasInternalLinkageFileVar() &&
+          SourceMgr.isInMainFile(VD->getLocation()))
         DiagnoseUnusedButSetDecl(VD, addDiag);
     }
 
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index b0e37251a3058..1bc495693ee52 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2305,8 +2305,8 @@ void Sema::ActOnPopScope(SourceLocation Loc, Scope *S) {
       if (const auto *RD = dyn_cast<RecordDecl>(D))
         DiagnoseUnusedNestedTypedefs(RD, addDiag);
       if (VarDecl *VD = dyn_cast<VarDecl>(D)) {
-        // Wait until end of TU to diagnose static globals.
-        if (!VD->isStaticFileVar()) {
+        // Wait until end of TU to diagnose internal linkage file vars.
+        if (!VD->hasInternalLinkageFileVar()) {
           DiagnoseUnusedButSetDecl(VD, addDiag);
           RefsMinusAssignments.erase(VD);
         }
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 513768f44b842..a762360e042b1 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -20480,9 +20480,7 @@ static void DoMarkVarDeclReferenced(
   // We skip static data members because they have external linkage.
   // TODO: static data members in anonymous namespaces have internal linkage 
and
   // should be diagnosed.
-  bool ShouldTrackForUnusedButSet =
-      Var->isStaticFileVar() && !Var->isStaticDataMember();
-  if ((Var->isLocalVarDeclOrParm() || ShouldTrackForUnusedButSet) &&
+  if ((Var->isLocalVarDeclOrParm() || Var->hasInternalLinkageFileVar()) &&
       !Var->hasExternalStorage()) {
     RefsMinusAssignments.insert({Var, 0}).first->getSecond()++;
   }
diff --git a/clang/test/Sema/warn-unused-but-set-static-global.cpp 
b/clang/test/Sema/warn-unused-but-set-static-global.cpp
index 2ec870777e91e..149e1b628e853 100644
--- a/clang/test/Sema/warn-unused-but-set-static-global.cpp
+++ b/clang/test/Sema/warn-unused-but-set-static-global.cpp
@@ -34,8 +34,9 @@ void f2() {
 }
 }
 
-// Anonymous namespace
+// Anonymous namespace (all vars have internal linkage)
 namespace {
+  int anon_ns_unused; // expected-warning {{variable 'anon_ns_unused' set but 
not used}}
   static int anon_ns_static_unused; // expected-warning {{variable 
'anon_ns_static_unused' set but not used}}
 
   // Should not warn on static data members in current implementation.
@@ -47,8 +48,9 @@ namespace {
   int AnonClass::unused_member = 0;
 
   void f3() {
-    anon_ns_static_unused = 1;
-    AnonClass::unused_member = 2;
+    anon_ns_unused = 1;
+    anon_ns_static_unused = 2;
+    AnonClass::unused_member = 3;
   }
 }
 

>From db27f0cc43a85f01b163bbddbaf73fa605647e8e Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Thu, 19 Feb 2026 20:47:19 +0100
Subject: [PATCH 14/20] add tests for unused attrs

---
 clang/test/Sema/warn-unused-but-set-static-global.cpp | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/clang/test/Sema/warn-unused-but-set-static-global.cpp 
b/clang/test/Sema/warn-unused-but-set-static-global.cpp
index 149e1b628e853..d9dc5878363ff 100644
--- a/clang/test/Sema/warn-unused-but-set-static-global.cpp
+++ b/clang/test/Sema/warn-unused-but-set-static-global.cpp
@@ -1,9 +1,13 @@
-// RUN: %clang_cc1 -fsyntax-only -Wunused-but-set-variable -verify -std=c++11 
%s
+// RUN: %clang_cc1 -fsyntax-only -Wunused-but-set-variable -verify -std=c++17 
%s
 
 static thread_local int tl_set_unused;  // expected-warning {{variable 
'tl_set_unused' set but not used}}
 static thread_local int tl_set_and_used;
 thread_local int tl_no_static_set_unused;
 
+// Warning should respect attributes.
+[[maybe_unused]] static int with_maybe_unused;
+__attribute__((unused)) static int with_unused_attr;
+
 void f0() {
   tl_set_unused = 1;
   tl_set_and_used = 2;
@@ -11,6 +15,9 @@ void f0() {
   (void)x;
 
   tl_no_static_set_unused = 3;
+
+  with_maybe_unused = 4;
+  with_unused_attr = 5;
 }
 
 namespace test {

>From 5ef62be2fd2852e9aa2fc1ce39a26f8e4f9fdbe1 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Mon, 23 Feb 2026 17:05:06 +0100
Subject: [PATCH 15/20] format, refactor, and organize tests

---
 clang/include/clang/AST/Decl.h                |  9 +--
 clang/include/clang/Sema/Sema.h               |  4 ++
 clang/lib/Sema/Sema.cpp                       | 13 ++--
 clang/lib/Sema/SemaDecl.cpp                   | 16 +++--
 clang/lib/Sema/SemaExpr.cpp                   |  4 +-
 .../warn-unused-but-set-static-global.cpp     | 63 +++++++++++++++----
 6 files changed, 70 insertions(+), 39 deletions(-)

diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 72732c1d7a0fb..61c2d08b98988 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -1212,14 +1212,9 @@ class VarDecl : public DeclaratorDecl, public 
Redeclarable<VarDecl> {
       && !isFileVarDecl();
   }
 
-  /// Returns true if a variable is a static file-scope variable.
-  bool isStaticFileVar() const {
-    return isFileVarDecl() && getStorageClass() == SC_Static;
-  }
-
   /// Returns true if this is a file-scope variable with internal linkage.
-  bool hasInternalLinkageFileVar() const {
-    return isFileVarDecl() && !isExternallyVisible() && !isStaticDataMember();
+  bool isInternalLinkageFileVar() const {
+    return isFileVarDecl() && !isExternallyVisible();
   }
 
   /// Returns true if a variable has extern or __private_extern__
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 832e46286194a..ec9e2489e107f 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4618,6 +4618,10 @@ class Sema final : public SemaBase {
 
   bool ShouldWarnIfUnusedFileScopedDecl(const DeclaratorDecl *D) const;
 
+  /// Determines whether the given source location is in the main file
+  /// and we're in a context where we should warn about unused entities.
+  bool isMainFileLoc(SourceLocation Loc) const;
+
   /// If it's a file scoped decl that must warn if not used, keep track
   /// of it.
   void MarkUnusedFileScopedDecl(const DeclaratorDecl *D);
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 1ee74ceb11cbb..481858493e5a2 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1623,17 +1623,16 @@ void Sema::ActOnEndOfTranslationUnit() {
       // Only diagnose internal linkage file vars defined in the main file to
       // match -Wunused-variable behavior and avoid false positives from
       // headers.
-      if (VD->hasInternalLinkageFileVar() &&
-          SourceMgr.isInMainFile(VD->getLocation()))
+      if (VD->isInternalLinkageFileVar() && isMainFileLoc(VD->getLocation()))
         DiagnoseUnusedButSetDecl(VD, addDiag);
     }
 
     llvm::sort(DeclDiags,
-              [](const LocAndDiag &LHS, const LocAndDiag &RHS) -> bool {
-                // Sorting purely for determinism; matches behavior in
-                // Sema::ActOnPopScope.
-                return LHS.Loc.getRawEncoding() < RHS.Loc.getRawEncoding();
-              });
+               [](const LocAndDiag &LHS, const LocAndDiag &RHS) -> bool {
+                 // Sorting purely for determinism; matches behavior in
+                 // Sema::ActOnPopScope.
+                 return LHS.Loc.getRawEncoding() < RHS.Loc.getRawEncoding();
+               });
     for (const LocAndDiag &D : DeclDiags)
       Diag(D.Loc, D.PD);
   }
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 1bc495693ee52..82b8846967457 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -1911,12 +1911,10 @@ bool Sema::mightHaveNonExternalLinkage(const 
DeclaratorDecl *D) {
   return !D->isExternallyVisible();
 }
 
-// FIXME: This needs to be refactored; some other isInMainFile users want
-// these semantics.
-static bool isMainFileLoc(const Sema &S, SourceLocation Loc) {
-  if (S.TUKind != TU_Complete || S.getLangOpts().IsHeaderFile)
+bool Sema::isMainFileLoc(SourceLocation Loc) const {
+  if (TUKind != TU_Complete || getLangOpts().IsHeaderFile)
     return false;
-  return S.SourceMgr.isInMainFile(Loc);
+  return SourceMgr.isInMainFile(Loc);
 }
 
 bool Sema::ShouldWarnIfUnusedFileScopedDecl(const DeclaratorDecl *D) const {
@@ -1945,7 +1943,7 @@ bool Sema::ShouldWarnIfUnusedFileScopedDecl(const 
DeclaratorDecl *D) const {
         return false;
     } else {
       // 'static inline' functions are defined in headers; don't warn.
-      if (FD->isInlined() && !isMainFileLoc(*this, FD->getLocation()))
+      if (FD->isInlined() && !isMainFileLoc(FD->getLocation()))
         return false;
     }
 
@@ -1956,7 +1954,7 @@ bool Sema::ShouldWarnIfUnusedFileScopedDecl(const 
DeclaratorDecl *D) const {
     // Constants and utility variables are defined in headers with internal
     // linkage; don't warn.  (Unlike functions, there isn't a convenient marker
     // like "inline".)
-    if (!isMainFileLoc(*this, VD->getLocation()))
+    if (!isMainFileLoc(VD->getLocation()))
       return false;
 
     if (Context.DeclMustBeEmitted(VD))
@@ -1970,7 +1968,7 @@ bool Sema::ShouldWarnIfUnusedFileScopedDecl(const 
DeclaratorDecl *D) const {
         VD->getMemberSpecializationInfo() && !VD->isOutOfLine())
       return false;
 
-    if (VD->isInline() && !isMainFileLoc(*this, VD->getLocation()))
+    if (VD->isInline() && !isMainFileLoc(VD->getLocation()))
       return false;
   } else {
     return false;
@@ -2306,7 +2304,7 @@ void Sema::ActOnPopScope(SourceLocation Loc, Scope *S) {
         DiagnoseUnusedNestedTypedefs(RD, addDiag);
       if (VarDecl *VD = dyn_cast<VarDecl>(D)) {
         // Wait until end of TU to diagnose internal linkage file vars.
-        if (!VD->hasInternalLinkageFileVar()) {
+        if (!VD->isInternalLinkageFileVar()) {
           DiagnoseUnusedButSetDecl(VD, addDiag);
           RefsMinusAssignments.erase(VD);
         }
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index a762360e042b1..9c969d9d62c87 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -20478,9 +20478,7 @@ static void DoMarkVarDeclReferenced(
       Var->mightBeUsableInConstantExpressions(SemaRef.Context);
 
   // We skip static data members because they have external linkage.
-  // TODO: static data members in anonymous namespaces have internal linkage 
and
-  // should be diagnosed.
-  if ((Var->isLocalVarDeclOrParm() || Var->hasInternalLinkageFileVar()) &&
+  if ((Var->isLocalVarDeclOrParm() || Var->isInternalLinkageFileVar()) &&
       !Var->hasExternalStorage()) {
     RefsMinusAssignments.insert({Var, 0}).first->getSecond()++;
   }
diff --git a/clang/test/Sema/warn-unused-but-set-static-global.cpp 
b/clang/test/Sema/warn-unused-but-set-static-global.cpp
index d9dc5878363ff..8dd471329b978 100644
--- a/clang/test/Sema/warn-unused-but-set-static-global.cpp
+++ b/clang/test/Sema/warn-unused-but-set-static-global.cpp
@@ -20,6 +20,7 @@ void f0() {
   with_unused_attr = 5;
 }
 
+// Named namespace.
 namespace test {
   static int set_unused;  // expected-warning {{variable 'set_unused' set but 
not used}}
   static int set_and_used;
@@ -32,6 +33,7 @@ namespace test {
   }
 }
 
+// Nested named namespace.
 namespace outer {
 namespace inner {
 static int nested_unused; // expected-warning {{variable 'nested_unused' set 
but not used}}
@@ -41,33 +43,23 @@ void f2() {
 }
 }
 
-// Anonymous namespace (all vars have internal linkage)
+// Anonymous namespace (all vars have internal linkage).
 namespace {
   int anon_ns_unused; // expected-warning {{variable 'anon_ns_unused' set but 
not used}}
   static int anon_ns_static_unused; // expected-warning {{variable 
'anon_ns_static_unused' set but not used}}
 
-  // Should not warn on static data members in current implementation.
-  class AnonClass {
-  public:
-    static int unused_member;
-  };
-
-  int AnonClass::unused_member = 0;
-
   void f3() {
     anon_ns_unused = 1;
     anon_ns_static_unused = 2;
-    AnonClass::unused_member = 3;
   }
 }
 
-// Function pointers at file scope (unused)
+// Function pointers at file scope.
 static void (*unused_func_ptr)(); // expected-warning {{variable 
'unused_func_ptr' set but not used}}
 void SetUnusedCallback(void (*f)()) {
   unused_func_ptr = f;
 }
 
-// Function pointers at file scope (used)
 static void (*used_func_ptr)();
 void SetUsedCallback(void (*f)()) {
   used_func_ptr = f;
@@ -77,7 +69,7 @@ void CallUsedCallback() {
     used_func_ptr();
 }
 
-// Static data members (have external linkage so should not warn).
+// Static data members (external linkage, should not warn).
 class MyClass {
 public:
   static int unused_static_member;
@@ -94,3 +86,48 @@ void f4() {
   int x = MyClass::used_static_member;
   (void)x;
 }
+
+// Static data members in a named namespace (external linkage, should not 
warn).
+namespace named {
+  struct NamedClass {
+    static int w;
+  };
+  int NamedClass::w = 0;
+}
+
+void f5() {
+  named::NamedClass::w = 4;
+}
+
+// Static data members in anonymous namespace (internal linkage, should warn).
+namespace {
+  class AnonClass {
+  public:
+    static int unused_member;
+    static int used_member;
+  };
+
+  int AnonClass::unused_member = 0; // expected-warning {{variable 
'unused_member' set but not used}}
+  int AnonClass::used_member = 0;
+}
+
+void f6() {
+  AnonClass::unused_member = 3;
+  AnonClass::used_member = 4;
+  int y = AnonClass::used_member;
+  (void)y;
+}
+
+// Static data members in nested anonymous namespace (internal linkage, should 
warn).
+namespace outer2 {
+  namespace {
+    struct NestedAnonClass {
+      static int v;
+    };
+    int NestedAnonClass::v = 0; // expected-warning {{variable 'v' set but not 
used}}
+  }
+}
+
+void f7() {
+  outer2::NestedAnonClass::v = 5;
+}

>From 41224c64b4d616ae79121cc4bc96e74178781f71 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Mon, 23 Feb 2026 17:19:25 +0100
Subject: [PATCH 16/20] fix new finding in test suite

---
 clang/lib/Sema/Sema.cpp                         | 3 ++-
 clang/test/SemaCXX/warn-variable-not-needed.cpp | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 481858493e5a2..38e92f4379bdf 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1606,7 +1606,8 @@ void Sema::ActOnEndOfTranslationUnit() {
 
   if (!Diags.isIgnored(diag::warn_unused_but_set_variable, SourceLocation())) {
     // Diagnose unused-but-set static globals in a deterministic order.
-    // Not tracking shadowing info for static globals; there's nothing to 
shadow.
+    // Not tracking shadowing info for static globals; there's nothing to
+    // shadow.
     struct LocAndDiag {
       SourceLocation Loc;
       PartialDiagnostic PD;
diff --git a/clang/test/SemaCXX/warn-variable-not-needed.cpp 
b/clang/test/SemaCXX/warn-variable-not-needed.cpp
index 103be189068f8..272c8998d15c0 100644
--- a/clang/test/SemaCXX/warn-variable-not-needed.cpp
+++ b/clang/test/SemaCXX/warn-variable-not-needed.cpp
@@ -18,7 +18,7 @@ namespace test2 {
   };
   namespace {
     struct foo : bah {
-      static char bar;
+      static char bar;  // expected-warning {{variable 'bar' set but not used}}
       virtual void zed();
     };
     void foo::zed() {

>From 9b68f68fa45077898de307f3462e55ebfebd4eda Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Wed, 11 Mar 2026 19:38:41 +0100
Subject: [PATCH 17/20] cleanup and organize defs and decls

---
 clang/include/clang/Sema/Sema.h |  8 ++++----
 clang/lib/Sema/Sema.cpp         | 10 ++++++++--
 clang/lib/Sema/SemaDecl.cpp     | 17 +++++------------
 3 files changed, 17 insertions(+), 18 deletions(-)

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index ec9e2489e107f..d092253ef5d9b 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -973,6 +973,10 @@ class Sema final : public SemaBase {
   /// unit because its type has no linkage and it's not extern "C".
   bool isExternalWithNoLinkageType(const ValueDecl *VD) const;
 
+  /// Determines whether the given source location is in the main file
+  /// and we're in a context where we should warn about unused entities.
+  bool isMainFileLoc(SourceLocation Loc) const;
+
   /// Obtain a sorted list of functions that are undefined but ODR-used.
   void getUndefinedButUsed(
       SmallVectorImpl<std::pair<NamedDecl *, SourceLocation>> &Undefined);
@@ -4618,10 +4622,6 @@ class Sema final : public SemaBase {
 
   bool ShouldWarnIfUnusedFileScopedDecl(const DeclaratorDecl *D) const;
 
-  /// Determines whether the given source location is in the main file
-  /// and we're in a context where we should warn about unused entities.
-  bool isMainFileLoc(SourceLocation Loc) const;
-
   /// If it's a file scoped decl that must warn if not used, keep track
   /// of it.
   void MarkUnusedFileScopedDecl(const DeclaratorDecl *D);
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 38e92f4379bdf..bb357ffd86be1 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -956,6 +956,12 @@ bool Sema::isExternalWithNoLinkageType(const ValueDecl 
*VD) const {
          !isFunctionOrVarDeclExternC(VD);
 }
 
+bool Sema::isMainFileLoc(SourceLocation Loc) const {
+  if (TUKind != TU_Complete || getLangOpts().IsHeaderFile)
+    return false;
+  return SourceMgr.isInMainFile(Loc);
+}
+
 /// Obtains a sorted list of functions and variables that are undefined but
 /// ODR-used.
 void Sema::getUndefinedButUsed(
@@ -1631,8 +1637,8 @@ void Sema::ActOnEndOfTranslationUnit() {
     llvm::sort(DeclDiags,
                [](const LocAndDiag &LHS, const LocAndDiag &RHS) -> bool {
                  // Sorting purely for determinism; matches behavior in
-                 // Sema::ActOnPopScope.
-                 return LHS.Loc.getRawEncoding() < RHS.Loc.getRawEncoding();
+                 // Sema::ActOnPopScope (operator< compares raw encoding).
+                 return LHS.Loc < RHS.Loc;
                });
     for (const LocAndDiag &D : DeclDiags)
       Diag(D.Loc, D.PD);
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 82b8846967457..55c97a4d129e7 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -1911,12 +1911,6 @@ bool Sema::mightHaveNonExternalLinkage(const 
DeclaratorDecl *D) {
   return !D->isExternallyVisible();
 }
 
-bool Sema::isMainFileLoc(SourceLocation Loc) const {
-  if (TUKind != TU_Complete || getLangOpts().IsHeaderFile)
-    return false;
-  return SourceMgr.isInMainFile(Loc);
-}
-
 bool Sema::ShouldWarnIfUnusedFileScopedDecl(const DeclaratorDecl *D) const {
   assert(D);
 
@@ -2302,12 +2296,11 @@ void Sema::ActOnPopScope(SourceLocation Loc, Scope *S) {
       DiagnoseUnusedDecl(D, addDiag);
       if (const auto *RD = dyn_cast<RecordDecl>(D))
         DiagnoseUnusedNestedTypedefs(RD, addDiag);
-      if (VarDecl *VD = dyn_cast<VarDecl>(D)) {
-        // Wait until end of TU to diagnose internal linkage file vars.
-        if (!VD->isInternalLinkageFileVar()) {
-          DiagnoseUnusedButSetDecl(VD, addDiag);
-          RefsMinusAssignments.erase(VD);
-        }
+      // Wait until end of TU to diagnose internal linkage file vars.
+      if (auto *VD = dyn_cast<VarDecl>(D);
+          VD && !VD->isInternalLinkageFileVar()) {
+        DiagnoseUnusedButSetDecl(VD, addDiag);
+        RefsMinusAssignments.erase(VD);
       }
     }
 

>From a3bbfdc1260e0025f5f69141bdc3792554299bb9 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Mon, 16 Mar 2026 22:03:39 +0100
Subject: [PATCH 18/20] Fix OpenMP CI failures caused by stale cache entries in
 linkage info

---
 clang/include/clang/AST/Decl.h | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 61c2d08b98988..0977ca965547e 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -1214,7 +1214,15 @@ class VarDecl : public DeclaratorDecl, public 
Redeclarable<VarDecl> {
 
   /// Returns true if this is a file-scope variable with internal linkage.
   bool isInternalLinkageFileVar() const {
-    return isFileVarDecl() && !isExternallyVisible();
+    // Calling isExternallyVisible() can trigger linkage computation/caching,
+    // which may produce stale results when a decl's DeclContext changes after
+    // creation (e.g., OpenMP declare mapper variables), so here we determine
+    // it syntactically instead.
+    if (!isFileVarDecl())
+      return false;
+    if (getStorageClass() == SC_Static)
+      return true;
+    return isInAnonymousNamespace();
   }
 
   /// Returns true if a variable has extern or __private_extern__

>From 2bc71922e91227b3f51c646c042dd7394a5b0d63 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Tue, 17 Mar 2026 20:25:15 +0100
Subject: [PATCH 19/20] Fix false positives for static data members

Use the canonical VarDecl as key for RefsMinusAssignments so refs are
tracked as a single entry for in-class decls and out-of-class defs.
---
 clang/include/clang/AST/Decl.h                |  4 +-
 clang/include/clang/Sema/Sema.h               |  3 ++
 clang/lib/Sema/SemaDecl.cpp                   |  4 +-
 clang/lib/Sema/SemaExpr.cpp                   | 13 ++++--
 clang/lib/Sema/SemaExprCXX.cpp                |  2 +-
 .../warn-unused-but-set-static-global.cpp     | 40 +++++++++++++++++--
 6 files changed, 54 insertions(+), 12 deletions(-)

diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 0977ca965547e..076d9ba935583 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -1220,7 +1220,9 @@ class VarDecl : public DeclaratorDecl, public 
Redeclarable<VarDecl> {
     // it syntactically instead.
     if (!isFileVarDecl())
       return false;
-    if (getStorageClass() == SC_Static)
+    // Linkage is determined by enclosing class/namespace for static data
+    // members.
+    if (getStorageClass() == SC_Static && !isStaticDataMember())
       return true;
     return isInAnonymousNamespace();
   }
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index d092253ef5d9b..a214a7aa9147b 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -7013,6 +7013,9 @@ class Sema final : public SemaBase {
   /// Increment when we find a reference; decrement when we find an ignored
   /// assignment.  Ultimately the value is 0 if every reference is an ignored
   /// assignment.
+  ///
+  /// Uses canonical VarDecl as key so in-class decls and out-of-class defs of
+  /// static data members get tracked as a single entry.
   llvm::DenseMap<const VarDecl *, int> RefsMinusAssignments;
 
   /// Used to control the generation of ExprWithCleanups.
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 55c97a4d129e7..b5428431f6dd2 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2218,7 +2218,7 @@ void Sema::DiagnoseUnusedButSetDecl(const VarDecl *VD,
   if (VD->hasAttr<ObjCPreciseLifetimeAttr>() && Ty->isObjCObjectPointerType())
     return;
 
-  auto iter = RefsMinusAssignments.find(VD);
+  auto iter = RefsMinusAssignments.find(VD->getCanonicalDecl());
   if (iter == RefsMinusAssignments.end())
     return;
 
@@ -2300,7 +2300,7 @@ void Sema::ActOnPopScope(SourceLocation Loc, Scope *S) {
       if (auto *VD = dyn_cast<VarDecl>(D);
           VD && !VD->isInternalLinkageFileVar()) {
         DiagnoseUnusedButSetDecl(VD, addDiag);
-        RefsMinusAssignments.erase(VD);
+        RefsMinusAssignments.erase(VD->getCanonicalDecl());
       }
     }
 
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 9c969d9d62c87..71f49af6ffdf0 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -20477,10 +20477,15 @@ static void DoMarkVarDeclReferenced(
   bool UsableInConstantExpr =
       Var->mightBeUsableInConstantExpressions(SemaRef.Context);
 
-  // We skip static data members because they have external linkage.
-  if ((Var->isLocalVarDeclOrParm() || Var->isInternalLinkageFileVar()) &&
-      !Var->hasExternalStorage()) {
-    RefsMinusAssignments.insert({Var, 0}).first->getSecond()++;
+  // Only track variables with internal linkage or local scope.
+  // Use canonical decl so in-class declarations and out-of-class definitions
+  // of static data members in anonymous namespaces are tracked as a single
+  // entry.
+  const VarDecl *CanonVar = Var->getCanonicalDecl();
+  if ((CanonVar->isLocalVarDeclOrParm() ||
+       CanonVar->isInternalLinkageFileVar()) &&
+      !CanonVar->hasExternalStorage()) {
+    RefsMinusAssignments.insert({CanonVar, 0}).first->getSecond()++;
   }
 
   // C++20 [expr.const]p12:
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 5a5bbf4d900dc..3922dcb155c2f 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -7483,7 +7483,7 @@ static void MaybeDecrementCount(
   if ((IsCompoundAssign || isIncrementDecrementUnaryOp) &&
       VD->getType().isVolatileQualified())
     return;
-  auto iter = RefsMinusAssignments.find(VD);
+  auto iter = RefsMinusAssignments.find(VD->getCanonicalDecl());
   if (iter == RefsMinusAssignments.end())
     return;
   iter->getSecond()--;
diff --git a/clang/test/Sema/warn-unused-but-set-static-global.cpp 
b/clang/test/Sema/warn-unused-but-set-static-global.cpp
index 8dd471329b978..1ddd7eb72ce29 100644
--- a/clang/test/Sema/warn-unused-but-set-static-global.cpp
+++ b/clang/test/Sema/warn-unused-but-set-static-global.cpp
@@ -103,11 +103,11 @@ void f5() {
 namespace {
   class AnonClass {
   public:
-    static int unused_member;
+    static int unused_member; // expected-warning {{variable 'unused_member' 
set but not used}}
     static int used_member;
   };
 
-  int AnonClass::unused_member = 0; // expected-warning {{variable 
'unused_member' set but not used}}
+  int AnonClass::unused_member = 0;
   int AnonClass::used_member = 0;
 }
 
@@ -122,12 +122,44 @@ void f6() {
 namespace outer2 {
   namespace {
     struct NestedAnonClass {
-      static int v;
+      static int v; // expected-warning {{variable 'v' set but not used}}
     };
-    int NestedAnonClass::v = 0; // expected-warning {{variable 'v' set but not 
used}}
+    int NestedAnonClass::v = 0;
   }
 }
 
 void f7() {
   outer2::NestedAnonClass::v = 5;
 }
+
+// Static data members set inside methods, read outside.
+namespace {
+  struct SetInMethod {
+    static int x;
+    static int y; // expected-warning {{variable 'y' set but not used}}
+    void setX() { x = 1; }
+    void setY() { y = 1; }
+  };
+  int SetInMethod::x;
+  int SetInMethod::y;
+}
+
+void f8() {
+  SetInMethod s;
+  s.setX();
+  s.setY();
+  int v = SetInMethod::x;
+  (void)v;  // only x is read
+}
+
+// External linkage static data members set inside methods.
+struct ExtSetInMethod {
+  static int x;
+  void set() { x = 1; }
+};
+int ExtSetInMethod::x;
+
+void f9() {
+  ExtSetInMethod e;
+  e.set();
+}

>From e199cde8feebd3a7be2295a6fd0f6c9788aba078 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Tue, 17 Mar 2026 23:09:44 +0100
Subject: [PATCH 20/20] Don't warn on file-scope volatiles

---
 clang/lib/Sema/Sema.cpp                             | 2 +-
 clang/lib/Sema/SemaDecl.cpp                         | 5 +++++
 clang/test/Sema/warn-unused-but-set-static-global.c | 2 +-
 3 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index bb357ffd86be1..483c4c5f9cbc0 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1637,7 +1637,7 @@ void Sema::ActOnEndOfTranslationUnit() {
     llvm::sort(DeclDiags,
                [](const LocAndDiag &LHS, const LocAndDiag &RHS) -> bool {
                  // Sorting purely for determinism; matches behavior in
-                 // Sema::ActOnPopScope (operator< compares raw encoding).
+                 // Sema::ActOnPopScope.
                  return LHS.Loc < RHS.Loc;
                });
     for (const LocAndDiag &D : DeclDiags)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index b5428431f6dd2..cf41eb701185a 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2206,6 +2206,11 @@ void Sema::DiagnoseUnusedButSetDecl(const VarDecl *VD,
       return;
   }
 
+  // Don't warn on volatile file-scope variables. They are visible beyond their
+  // declaring function and writes to them could be observable side effects.
+  if (VD->getType().isVolatileQualified() && VD->isFileVarDecl())
+    return;
+
   // Don't warn about __block Objective-C pointer variables, as they might
   // be assigned in the block but not used elsewhere for the purpose of 
lifetime
   // extension.
diff --git a/clang/test/Sema/warn-unused-but-set-static-global.c 
b/clang/test/Sema/warn-unused-but-set-static-global.c
index f37d389b8208d..7c4d20a42b2d2 100644
--- a/clang/test/Sema/warn-unused-but-set-static-global.c
+++ b/clang/test/Sema/warn-unused-but-set-static-global.c
@@ -55,7 +55,7 @@ void f3() {
   (void)x;
 }
 
-static volatile int vol_set; // expected-warning {{variable 'vol_set' set but 
not used}}
+static volatile int vol_set;
 void f4() {
   vol_set = 1;
 }

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to