[clang] [clang] Extend -Wunused-but-set-variable to static globals (PR #178342)
John Paul Jepko via cfe-commits
cfe-commits at lists.llvm.org
Thu Feb 19 11:50:06 PST 2026
https://github.com/jpjepko updated https://github.com/llvm/llvm-project/pull/178342
>From abb706af37605b5e8d664882d996e307aa2994cf Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Wed, 28 Jan 2026 02:37:45 +0100
Subject: [PATCH 01/14] [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 d53527af38653..a533593f25c59 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1620,6 +1620,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 5795a71b5cae8..46434ce1511e5 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -20257,7 +20257,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 ca8f1b1da932dfe12a4f1ed5e3b73a4610c8843e Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Wed, 28 Jan 2026 18:32:20 +0100
Subject: [PATCH 02/14] 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 46434ce1511e5..0c29bb40fe6e7 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -20257,9 +20257,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 68a66bfeefbae121948d15a8c6ef58d21b962cfe Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Thu, 29 Jan 2026 21:56:23 +0100
Subject: [PATCH 03/14] 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 907b7b367f19b..2b9218db95355 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2294,8 +2294,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 c06baa6cdf50a4a080c2a8570f91732ca15d02c2 Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Mon, 2 Feb 2026 23:05:06 +0100
Subject: [PATCH 04/14] 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 5c46c912186c4..3040514ab7dad 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 a533593f25c59..4266d073f358e 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1636,7 +1636,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 2b9218db95355..b86bff897289a 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2295,9 +2295,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 0c29bb40fe6e7..d03f6cf4636e0 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -20257,10 +20257,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 44c2f6f464094b547a04a66958fb2cac4e570bc8 Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Tue, 3 Feb 2026 01:43:14 +0100
Subject: [PATCH 05/14] 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 4266d073f358e..0f0a749701615 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1620,9 +1620,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;
@@ -1632,12 +1631,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);
@@ -1646,8 +1645,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 b86bff897289a..82f56b60aef65 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2294,7 +2294,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 0c837b08d43e2e47194e27013f0471c7de167285 Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Tue, 3 Feb 2026 02:10:11 +0100
Subject: [PATCH 06/14] 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 0f0a749701615..86770549b9369 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1633,15 +1633,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 6c95f820df0aded5f1dd4b96e0efa2ec1dbb8afb Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Tue, 3 Feb 2026 22:12:18 +0100
Subject: [PATCH 07/14] 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 e9fbbb05228b51510267b7053466ffefa2bddb85 Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Mon, 16 Feb 2026 20:57:12 +0100
Subject: [PATCH 08/14] 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 d03f6cf4636e0..59bdba97cd2e3 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -20257,9 +20257,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 f65b6503ffd43ac4c00c7f07ccd3f6a254cc97ef Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Wed, 18 Feb 2026 21:43:12 +0100
Subject: [PATCH 09/14] 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 e22e7e0fce05c..7edb5c8e35fd6 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -141,6 +141,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 c3734b5868c1ab98e994e891c13bacfa57499d2a Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Thu, 19 Feb 2026 18:55:33 +0100
Subject: [PATCH 10/14] 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 7edb5c8e35fd6..593eb56bd918e 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -142,7 +142,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 86770549b9369..e0515b4bb2645 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1589,6 +1589,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.
@@ -1620,43 +1658,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 b95f71522b8f26b1e6be9376cfbbd7cf14f6f5e9 Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Thu, 19 Feb 2026 18:59:14 +0100
Subject: [PATCH 11/14] 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 e0515b4bb2645..fc83256cbbf10 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1603,18 +1603,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 34c2a232a63561344682dcfa946cb846f797e67b Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Thu, 19 Feb 2026 19:45:01 +0100
Subject: [PATCH 12/14] 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 fc83256cbbf10..aea0fdd6bcac6 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1614,7 +1614,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 22c9511a247de7c4630011dab4acb46574d08e22 Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Thu, 19 Feb 2026 20:30:31 +0100
Subject: [PATCH 13/14] 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 3040514ab7dad..0ca988d089b11 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 aea0fdd6bcac6..081f9049246a4 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1605,9 +1605,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 82f56b60aef65..3f473e3e11fa4 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2294,8 +2294,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 59bdba97cd2e3..0978584606baf 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -20260,9 +20260,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 ef014657aec9d73443f06a80474257e83841d379 Mon Sep 17 00:00:00 2001
From: John Jepko <john.jepko at ericsson.com>
Date: Thu, 19 Feb 2026 20:47:19 +0100
Subject: [PATCH 14/14] 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 {
More information about the cfe-commits
mailing list