[clang] [clang] Extend -Wunused-but-set-variable to static globals (PR #178342)
John Paul Jepko via cfe-commits
cfe-commits at lists.llvm.org
Tue Mar 17 12:59:35 PDT 2026
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 <john.jepko at ericsson.com>
Date: Wed, 28 Jan 2026 02:37:45 +0100
Subject: [PATCH 01/19] [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 <john.jepko at ericsson.com>
Date: Wed, 28 Jan 2026 18:32:20 +0100
Subject: [PATCH 02/19] 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 <john.jepko at ericsson.com>
Date: Thu, 29 Jan 2026 21:56:23 +0100
Subject: [PATCH 03/19] 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 <john.jepko at ericsson.com>
Date: Mon, 2 Feb 2026 23:05:06 +0100
Subject: [PATCH 04/19] 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 <john.jepko at ericsson.com>
Date: Tue, 3 Feb 2026 01:43:14 +0100
Subject: [PATCH 05/19] 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 <john.jepko at ericsson.com>
Date: Tue, 3 Feb 2026 02:10:11 +0100
Subject: [PATCH 06/19] 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 <john.jepko at ericsson.com>
Date: Tue, 3 Feb 2026 22:12:18 +0100
Subject: [PATCH 07/19] 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 <john.jepko at ericsson.com>
Date: Mon, 16 Feb 2026 20:57:12 +0100
Subject: [PATCH 08/19] 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 <john.jepko at ericsson.com>
Date: Wed, 18 Feb 2026 21:43:12 +0100
Subject: [PATCH 09/19] 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 <john.jepko at ericsson.com>
Date: Thu, 19 Feb 2026 18:55:33 +0100
Subject: [PATCH 10/19] 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 <john.jepko at ericsson.com>
Date: Thu, 19 Feb 2026 18:59:14 +0100
Subject: [PATCH 11/19] 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 <john.jepko at ericsson.com>
Date: Thu, 19 Feb 2026 19:45:01 +0100
Subject: [PATCH 12/19] 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 <john.jepko at ericsson.com>
Date: Thu, 19 Feb 2026 20:30:31 +0100
Subject: [PATCH 13/19] 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 <john.jepko at ericsson.com>
Date: Thu, 19 Feb 2026 20:47:19 +0100
Subject: [PATCH 14/19] 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 <john.jepko at ericsson.com>
Date: Mon, 23 Feb 2026 17:05:06 +0100
Subject: [PATCH 15/19] 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 <john.jepko at ericsson.com>
Date: Mon, 23 Feb 2026 17:19:25 +0100
Subject: [PATCH 16/19] 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 <john.jepko at ericsson.com>
Date: Wed, 11 Mar 2026 19:38:41 +0100
Subject: [PATCH 17/19] 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 <john.jepko at ericsson.com>
Date: Mon, 16 Mar 2026 22:03:39 +0100
Subject: [PATCH 18/19] 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 <john.jepko at ericsson.com>
Date: Tue, 17 Mar 2026 20:25:15 +0100
Subject: [PATCH 19/19] 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();
+}
More information about the cfe-commits
mailing list