https://github.com/jpjepko updated https://github.com/llvm/llvm-project/pull/178342
>From 4bae297c12fda0bb19a15fd6c460005bd6a608f6 Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Wed, 28 Jan 2026 02:37:45 +0100 Subject: [PATCH 01/20] [clang] Extend -Wunused-but-set-variable to static globals This change extends -Wunused-but-set-variable to diagnose static globals within the translation unit that are assigned to within function bodies, but whose values are never used. Fixes #148361 --- clang/lib/Sema/Sema.cpp | 32 +++++++ clang/lib/Sema/SemaExpr.cpp | 6 +- clang/test/C/C2y/n3622.c | 8 +- .../Sema/warn-unused-but-set-static-global.c | 84 +++++++++++++++++++ 4 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 clang/test/Sema/warn-unused-but-set-static-global.c diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index 8b1d0398cf65d..7948655e5bdd2 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -1635,6 +1635,38 @@ void Sema::ActOnEndOfTranslationUnit() { if (Context.hasAnyFunctionEffects()) performFunctionEffectAnalysis(Context.getTranslationUnitDecl()); + // diagnose unused-but-set static globals in a deterministic order + // + // not trackings shadowing info for static globals; there's nothing to shadow + struct LocAndDiag { + SourceLocation Loc; + PartialDiagnostic PD; + }; + SmallVector<LocAndDiag, 16> DeclDiags; + auto addDiag = [&DeclDiags](SourceLocation Loc, PartialDiagnostic PD) { + DeclDiags.push_back(LocAndDiag{Loc, std::move(PD)}); + }; + + // for -Wunused-but-set-variable we only care about variables that were + // referenced by the TU end + for (const auto &Ref : RefsMinusAssignments) { + const VarDecl *VD = Ref.first; + if (VD->isFileVarDecl() && VD->getStorageClass() == SC_Static) { + DiagnoseUnusedButSetDecl(VD, addDiag); + RefsMinusAssignments.erase(VD); + } + } + + llvm::sort(DeclDiags, + [](const LocAndDiag &LHS, const LocAndDiag &RHS) -> bool { + // sorting purely for determinism; matches behavior in + // SemaDecl.cpp + return LHS.Loc.getRawEncoding() < RHS.Loc.getRawEncoding(); + }); + for (const LocAndDiag &D : DeclDiags) { + Diag(D.Loc, D.PD); + } + // Check we've noticed that we're no longer parsing the initializer for every // variable. If we miss cases, then at best we have a performance issue and // at worst a rejects-valid bug. diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index db6d93ce54791..85eaccabedd20 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -20477,7 +20477,11 @@ static void DoMarkVarDeclReferenced( bool UsableInConstantExpr = Var->mightBeUsableInConstantExpressions(SemaRef.Context); - if (Var->isLocalVarDeclOrParm() && !Var->hasExternalStorage()) { + bool StaticGlobalReferenced = Var->isFileVarDecl() && + Var->getStorageClass() == SC_Static && + !Var->isStaticDataMember(); + if ((Var->isLocalVarDeclOrParm() || StaticGlobalReferenced) && + !Var->hasExternalStorage()) { RefsMinusAssignments.insert({Var, 0}).first->getSecond()++; } diff --git a/clang/test/C/C2y/n3622.c b/clang/test/C/C2y/n3622.c index 95b92e8f235a8..d90b0c51d3ccf 100644 --- a/clang/test/C/C2y/n3622.c +++ b/clang/test/C/C2y/n3622.c @@ -1,7 +1,7 @@ -// RUN: %clang_cc1 -verify=good -pedantic -Wall -std=c2y %s -// RUN: %clang_cc1 -verify=compat,expected -pedantic -Wall -Wpre-c2y-compat -std=c2y %s -// RUN: %clang_cc1 -verify=ped,expected -pedantic -Wall -std=c23 %s -// RUN: %clang_cc1 -verify=ped,expected -pedantic -Wall -std=c17 %s +// RUN: %clang_cc1 -verify=good -pedantic -Wall -Wno-unused-but-set-variable -std=c2y %s +// RUN: %clang_cc1 -verify=compat,expected -pedantic -Wall -Wno-unused-but-set-variable -Wpre-c2y-compat -std=c2y %s +// RUN: %clang_cc1 -verify=ped,expected -pedantic -Wall -Wno-unused-but-set-variable -std=c23 %s +// RUN: %clang_cc1 -verify=ped,expected -pedantic -Wall -Wno-unused-but-set-variable -std=c17 %s // good-no-diagnostics /* WG14 N3622: Clang 22 diff --git a/clang/test/Sema/warn-unused-but-set-static-global.c b/clang/test/Sema/warn-unused-but-set-static-global.c new file mode 100644 index 0000000000000..a68136749047d --- /dev/null +++ b/clang/test/Sema/warn-unused-but-set-static-global.c @@ -0,0 +1,84 @@ +// RUN: %clang_cc1 -fsyntax-only -Wunused-but-set-variable -verify %s + +static int set_unused; // expected-warning {{variable 'set_unused' set but not used}} +static int set_and_used; +static int only_used; +static int addr_taken; +extern int external_var; // no warning (external linkage) +extern int global_var; // no warning (not static) + +void f1() { + set_unused = 1; + set_and_used = 2; + + int x = set_and_used; + (void)x; + + int y = only_used; + (void)y; + + int *p = &addr_taken; + (void)p; + + external_var = 3; + global_var = 4; +} + +// test across multiple functions +static int set_used1; +static int set_used2; + +static int set1; // expected-warning {{variable 'set1' set but not used}} +static int set2; // expected-warning {{variable 'set2' set but not used}} + +void f2() { + set1 = 1; + set_used1 = 1; + + int x = set_used2; + (void)x; +} + +void f3() { + set2 = 2; + set_used2 = 2; + + int x = set_used1; + (void)x; +} + +static volatile int vol_set; // expected-warning {{variable 'vol_set' set but not used}} +void f4() { + vol_set = 1; +} + +// read and use +static int compound; // expected-warning{{variable 'compound' set but not used}} +static volatile int vol_compound; +static int unary; // expected-warning{{variable 'unary' set but not used}} +static volatile int vol_unary; +void f5() { + compound += 1; + vol_compound += 1; + unary++; + vol_unary++; +} + +struct S { + int i; +}; +static struct S s_set; // expected-warning{{variable 's_set' set but not used}} +static struct S s_used; +void f6() { + struct S t; + s_set = t; + t = s_used; +} + +// multiple assignments +static int multi; // expected-warning{{variable 'multi' set but not used}} +void f7() { + multi = 1; + multi = 2; + multi = 3; +} >From 96aa029432924f76345ee41b142d9e3453f10600 Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Wed, 28 Jan 2026 18:32:20 +0100 Subject: [PATCH 02/20] fix function ptr and namespace issues, add tests --- clang/lib/Sema/SemaExpr.cpp | 6 ++-- .../Sema/warn-unused-but-set-static-global.c | 35 +++++++++++++++++++ .../warn-unused-but-set-static-global.cpp | 28 +++++++++++++++ 3 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 clang/test/Sema/warn-unused-but-set-static-global.cpp diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 85eaccabedd20..282d01b209226 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -20477,9 +20477,9 @@ static void DoMarkVarDeclReferenced( bool UsableInConstantExpr = Var->mightBeUsableInConstantExpressions(SemaRef.Context); - bool StaticGlobalReferenced = Var->isFileVarDecl() && - Var->getStorageClass() == SC_Static && - !Var->isStaticDataMember(); + bool StaticGlobalReferenced = + Var->isFileVarDecl() && Var->getStorageClass() == SC_Static && + !Var->isStaticDataMember() && !Var->getType()->isFunctionPointerType(); if ((Var->isLocalVarDeclOrParm() || StaticGlobalReferenced) && !Var->hasExternalStorage()) { RefsMinusAssignments.insert({Var, 0}).first->getSecond()++; diff --git a/clang/test/Sema/warn-unused-but-set-static-global.c b/clang/test/Sema/warn-unused-but-set-static-global.c index a68136749047d..406b9af116646 100644 --- a/clang/test/Sema/warn-unused-but-set-static-global.c +++ b/clang/test/Sema/warn-unused-but-set-static-global.c @@ -1,5 +1,10 @@ // RUN: %clang_cc1 -fsyntax-only -Wunused-but-set-variable -verify %s +#define NULL (void*)0 + +void *set(int size); +void func_call(void *); + static int set_unused; // expected-warning {{variable 'set_unused' set but not used}} static int set_and_used; static int only_used; @@ -82,3 +87,33 @@ void f7() { multi = 2; multi = 3; } + +// unused pointers +static int *unused_ptr; // expected-warning{{variable 'unused_ptr' set but not used}} +static char *str_ptr; // expected-warning{{variable 'str_ptr' set but not used}} +void f8() { + unused_ptr = set(5); + str_ptr = "hello"; +} + +// used pointers +void a(void *); +static int *used_ptr; +static int *param_ptr; +static int *null_check_ptr; +void f9() { + used_ptr = set(5); + *used_ptr = 5; + + param_ptr = set(5); + func_call(param_ptr); + + null_check_ptr = set(5); + if (null_check_ptr == NULL) {} +} + +// function pointers +static void (*sandboxing_callback)(); +void SetSandboxingCallback(void (*f)()) { + sandboxing_callback = f; +} diff --git a/clang/test/Sema/warn-unused-but-set-static-global.cpp b/clang/test/Sema/warn-unused-but-set-static-global.cpp new file mode 100644 index 0000000000000..ead7599d6cc78 --- /dev/null +++ b/clang/test/Sema/warn-unused-but-set-static-global.cpp @@ -0,0 +1,28 @@ +// RUN: %clang_cc1 -fsyntax-only -Wunused-but-set-variable -verify -std=c++11 %s + +namespace test { + static int set_unused; // expected-warning {{variable 'set_unused' set but not used}} + static int set_and_used; + + void f1() { + set_unused = 1; + set_and_used = 2; + int x = set_and_used; + (void)x; + } + + // function pointer in namespace + static void (*sandboxing_callback)(); + void SetSandboxingCallback(void (*f)()) { + sandboxing_callback = f; + } +} + +namespace outer { +namespace inner { +static int nested_unused; // expected-warning {{variable 'nested_unused' set but not used}} +void f2() { + nested_unused = 5; +} +} +} >From 99072c63061644872d39a1cc3becd7562df21e49 Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Thu, 29 Jan 2026 21:56:23 +0100 Subject: [PATCH 03/20] ensure static globals only diagnosed at end of TU --- clang/lib/Sema/SemaDecl.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 101d5085b980b..2836b00ed980d 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -2305,8 +2305,13 @@ void Sema::ActOnPopScope(SourceLocation Loc, Scope *S) { if (const auto *RD = dyn_cast<RecordDecl>(D)) DiagnoseUnusedNestedTypedefs(RD, addDiag); if (VarDecl *VD = dyn_cast<VarDecl>(D)) { - DiagnoseUnusedButSetDecl(VD, addDiag); - RefsMinusAssignments.erase(VD); + // wait until end of TU to diagnose static globals + bool isStaticGlobal = + VD->isFileVarDecl() && VD->getStorageClass() == SC_Static; + if (!isStaticGlobal) { + DiagnoseUnusedButSetDecl(VD, addDiag); + RefsMinusAssignments.erase(VD); + } } } >From 16710426006ebd59fbd4087de7f546b49b4ae346 Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Mon, 2 Feb 2026 23:05:06 +0100 Subject: [PATCH 04/20] skip header-defined static globals, add tests and refactor --- clang/include/clang/AST/Decl.h | 5 +++++ clang/lib/Sema/Sema.cpp | 4 +++- clang/lib/Sema/SemaDecl.cpp | 4 +--- clang/lib/Sema/SemaExpr.cpp | 8 ++++---- .../warn-unused-but-set-static-global-header-test.c | 3 +++ .../Inputs/warn-unused-but-set-static-global-header.h | 3 +++ clang/test/Sema/warn-unused-but-set-static-global.c | 1 + 7 files changed, 20 insertions(+), 8 deletions(-) create mode 100644 clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c create mode 100644 clang/test/Sema/Inputs/warn-unused-but-set-static-global-header.h diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index c3cd74a5b34db..47db1ff8d5817 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -1212,6 +1212,11 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> { && !isFileVarDecl(); } + /// Returns true if a variable is a static file-scope variable. + bool isStaticFileVar() const { + return isFileVarDecl() && getStorageClass() == SC_Static; + } + /// Returns true if a variable has extern or __private_extern__ /// storage. bool hasExternalStorage() const { diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index 7948655e5bdd2..a64e268e6ab35 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -1651,7 +1651,9 @@ void Sema::ActOnEndOfTranslationUnit() { // referenced by the TU end for (const auto &Ref : RefsMinusAssignments) { const VarDecl *VD = Ref.first; - if (VD->isFileVarDecl() && VD->getStorageClass() == SC_Static) { + // only diagnose static file vars defined in the main file to match + // -Wunused-variable behavior and avoid false positives from header vars + if (VD->isStaticFileVar() && SourceMgr.isInMainFile(VD->getLocation())) { DiagnoseUnusedButSetDecl(VD, addDiag); RefsMinusAssignments.erase(VD); } diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 2836b00ed980d..467c06582299f 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -2306,9 +2306,7 @@ void Sema::ActOnPopScope(SourceLocation Loc, Scope *S) { DiagnoseUnusedNestedTypedefs(RD, addDiag); if (VarDecl *VD = dyn_cast<VarDecl>(D)) { // wait until end of TU to diagnose static globals - bool isStaticGlobal = - VD->isFileVarDecl() && VD->getStorageClass() == SC_Static; - if (!isStaticGlobal) { + if (!VD->isStaticFileVar()) { DiagnoseUnusedButSetDecl(VD, addDiag); RefsMinusAssignments.erase(VD); } diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 282d01b209226..6013a0cad06e5 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -20477,10 +20477,10 @@ static void DoMarkVarDeclReferenced( bool UsableInConstantExpr = Var->mightBeUsableInConstantExpressions(SemaRef.Context); - bool StaticGlobalReferenced = - Var->isFileVarDecl() && Var->getStorageClass() == SC_Static && - !Var->isStaticDataMember() && !Var->getType()->isFunctionPointerType(); - if ((Var->isLocalVarDeclOrParm() || StaticGlobalReferenced) && + bool ShouldTrackForUnusedButSet = Var->isStaticFileVar() && + !Var->isStaticDataMember() && + !Var->getType()->isFunctionPointerType(); + if ((Var->isLocalVarDeclOrParm() || ShouldTrackForUnusedButSet) && !Var->hasExternalStorage()) { RefsMinusAssignments.insert({Var, 0}).first->getSecond()++; } diff --git a/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c b/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c new file mode 100644 index 0000000000000..e4c316b37d15f --- /dev/null +++ b/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c @@ -0,0 +1,3 @@ +// expected-no-diagnostics +// test that header-defined static globals don't warn +#include "warn-unused-but-set-static-global-header.h" diff --git a/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header.h b/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header.h new file mode 100644 index 0000000000000..a06e9e66a34f4 --- /dev/null +++ b/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header.h @@ -0,0 +1,3 @@ +// header file for testing that header-defined static globals don't warn +static int header_set_unused = 0; +static void header_init() { header_set_unused = 1; } diff --git a/clang/test/Sema/warn-unused-but-set-static-global.c b/clang/test/Sema/warn-unused-but-set-static-global.c index 406b9af116646..bf099d3c5759c 100644 --- a/clang/test/Sema/warn-unused-but-set-static-global.c +++ b/clang/test/Sema/warn-unused-but-set-static-global.c @@ -1,4 +1,5 @@ // RUN: %clang_cc1 -fsyntax-only -Wunused-but-set-variable -verify %s +// RUN: %clang_cc1 -fsyntax-only -Wunused-but-set-variable -I %S -verify %S/Inputs/warn-unused-but-set-static-global-header-test.c #define NULL (void*)0 >From f8db9c6e46d3979d96782bb713d2b27b54b06bff Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Tue, 3 Feb 2026 01:43:14 +0100 Subject: [PATCH 05/20] fix comment formatting --- clang/lib/Sema/Sema.cpp | 17 ++++++++--------- clang/lib/Sema/SemaDecl.cpp | 2 +- ...n-unused-but-set-static-global-header-test.c | 2 +- .../warn-unused-but-set-static-global-header.h | 2 +- .../Sema/warn-unused-but-set-static-global.c | 16 ++++++++-------- .../Sema/warn-unused-but-set-static-global.cpp | 2 +- 6 files changed, 20 insertions(+), 21 deletions(-) diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index a64e268e6ab35..3fff5526158d0 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -1635,9 +1635,8 @@ void Sema::ActOnEndOfTranslationUnit() { if (Context.hasAnyFunctionEffects()) performFunctionEffectAnalysis(Context.getTranslationUnitDecl()); - // diagnose unused-but-set static globals in a deterministic order - // - // not trackings shadowing info for static globals; there's nothing to shadow + // Diagnose unused-but-set static globals in a deterministic order. + // Not tracking shadowing info for static globals; there's nothing to shadow. struct LocAndDiag { SourceLocation Loc; PartialDiagnostic PD; @@ -1647,12 +1646,12 @@ void Sema::ActOnEndOfTranslationUnit() { DeclDiags.push_back(LocAndDiag{Loc, std::move(PD)}); }; - // for -Wunused-but-set-variable we only care about variables that were - // referenced by the TU end + // For -Wunused-but-set-variable we only care about variables that were + // referenced by the TU end. for (const auto &Ref : RefsMinusAssignments) { const VarDecl *VD = Ref.first; - // only diagnose static file vars defined in the main file to match - // -Wunused-variable behavior and avoid false positives from header vars + // Only diagnose static file vars defined in the main file to match + // -Wunused-variable behavior and avoid false positives from header vars. if (VD->isStaticFileVar() && SourceMgr.isInMainFile(VD->getLocation())) { DiagnoseUnusedButSetDecl(VD, addDiag); RefsMinusAssignments.erase(VD); @@ -1661,8 +1660,8 @@ void Sema::ActOnEndOfTranslationUnit() { llvm::sort(DeclDiags, [](const LocAndDiag &LHS, const LocAndDiag &RHS) -> bool { - // sorting purely for determinism; matches behavior in - // SemaDecl.cpp + // Sorting purely for determinism; matches behavior in + // SemaDecl.cpp. return LHS.Loc.getRawEncoding() < RHS.Loc.getRawEncoding(); }); for (const LocAndDiag &D : DeclDiags) { diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 467c06582299f..b0e37251a3058 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -2305,7 +2305,7 @@ void Sema::ActOnPopScope(SourceLocation Loc, Scope *S) { if (const auto *RD = dyn_cast<RecordDecl>(D)) DiagnoseUnusedNestedTypedefs(RD, addDiag); if (VarDecl *VD = dyn_cast<VarDecl>(D)) { - // wait until end of TU to diagnose static globals + // Wait until end of TU to diagnose static globals. if (!VD->isStaticFileVar()) { DiagnoseUnusedButSetDecl(VD, addDiag); RefsMinusAssignments.erase(VD); diff --git a/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c b/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c index e4c316b37d15f..f26c90733a2bd 100644 --- a/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c +++ b/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c @@ -1,3 +1,3 @@ // expected-no-diagnostics -// test that header-defined static globals don't warn +// Test that header-defined static globals don't warn. #include "warn-unused-but-set-static-global-header.h" diff --git a/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header.h b/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header.h index a06e9e66a34f4..40637d644f717 100644 --- a/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header.h +++ b/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header.h @@ -1,3 +1,3 @@ -// header file for testing that header-defined static globals don't warn +// Header file for testing that header-defined static globals don't warn. static int header_set_unused = 0; static void header_init() { header_set_unused = 1; } diff --git a/clang/test/Sema/warn-unused-but-set-static-global.c b/clang/test/Sema/warn-unused-but-set-static-global.c index bf099d3c5759c..12679a4de8a34 100644 --- a/clang/test/Sema/warn-unused-but-set-static-global.c +++ b/clang/test/Sema/warn-unused-but-set-static-global.c @@ -10,8 +10,8 @@ static int set_unused; // expected-warning {{variable 'set_unused' set but not u static int set_and_used; static int only_used; static int addr_taken; -extern int external_var; // no warning (external linkage) -extern int global_var; // no warning (not static) +extern int external_var; // No warning (external linkage). +extern int global_var; // No warning (not static). void f1() { set_unused = 1; @@ -30,7 +30,7 @@ void f1() { global_var = 4; } -// test across multiple functions +// Test across multiple functions. static int set_used1; static int set_used2; @@ -58,7 +58,7 @@ void f4() { vol_set = 1; } -// read and use +// Read and use static int compound; // expected-warning{{variable 'compound' set but not used}} static volatile int vol_compound; static int unary; // expected-warning{{variable 'unary' set but not used}} @@ -81,7 +81,7 @@ void f6() { t = s_used; } -// multiple assignments +// Multiple assignments static int multi; // expected-warning{{variable 'multi' set but not used}} void f7() { multi = 1; @@ -89,7 +89,7 @@ void f7() { multi = 3; } -// unused pointers +// Unused pointers static int *unused_ptr; // expected-warning{{variable 'unused_ptr' set but not used}} static char *str_ptr; // expected-warning{{variable 'str_ptr' set but not used}} void f8() { @@ -97,7 +97,7 @@ void f8() { str_ptr = "hello"; } -// used pointers +// Used pointers void a(void *); static int *used_ptr; static int *param_ptr; @@ -113,7 +113,7 @@ void f9() { if (null_check_ptr == NULL) {} } -// function pointers +// Function pointers static void (*sandboxing_callback)(); void SetSandboxingCallback(void (*f)()) { sandboxing_callback = f; diff --git a/clang/test/Sema/warn-unused-but-set-static-global.cpp b/clang/test/Sema/warn-unused-but-set-static-global.cpp index ead7599d6cc78..66d20bc242ca0 100644 --- a/clang/test/Sema/warn-unused-but-set-static-global.cpp +++ b/clang/test/Sema/warn-unused-but-set-static-global.cpp @@ -11,7 +11,7 @@ namespace test { (void)x; } - // function pointer in namespace + // Function pointer in namespace. static void (*sandboxing_callback)(); void SetSandboxingCallback(void (*f)()) { sandboxing_callback = f; >From 9484cc6882f3e7a96290d22bdc92f503399afb9c Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Tue, 3 Feb 2026 02:10:11 +0100 Subject: [PATCH 06/20] wait after iterating to erase decls --- clang/lib/Sema/Sema.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index 3fff5526158d0..580560afb296c 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -1648,15 +1648,19 @@ void Sema::ActOnEndOfTranslationUnit() { // For -Wunused-but-set-variable we only care about variables that were // referenced by the TU end. + SmallVector<const VarDecl *, 16> DeclsToErase; for (const auto &Ref : RefsMinusAssignments) { const VarDecl *VD = Ref.first; // Only diagnose static file vars defined in the main file to match // -Wunused-variable behavior and avoid false positives from header vars. if (VD->isStaticFileVar() && SourceMgr.isInMainFile(VD->getLocation())) { DiagnoseUnusedButSetDecl(VD, addDiag); - RefsMinusAssignments.erase(VD); + DeclsToErase.push_back(VD); } } + for (const VarDecl *VD : DeclsToErase) { + RefsMinusAssignments.erase(VD); + } llvm::sort(DeclDiags, [](const LocAndDiag &LHS, const LocAndDiag &RHS) -> bool { >From feeb8317c0b0afee53bd9083af3bb72983d92c13 Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Tue, 3 Feb 2026 22:12:18 +0100 Subject: [PATCH 07/20] add test for static thread_local --- .../test/Sema/warn-unused-but-set-static-global.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/clang/test/Sema/warn-unused-but-set-static-global.cpp b/clang/test/Sema/warn-unused-but-set-static-global.cpp index 66d20bc242ca0..a76b37f63eae2 100644 --- a/clang/test/Sema/warn-unused-but-set-static-global.cpp +++ b/clang/test/Sema/warn-unused-but-set-static-global.cpp @@ -1,5 +1,18 @@ // RUN: %clang_cc1 -fsyntax-only -Wunused-but-set-variable -verify -std=c++11 %s +static thread_local int tl_set_unused; // expected-warning {{variable 'tl_set_unused' set but not used}} +static thread_local int tl_set_and_used; +thread_local int tl_no_static_set_unused; + +void f0() { + tl_set_unused = 1; + tl_set_and_used = 2; + int x = tl_set_and_used; + (void)x; + + tl_no_static_set_unused = 3; +} + namespace test { static int set_unused; // expected-warning {{variable 'set_unused' set but not used}} static int set_and_used; >From 9d888ae9f31ab7251f9d3543a4a0bb3dfc2a97d8 Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Mon, 16 Feb 2026 20:57:12 +0100 Subject: [PATCH 08/20] revert function ptr exclusion and test --- clang/lib/Sema/SemaExpr.cpp | 8 ++- .../Sema/warn-unused-but-set-static-global.c | 18 ++++-- .../warn-unused-but-set-static-global.cpp | 58 +++++++++++++++++-- 3 files changed, 71 insertions(+), 13 deletions(-) diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 6013a0cad06e5..513768f44b842 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -20477,9 +20477,11 @@ static void DoMarkVarDeclReferenced( bool UsableInConstantExpr = Var->mightBeUsableInConstantExpressions(SemaRef.Context); - bool ShouldTrackForUnusedButSet = Var->isStaticFileVar() && - !Var->isStaticDataMember() && - !Var->getType()->isFunctionPointerType(); + // We skip static data members because they have external linkage. + // TODO: static data members in anonymous namespaces have internal linkage and + // should be diagnosed. + bool ShouldTrackForUnusedButSet = + Var->isStaticFileVar() && !Var->isStaticDataMember(); if ((Var->isLocalVarDeclOrParm() || ShouldTrackForUnusedButSet) && !Var->hasExternalStorage()) { RefsMinusAssignments.insert({Var, 0}).first->getSecond()++; diff --git a/clang/test/Sema/warn-unused-but-set-static-global.c b/clang/test/Sema/warn-unused-but-set-static-global.c index 12679a4de8a34..0a16486c45225 100644 --- a/clang/test/Sema/warn-unused-but-set-static-global.c +++ b/clang/test/Sema/warn-unused-but-set-static-global.c @@ -113,8 +113,18 @@ void f9() { if (null_check_ptr == NULL) {} } -// Function pointers -static void (*sandboxing_callback)(); -void SetSandboxingCallback(void (*f)()) { - sandboxing_callback = f; +// Function pointers (unused) +static void (*unused_func_ptr)(); // expected-warning {{variable 'unused_func_ptr' set but not used}} +void SetUnusedCallback(void (*f)()) { + unused_func_ptr = f; +} + +// Function pointers (used) +static void (*used_func_ptr)(); +void SetUsedCallback(void (*f)()) { + used_func_ptr = f; +} +void CallUsedCallback() { + if (used_func_ptr) + used_func_ptr(); } diff --git a/clang/test/Sema/warn-unused-but-set-static-global.cpp b/clang/test/Sema/warn-unused-but-set-static-global.cpp index a76b37f63eae2..2ec870777e91e 100644 --- a/clang/test/Sema/warn-unused-but-set-static-global.cpp +++ b/clang/test/Sema/warn-unused-but-set-static-global.cpp @@ -23,12 +23,6 @@ namespace test { int x = set_and_used; (void)x; } - - // Function pointer in namespace. - static void (*sandboxing_callback)(); - void SetSandboxingCallback(void (*f)()) { - sandboxing_callback = f; - } } namespace outer { @@ -39,3 +33,55 @@ void f2() { } } } + +// Anonymous namespace +namespace { + static int anon_ns_static_unused; // expected-warning {{variable 'anon_ns_static_unused' set but not used}} + + // Should not warn on static data members in current implementation. + class AnonClass { + public: + static int unused_member; + }; + + int AnonClass::unused_member = 0; + + void f3() { + anon_ns_static_unused = 1; + AnonClass::unused_member = 2; + } +} + +// Function pointers at file scope (unused) +static void (*unused_func_ptr)(); // expected-warning {{variable 'unused_func_ptr' set but not used}} +void SetUnusedCallback(void (*f)()) { + unused_func_ptr = f; +} + +// Function pointers at file scope (used) +static void (*used_func_ptr)(); +void SetUsedCallback(void (*f)()) { + used_func_ptr = f; +} +void CallUsedCallback() { + if (used_func_ptr) + used_func_ptr(); +} + +// Static data members (have external linkage so should not warn). +class MyClass { +public: + static int unused_static_member; + static int used_static_member; +}; + +int MyClass::unused_static_member = 0; +int MyClass::used_static_member = 0; + +void f4() { + MyClass::unused_static_member = 10; + + MyClass::used_static_member = 20; + int x = MyClass::used_static_member; + (void)x; +} >From 6307c8a7d563a59f7189a26f3cc0a7bdd5b6fb1c Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Wed, 18 Feb 2026 21:43:12 +0100 Subject: [PATCH 09/20] add release note --- clang/docs/ReleaseNotes.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 6f89cc9a30603..4c5a904715712 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -215,6 +215,9 @@ Attribute Changes in Clang Improvements to Clang's diagnostics ----------------------------------- +- ``-Wunused-but-set-variable`` now diagnoses file-scope variables with + internal linkage (``static`` storage class) that are assigned but never used. + - Added ``-Wlifetime-safety`` to enable lifetime safety analysis, a CFG-based intra-procedural analysis that detects use-after-free and related temporal safety bugs. See the >From d6306ef8ccc73c23e750d2ad8ef825e19568d06e Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Thu, 19 Feb 2026 18:55:33 +0100 Subject: [PATCH 10/20] style changes, refactor, update rel notes --- clang/docs/ReleaseNotes.rst | 2 +- clang/lib/Sema/Sema.cpp | 75 +++++++++++++++++++------------------ 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 4c5a904715712..9fd1d0ad0a1c6 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -216,7 +216,7 @@ Attribute Changes in Clang Improvements to Clang's diagnostics ----------------------------------- - ``-Wunused-but-set-variable`` now diagnoses file-scope variables with - internal linkage (``static`` storage class) that are assigned but never used. + internal linkage (``static`` storage class) that are assigned but never used. (#GH148361) - Added ``-Wlifetime-safety`` to enable lifetime safety analysis, a CFG-based intra-procedural analysis that detects use-after-free and related diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index 580560afb296c..3c6f54eccce1c 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -1604,6 +1604,44 @@ void Sema::ActOnEndOfTranslationUnit() { emitAndClearUnusedLocalTypedefWarnings(); } + if (!Diags.isIgnored(diag::warn_unused_but_set_variable, SourceLocation())) { + // Diagnose unused-but-set static globals in a deterministic order. + // Not tracking shadowing info for static globals; there's nothing to shadow. + struct LocAndDiag { + SourceLocation Loc; + PartialDiagnostic PD; + }; + SmallVector<LocAndDiag, 16> DeclDiags; + auto addDiag = [&DeclDiags](SourceLocation Loc, PartialDiagnostic PD) { + DeclDiags.push_back(LocAndDiag{Loc, std::move(PD)}); + }; + + // For -Wunused-but-set-variable we only care about variables that were + // referenced by the TU end. + SmallVector<const VarDecl *, 16> DeclsToErase; + for (const auto &Ref : RefsMinusAssignments) { + const VarDecl *VD = Ref.first; + // Only diagnose static file vars defined in the main file to match + // -Wunused-variable behavior and avoid false positives from header vars. + if (VD->isStaticFileVar() && SourceMgr.isInMainFile(VD->getLocation())) { + DiagnoseUnusedButSetDecl(VD, addDiag); + DeclsToErase.push_back(VD); + } + } + for (const VarDecl *VD : DeclsToErase) { + RefsMinusAssignments.erase(VD); + } + + llvm::sort(DeclDiags, + [](const LocAndDiag &LHS, const LocAndDiag &RHS) -> bool { + // Sorting purely for determinism; matches behavior in + // SemaDecl.cpp. + return LHS.Loc.getRawEncoding() < RHS.Loc.getRawEncoding(); + }); + for (const LocAndDiag &D : DeclDiags) + Diag(D.Loc, D.PD); + } + if (!Diags.isIgnored(diag::warn_unused_private_field, SourceLocation())) { // FIXME: Load additional unused private field candidates from the external // source. @@ -1635,43 +1673,6 @@ void Sema::ActOnEndOfTranslationUnit() { if (Context.hasAnyFunctionEffects()) performFunctionEffectAnalysis(Context.getTranslationUnitDecl()); - // Diagnose unused-but-set static globals in a deterministic order. - // Not tracking shadowing info for static globals; there's nothing to shadow. - struct LocAndDiag { - SourceLocation Loc; - PartialDiagnostic PD; - }; - SmallVector<LocAndDiag, 16> DeclDiags; - auto addDiag = [&DeclDiags](SourceLocation Loc, PartialDiagnostic PD) { - DeclDiags.push_back(LocAndDiag{Loc, std::move(PD)}); - }; - - // For -Wunused-but-set-variable we only care about variables that were - // referenced by the TU end. - SmallVector<const VarDecl *, 16> DeclsToErase; - for (const auto &Ref : RefsMinusAssignments) { - const VarDecl *VD = Ref.first; - // Only diagnose static file vars defined in the main file to match - // -Wunused-variable behavior and avoid false positives from header vars. - if (VD->isStaticFileVar() && SourceMgr.isInMainFile(VD->getLocation())) { - DiagnoseUnusedButSetDecl(VD, addDiag); - DeclsToErase.push_back(VD); - } - } - for (const VarDecl *VD : DeclsToErase) { - RefsMinusAssignments.erase(VD); - } - - llvm::sort(DeclDiags, - [](const LocAndDiag &LHS, const LocAndDiag &RHS) -> bool { - // Sorting purely for determinism; matches behavior in - // SemaDecl.cpp. - return LHS.Loc.getRawEncoding() < RHS.Loc.getRawEncoding(); - }); - for (const LocAndDiag &D : DeclDiags) { - Diag(D.Loc, D.PD); - } - // Check we've noticed that we're no longer parsing the initializer for every // variable. If we miss cases, then at best we have a performance issue and // at worst a rejects-valid bug. >From 1916e833e77ee6fdf1e4ec75017020f8d3347afd Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Thu, 19 Feb 2026 18:59:14 +0100 Subject: [PATCH 11/20] remove redundant erase --- clang/lib/Sema/Sema.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index 3c6f54eccce1c..8fee6f3b8e641 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -1618,18 +1618,12 @@ void Sema::ActOnEndOfTranslationUnit() { // For -Wunused-but-set-variable we only care about variables that were // referenced by the TU end. - SmallVector<const VarDecl *, 16> DeclsToErase; for (const auto &Ref : RefsMinusAssignments) { const VarDecl *VD = Ref.first; // Only diagnose static file vars defined in the main file to match // -Wunused-variable behavior and avoid false positives from header vars. - if (VD->isStaticFileVar() && SourceMgr.isInMainFile(VD->getLocation())) { + if (VD->isStaticFileVar() && SourceMgr.isInMainFile(VD->getLocation())) DiagnoseUnusedButSetDecl(VD, addDiag); - DeclsToErase.push_back(VD); - } - } - for (const VarDecl *VD : DeclsToErase) { - RefsMinusAssignments.erase(VD); } llvm::sort(DeclDiags, >From 433fd8ee8afc1fc1648cde8140e5f46a6095036a Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Thu, 19 Feb 2026 19:45:01 +0100 Subject: [PATCH 12/20] properly organize tests and improve comment --- clang/lib/Sema/Sema.cpp | 2 +- .../Inputs/warn-unused-but-set-static-global-header-test.c | 3 --- clang/test/Sema/warn-unused-but-set-static-global.c | 6 ++++-- 3 files changed, 5 insertions(+), 6 deletions(-) delete mode 100644 clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index 8fee6f3b8e641..a4775cf916a8c 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -1629,7 +1629,7 @@ void Sema::ActOnEndOfTranslationUnit() { llvm::sort(DeclDiags, [](const LocAndDiag &LHS, const LocAndDiag &RHS) -> bool { // Sorting purely for determinism; matches behavior in - // SemaDecl.cpp. + // Sema::ActOnPopScope. return LHS.Loc.getRawEncoding() < RHS.Loc.getRawEncoding(); }); for (const LocAndDiag &D : DeclDiags) diff --git a/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c b/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c deleted file mode 100644 index f26c90733a2bd..0000000000000 --- a/clang/test/Sema/Inputs/warn-unused-but-set-static-global-header-test.c +++ /dev/null @@ -1,3 +0,0 @@ -// expected-no-diagnostics -// Test that header-defined static globals don't warn. -#include "warn-unused-but-set-static-global-header.h" diff --git a/clang/test/Sema/warn-unused-but-set-static-global.c b/clang/test/Sema/warn-unused-but-set-static-global.c index 0a16486c45225..f37d389b8208d 100644 --- a/clang/test/Sema/warn-unused-but-set-static-global.c +++ b/clang/test/Sema/warn-unused-but-set-static-global.c @@ -1,5 +1,7 @@ -// RUN: %clang_cc1 -fsyntax-only -Wunused-but-set-variable -verify %s -// RUN: %clang_cc1 -fsyntax-only -Wunused-but-set-variable -I %S -verify %S/Inputs/warn-unused-but-set-static-global-header-test.c +// RUN: %clang_cc1 -fsyntax-only -Wunused-but-set-variable -I %S/Inputs -verify %s + +// Test that header-defined static globals don't warn. +#include "warn-unused-but-set-static-global-header.h" #define NULL (void*)0 >From f6c649f698b72cd86550382d99f9d248fc076069 Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Thu, 19 Feb 2026 20:30:31 +0100 Subject: [PATCH 13/20] diagnose all vars in anon namespaces --- clang/include/clang/AST/Decl.h | 5 +++++ clang/lib/Sema/Sema.cpp | 8 +++++--- clang/lib/Sema/SemaDecl.cpp | 4 ++-- clang/lib/Sema/SemaExpr.cpp | 4 +--- clang/test/Sema/warn-unused-but-set-static-global.cpp | 8 +++++--- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index 47db1ff8d5817..72732c1d7a0fb 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -1217,6 +1217,11 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> { return isFileVarDecl() && getStorageClass() == SC_Static; } + /// Returns true if this is a file-scope variable with internal linkage. + bool hasInternalLinkageFileVar() const { + return isFileVarDecl() && !isExternallyVisible() && !isStaticDataMember(); + } + /// Returns true if a variable has extern or __private_extern__ /// storage. bool hasExternalStorage() const { diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index a4775cf916a8c..1ee74ceb11cbb 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -1620,9 +1620,11 @@ void Sema::ActOnEndOfTranslationUnit() { // referenced by the TU end. for (const auto &Ref : RefsMinusAssignments) { const VarDecl *VD = Ref.first; - // Only diagnose static file vars defined in the main file to match - // -Wunused-variable behavior and avoid false positives from header vars. - if (VD->isStaticFileVar() && SourceMgr.isInMainFile(VD->getLocation())) + // Only diagnose internal linkage file vars defined in the main file to + // match -Wunused-variable behavior and avoid false positives from + // headers. + if (VD->hasInternalLinkageFileVar() && + SourceMgr.isInMainFile(VD->getLocation())) DiagnoseUnusedButSetDecl(VD, addDiag); } diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index b0e37251a3058..1bc495693ee52 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -2305,8 +2305,8 @@ void Sema::ActOnPopScope(SourceLocation Loc, Scope *S) { if (const auto *RD = dyn_cast<RecordDecl>(D)) DiagnoseUnusedNestedTypedefs(RD, addDiag); if (VarDecl *VD = dyn_cast<VarDecl>(D)) { - // Wait until end of TU to diagnose static globals. - if (!VD->isStaticFileVar()) { + // Wait until end of TU to diagnose internal linkage file vars. + if (!VD->hasInternalLinkageFileVar()) { DiagnoseUnusedButSetDecl(VD, addDiag); RefsMinusAssignments.erase(VD); } diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 513768f44b842..a762360e042b1 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -20480,9 +20480,7 @@ static void DoMarkVarDeclReferenced( // We skip static data members because they have external linkage. // TODO: static data members in anonymous namespaces have internal linkage and // should be diagnosed. - bool ShouldTrackForUnusedButSet = - Var->isStaticFileVar() && !Var->isStaticDataMember(); - if ((Var->isLocalVarDeclOrParm() || ShouldTrackForUnusedButSet) && + if ((Var->isLocalVarDeclOrParm() || Var->hasInternalLinkageFileVar()) && !Var->hasExternalStorage()) { RefsMinusAssignments.insert({Var, 0}).first->getSecond()++; } diff --git a/clang/test/Sema/warn-unused-but-set-static-global.cpp b/clang/test/Sema/warn-unused-but-set-static-global.cpp index 2ec870777e91e..149e1b628e853 100644 --- a/clang/test/Sema/warn-unused-but-set-static-global.cpp +++ b/clang/test/Sema/warn-unused-but-set-static-global.cpp @@ -34,8 +34,9 @@ void f2() { } } -// Anonymous namespace +// Anonymous namespace (all vars have internal linkage) namespace { + int anon_ns_unused; // expected-warning {{variable 'anon_ns_unused' set but not used}} static int anon_ns_static_unused; // expected-warning {{variable 'anon_ns_static_unused' set but not used}} // Should not warn on static data members in current implementation. @@ -47,8 +48,9 @@ namespace { int AnonClass::unused_member = 0; void f3() { - anon_ns_static_unused = 1; - AnonClass::unused_member = 2; + anon_ns_unused = 1; + anon_ns_static_unused = 2; + AnonClass::unused_member = 3; } } >From db27f0cc43a85f01b163bbddbaf73fa605647e8e Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Thu, 19 Feb 2026 20:47:19 +0100 Subject: [PATCH 14/20] add tests for unused attrs --- clang/test/Sema/warn-unused-but-set-static-global.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/clang/test/Sema/warn-unused-but-set-static-global.cpp b/clang/test/Sema/warn-unused-but-set-static-global.cpp index 149e1b628e853..d9dc5878363ff 100644 --- a/clang/test/Sema/warn-unused-but-set-static-global.cpp +++ b/clang/test/Sema/warn-unused-but-set-static-global.cpp @@ -1,9 +1,13 @@ -// RUN: %clang_cc1 -fsyntax-only -Wunused-but-set-variable -verify -std=c++11 %s +// RUN: %clang_cc1 -fsyntax-only -Wunused-but-set-variable -verify -std=c++17 %s static thread_local int tl_set_unused; // expected-warning {{variable 'tl_set_unused' set but not used}} static thread_local int tl_set_and_used; thread_local int tl_no_static_set_unused; +// Warning should respect attributes. +[[maybe_unused]] static int with_maybe_unused; +__attribute__((unused)) static int with_unused_attr; + void f0() { tl_set_unused = 1; tl_set_and_used = 2; @@ -11,6 +15,9 @@ void f0() { (void)x; tl_no_static_set_unused = 3; + + with_maybe_unused = 4; + with_unused_attr = 5; } namespace test { >From 5ef62be2fd2852e9aa2fc1ce39a26f8e4f9fdbe1 Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Mon, 23 Feb 2026 17:05:06 +0100 Subject: [PATCH 15/20] format, refactor, and organize tests --- clang/include/clang/AST/Decl.h | 9 +-- clang/include/clang/Sema/Sema.h | 4 ++ clang/lib/Sema/Sema.cpp | 13 ++-- clang/lib/Sema/SemaDecl.cpp | 16 +++-- clang/lib/Sema/SemaExpr.cpp | 4 +- .../warn-unused-but-set-static-global.cpp | 63 +++++++++++++++---- 6 files changed, 70 insertions(+), 39 deletions(-) diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index 72732c1d7a0fb..61c2d08b98988 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -1212,14 +1212,9 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> { && !isFileVarDecl(); } - /// Returns true if a variable is a static file-scope variable. - bool isStaticFileVar() const { - return isFileVarDecl() && getStorageClass() == SC_Static; - } - /// Returns true if this is a file-scope variable with internal linkage. - bool hasInternalLinkageFileVar() const { - return isFileVarDecl() && !isExternallyVisible() && !isStaticDataMember(); + bool isInternalLinkageFileVar() const { + return isFileVarDecl() && !isExternallyVisible(); } /// Returns true if a variable has extern or __private_extern__ diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 832e46286194a..ec9e2489e107f 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -4618,6 +4618,10 @@ class Sema final : public SemaBase { bool ShouldWarnIfUnusedFileScopedDecl(const DeclaratorDecl *D) const; + /// Determines whether the given source location is in the main file + /// and we're in a context where we should warn about unused entities. + bool isMainFileLoc(SourceLocation Loc) const; + /// If it's a file scoped decl that must warn if not used, keep track /// of it. void MarkUnusedFileScopedDecl(const DeclaratorDecl *D); diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index 1ee74ceb11cbb..481858493e5a2 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -1623,17 +1623,16 @@ void Sema::ActOnEndOfTranslationUnit() { // Only diagnose internal linkage file vars defined in the main file to // match -Wunused-variable behavior and avoid false positives from // headers. - if (VD->hasInternalLinkageFileVar() && - SourceMgr.isInMainFile(VD->getLocation())) + if (VD->isInternalLinkageFileVar() && isMainFileLoc(VD->getLocation())) DiagnoseUnusedButSetDecl(VD, addDiag); } llvm::sort(DeclDiags, - [](const LocAndDiag &LHS, const LocAndDiag &RHS) -> bool { - // Sorting purely for determinism; matches behavior in - // Sema::ActOnPopScope. - return LHS.Loc.getRawEncoding() < RHS.Loc.getRawEncoding(); - }); + [](const LocAndDiag &LHS, const LocAndDiag &RHS) -> bool { + // Sorting purely for determinism; matches behavior in + // Sema::ActOnPopScope. + return LHS.Loc.getRawEncoding() < RHS.Loc.getRawEncoding(); + }); for (const LocAndDiag &D : DeclDiags) Diag(D.Loc, D.PD); } diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 1bc495693ee52..82b8846967457 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -1911,12 +1911,10 @@ bool Sema::mightHaveNonExternalLinkage(const DeclaratorDecl *D) { return !D->isExternallyVisible(); } -// FIXME: This needs to be refactored; some other isInMainFile users want -// these semantics. -static bool isMainFileLoc(const Sema &S, SourceLocation Loc) { - if (S.TUKind != TU_Complete || S.getLangOpts().IsHeaderFile) +bool Sema::isMainFileLoc(SourceLocation Loc) const { + if (TUKind != TU_Complete || getLangOpts().IsHeaderFile) return false; - return S.SourceMgr.isInMainFile(Loc); + return SourceMgr.isInMainFile(Loc); } bool Sema::ShouldWarnIfUnusedFileScopedDecl(const DeclaratorDecl *D) const { @@ -1945,7 +1943,7 @@ bool Sema::ShouldWarnIfUnusedFileScopedDecl(const DeclaratorDecl *D) const { return false; } else { // 'static inline' functions are defined in headers; don't warn. - if (FD->isInlined() && !isMainFileLoc(*this, FD->getLocation())) + if (FD->isInlined() && !isMainFileLoc(FD->getLocation())) return false; } @@ -1956,7 +1954,7 @@ bool Sema::ShouldWarnIfUnusedFileScopedDecl(const DeclaratorDecl *D) const { // Constants and utility variables are defined in headers with internal // linkage; don't warn. (Unlike functions, there isn't a convenient marker // like "inline".) - if (!isMainFileLoc(*this, VD->getLocation())) + if (!isMainFileLoc(VD->getLocation())) return false; if (Context.DeclMustBeEmitted(VD)) @@ -1970,7 +1968,7 @@ bool Sema::ShouldWarnIfUnusedFileScopedDecl(const DeclaratorDecl *D) const { VD->getMemberSpecializationInfo() && !VD->isOutOfLine()) return false; - if (VD->isInline() && !isMainFileLoc(*this, VD->getLocation())) + if (VD->isInline() && !isMainFileLoc(VD->getLocation())) return false; } else { return false; @@ -2306,7 +2304,7 @@ void Sema::ActOnPopScope(SourceLocation Loc, Scope *S) { DiagnoseUnusedNestedTypedefs(RD, addDiag); if (VarDecl *VD = dyn_cast<VarDecl>(D)) { // Wait until end of TU to diagnose internal linkage file vars. - if (!VD->hasInternalLinkageFileVar()) { + if (!VD->isInternalLinkageFileVar()) { DiagnoseUnusedButSetDecl(VD, addDiag); RefsMinusAssignments.erase(VD); } diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index a762360e042b1..9c969d9d62c87 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -20478,9 +20478,7 @@ static void DoMarkVarDeclReferenced( Var->mightBeUsableInConstantExpressions(SemaRef.Context); // We skip static data members because they have external linkage. - // TODO: static data members in anonymous namespaces have internal linkage and - // should be diagnosed. - if ((Var->isLocalVarDeclOrParm() || Var->hasInternalLinkageFileVar()) && + if ((Var->isLocalVarDeclOrParm() || Var->isInternalLinkageFileVar()) && !Var->hasExternalStorage()) { RefsMinusAssignments.insert({Var, 0}).first->getSecond()++; } diff --git a/clang/test/Sema/warn-unused-but-set-static-global.cpp b/clang/test/Sema/warn-unused-but-set-static-global.cpp index d9dc5878363ff..8dd471329b978 100644 --- a/clang/test/Sema/warn-unused-but-set-static-global.cpp +++ b/clang/test/Sema/warn-unused-but-set-static-global.cpp @@ -20,6 +20,7 @@ void f0() { with_unused_attr = 5; } +// Named namespace. namespace test { static int set_unused; // expected-warning {{variable 'set_unused' set but not used}} static int set_and_used; @@ -32,6 +33,7 @@ namespace test { } } +// Nested named namespace. namespace outer { namespace inner { static int nested_unused; // expected-warning {{variable 'nested_unused' set but not used}} @@ -41,33 +43,23 @@ void f2() { } } -// Anonymous namespace (all vars have internal linkage) +// Anonymous namespace (all vars have internal linkage). namespace { int anon_ns_unused; // expected-warning {{variable 'anon_ns_unused' set but not used}} static int anon_ns_static_unused; // expected-warning {{variable 'anon_ns_static_unused' set but not used}} - // Should not warn on static data members in current implementation. - class AnonClass { - public: - static int unused_member; - }; - - int AnonClass::unused_member = 0; - void f3() { anon_ns_unused = 1; anon_ns_static_unused = 2; - AnonClass::unused_member = 3; } } -// Function pointers at file scope (unused) +// Function pointers at file scope. static void (*unused_func_ptr)(); // expected-warning {{variable 'unused_func_ptr' set but not used}} void SetUnusedCallback(void (*f)()) { unused_func_ptr = f; } -// Function pointers at file scope (used) static void (*used_func_ptr)(); void SetUsedCallback(void (*f)()) { used_func_ptr = f; @@ -77,7 +69,7 @@ void CallUsedCallback() { used_func_ptr(); } -// Static data members (have external linkage so should not warn). +// Static data members (external linkage, should not warn). class MyClass { public: static int unused_static_member; @@ -94,3 +86,48 @@ void f4() { int x = MyClass::used_static_member; (void)x; } + +// Static data members in a named namespace (external linkage, should not warn). +namespace named { + struct NamedClass { + static int w; + }; + int NamedClass::w = 0; +} + +void f5() { + named::NamedClass::w = 4; +} + +// Static data members in anonymous namespace (internal linkage, should warn). +namespace { + class AnonClass { + public: + static int unused_member; + static int used_member; + }; + + int AnonClass::unused_member = 0; // expected-warning {{variable 'unused_member' set but not used}} + int AnonClass::used_member = 0; +} + +void f6() { + AnonClass::unused_member = 3; + AnonClass::used_member = 4; + int y = AnonClass::used_member; + (void)y; +} + +// Static data members in nested anonymous namespace (internal linkage, should warn). +namespace outer2 { + namespace { + struct NestedAnonClass { + static int v; + }; + int NestedAnonClass::v = 0; // expected-warning {{variable 'v' set but not used}} + } +} + +void f7() { + outer2::NestedAnonClass::v = 5; +} >From 41224c64b4d616ae79121cc4bc96e74178781f71 Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Mon, 23 Feb 2026 17:19:25 +0100 Subject: [PATCH 16/20] fix new finding in test suite --- clang/lib/Sema/Sema.cpp | 3 ++- clang/test/SemaCXX/warn-variable-not-needed.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index 481858493e5a2..38e92f4379bdf 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -1606,7 +1606,8 @@ void Sema::ActOnEndOfTranslationUnit() { if (!Diags.isIgnored(diag::warn_unused_but_set_variable, SourceLocation())) { // Diagnose unused-but-set static globals in a deterministic order. - // Not tracking shadowing info for static globals; there's nothing to shadow. + // Not tracking shadowing info for static globals; there's nothing to + // shadow. struct LocAndDiag { SourceLocation Loc; PartialDiagnostic PD; diff --git a/clang/test/SemaCXX/warn-variable-not-needed.cpp b/clang/test/SemaCXX/warn-variable-not-needed.cpp index 103be189068f8..272c8998d15c0 100644 --- a/clang/test/SemaCXX/warn-variable-not-needed.cpp +++ b/clang/test/SemaCXX/warn-variable-not-needed.cpp @@ -18,7 +18,7 @@ namespace test2 { }; namespace { struct foo : bah { - static char bar; + static char bar; // expected-warning {{variable 'bar' set but not used}} virtual void zed(); }; void foo::zed() { >From 9b68f68fa45077898de307f3462e55ebfebd4eda Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Wed, 11 Mar 2026 19:38:41 +0100 Subject: [PATCH 17/20] cleanup and organize defs and decls --- clang/include/clang/Sema/Sema.h | 8 ++++---- clang/lib/Sema/Sema.cpp | 10 ++++++++-- clang/lib/Sema/SemaDecl.cpp | 17 +++++------------ 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index ec9e2489e107f..d092253ef5d9b 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -973,6 +973,10 @@ class Sema final : public SemaBase { /// unit because its type has no linkage and it's not extern "C". bool isExternalWithNoLinkageType(const ValueDecl *VD) const; + /// Determines whether the given source location is in the main file + /// and we're in a context where we should warn about unused entities. + bool isMainFileLoc(SourceLocation Loc) const; + /// Obtain a sorted list of functions that are undefined but ODR-used. void getUndefinedButUsed( SmallVectorImpl<std::pair<NamedDecl *, SourceLocation>> &Undefined); @@ -4618,10 +4622,6 @@ class Sema final : public SemaBase { bool ShouldWarnIfUnusedFileScopedDecl(const DeclaratorDecl *D) const; - /// Determines whether the given source location is in the main file - /// and we're in a context where we should warn about unused entities. - bool isMainFileLoc(SourceLocation Loc) const; - /// If it's a file scoped decl that must warn if not used, keep track /// of it. void MarkUnusedFileScopedDecl(const DeclaratorDecl *D); diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index 38e92f4379bdf..bb357ffd86be1 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -956,6 +956,12 @@ bool Sema::isExternalWithNoLinkageType(const ValueDecl *VD) const { !isFunctionOrVarDeclExternC(VD); } +bool Sema::isMainFileLoc(SourceLocation Loc) const { + if (TUKind != TU_Complete || getLangOpts().IsHeaderFile) + return false; + return SourceMgr.isInMainFile(Loc); +} + /// Obtains a sorted list of functions and variables that are undefined but /// ODR-used. void Sema::getUndefinedButUsed( @@ -1631,8 +1637,8 @@ void Sema::ActOnEndOfTranslationUnit() { llvm::sort(DeclDiags, [](const LocAndDiag &LHS, const LocAndDiag &RHS) -> bool { // Sorting purely for determinism; matches behavior in - // Sema::ActOnPopScope. - return LHS.Loc.getRawEncoding() < RHS.Loc.getRawEncoding(); + // Sema::ActOnPopScope (operator< compares raw encoding). + return LHS.Loc < RHS.Loc; }); for (const LocAndDiag &D : DeclDiags) Diag(D.Loc, D.PD); diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 82b8846967457..55c97a4d129e7 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -1911,12 +1911,6 @@ bool Sema::mightHaveNonExternalLinkage(const DeclaratorDecl *D) { return !D->isExternallyVisible(); } -bool Sema::isMainFileLoc(SourceLocation Loc) const { - if (TUKind != TU_Complete || getLangOpts().IsHeaderFile) - return false; - return SourceMgr.isInMainFile(Loc); -} - bool Sema::ShouldWarnIfUnusedFileScopedDecl(const DeclaratorDecl *D) const { assert(D); @@ -2302,12 +2296,11 @@ void Sema::ActOnPopScope(SourceLocation Loc, Scope *S) { DiagnoseUnusedDecl(D, addDiag); if (const auto *RD = dyn_cast<RecordDecl>(D)) DiagnoseUnusedNestedTypedefs(RD, addDiag); - if (VarDecl *VD = dyn_cast<VarDecl>(D)) { - // Wait until end of TU to diagnose internal linkage file vars. - if (!VD->isInternalLinkageFileVar()) { - DiagnoseUnusedButSetDecl(VD, addDiag); - RefsMinusAssignments.erase(VD); - } + // Wait until end of TU to diagnose internal linkage file vars. + if (auto *VD = dyn_cast<VarDecl>(D); + VD && !VD->isInternalLinkageFileVar()) { + DiagnoseUnusedButSetDecl(VD, addDiag); + RefsMinusAssignments.erase(VD); } } >From a3bbfdc1260e0025f5f69141bdc3792554299bb9 Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Mon, 16 Mar 2026 22:03:39 +0100 Subject: [PATCH 18/20] Fix OpenMP CI failures caused by stale cache entries in linkage info --- clang/include/clang/AST/Decl.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index 61c2d08b98988..0977ca965547e 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -1214,7 +1214,15 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> { /// Returns true if this is a file-scope variable with internal linkage. bool isInternalLinkageFileVar() const { - return isFileVarDecl() && !isExternallyVisible(); + // Calling isExternallyVisible() can trigger linkage computation/caching, + // which may produce stale results when a decl's DeclContext changes after + // creation (e.g., OpenMP declare mapper variables), so here we determine + // it syntactically instead. + if (!isFileVarDecl()) + return false; + if (getStorageClass() == SC_Static) + return true; + return isInAnonymousNamespace(); } /// Returns true if a variable has extern or __private_extern__ >From 2bc71922e91227b3f51c646c042dd7394a5b0d63 Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Tue, 17 Mar 2026 20:25:15 +0100 Subject: [PATCH 19/20] Fix false positives for static data members Use the canonical VarDecl as key for RefsMinusAssignments so refs are tracked as a single entry for in-class decls and out-of-class defs. --- clang/include/clang/AST/Decl.h | 4 +- clang/include/clang/Sema/Sema.h | 3 ++ clang/lib/Sema/SemaDecl.cpp | 4 +- clang/lib/Sema/SemaExpr.cpp | 13 ++++-- clang/lib/Sema/SemaExprCXX.cpp | 2 +- .../warn-unused-but-set-static-global.cpp | 40 +++++++++++++++++-- 6 files changed, 54 insertions(+), 12 deletions(-) diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index 0977ca965547e..076d9ba935583 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -1220,7 +1220,9 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> { // it syntactically instead. if (!isFileVarDecl()) return false; - if (getStorageClass() == SC_Static) + // Linkage is determined by enclosing class/namespace for static data + // members. + if (getStorageClass() == SC_Static && !isStaticDataMember()) return true; return isInAnonymousNamespace(); } diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index d092253ef5d9b..a214a7aa9147b 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -7013,6 +7013,9 @@ class Sema final : public SemaBase { /// Increment when we find a reference; decrement when we find an ignored /// assignment. Ultimately the value is 0 if every reference is an ignored /// assignment. + /// + /// Uses canonical VarDecl as key so in-class decls and out-of-class defs of + /// static data members get tracked as a single entry. llvm::DenseMap<const VarDecl *, int> RefsMinusAssignments; /// Used to control the generation of ExprWithCleanups. diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 55c97a4d129e7..b5428431f6dd2 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -2218,7 +2218,7 @@ void Sema::DiagnoseUnusedButSetDecl(const VarDecl *VD, if (VD->hasAttr<ObjCPreciseLifetimeAttr>() && Ty->isObjCObjectPointerType()) return; - auto iter = RefsMinusAssignments.find(VD); + auto iter = RefsMinusAssignments.find(VD->getCanonicalDecl()); if (iter == RefsMinusAssignments.end()) return; @@ -2300,7 +2300,7 @@ void Sema::ActOnPopScope(SourceLocation Loc, Scope *S) { if (auto *VD = dyn_cast<VarDecl>(D); VD && !VD->isInternalLinkageFileVar()) { DiagnoseUnusedButSetDecl(VD, addDiag); - RefsMinusAssignments.erase(VD); + RefsMinusAssignments.erase(VD->getCanonicalDecl()); } } diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 9c969d9d62c87..71f49af6ffdf0 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -20477,10 +20477,15 @@ static void DoMarkVarDeclReferenced( bool UsableInConstantExpr = Var->mightBeUsableInConstantExpressions(SemaRef.Context); - // We skip static data members because they have external linkage. - if ((Var->isLocalVarDeclOrParm() || Var->isInternalLinkageFileVar()) && - !Var->hasExternalStorage()) { - RefsMinusAssignments.insert({Var, 0}).first->getSecond()++; + // Only track variables with internal linkage or local scope. + // Use canonical decl so in-class declarations and out-of-class definitions + // of static data members in anonymous namespaces are tracked as a single + // entry. + const VarDecl *CanonVar = Var->getCanonicalDecl(); + if ((CanonVar->isLocalVarDeclOrParm() || + CanonVar->isInternalLinkageFileVar()) && + !CanonVar->hasExternalStorage()) { + RefsMinusAssignments.insert({CanonVar, 0}).first->getSecond()++; } // C++20 [expr.const]p12: diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 5a5bbf4d900dc..3922dcb155c2f 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -7483,7 +7483,7 @@ static void MaybeDecrementCount( if ((IsCompoundAssign || isIncrementDecrementUnaryOp) && VD->getType().isVolatileQualified()) return; - auto iter = RefsMinusAssignments.find(VD); + auto iter = RefsMinusAssignments.find(VD->getCanonicalDecl()); if (iter == RefsMinusAssignments.end()) return; iter->getSecond()--; diff --git a/clang/test/Sema/warn-unused-but-set-static-global.cpp b/clang/test/Sema/warn-unused-but-set-static-global.cpp index 8dd471329b978..1ddd7eb72ce29 100644 --- a/clang/test/Sema/warn-unused-but-set-static-global.cpp +++ b/clang/test/Sema/warn-unused-but-set-static-global.cpp @@ -103,11 +103,11 @@ void f5() { namespace { class AnonClass { public: - static int unused_member; + static int unused_member; // expected-warning {{variable 'unused_member' set but not used}} static int used_member; }; - int AnonClass::unused_member = 0; // expected-warning {{variable 'unused_member' set but not used}} + int AnonClass::unused_member = 0; int AnonClass::used_member = 0; } @@ -122,12 +122,44 @@ void f6() { namespace outer2 { namespace { struct NestedAnonClass { - static int v; + static int v; // expected-warning {{variable 'v' set but not used}} }; - int NestedAnonClass::v = 0; // expected-warning {{variable 'v' set but not used}} + int NestedAnonClass::v = 0; } } void f7() { outer2::NestedAnonClass::v = 5; } + +// Static data members set inside methods, read outside. +namespace { + struct SetInMethod { + static int x; + static int y; // expected-warning {{variable 'y' set but not used}} + void setX() { x = 1; } + void setY() { y = 1; } + }; + int SetInMethod::x; + int SetInMethod::y; +} + +void f8() { + SetInMethod s; + s.setX(); + s.setY(); + int v = SetInMethod::x; + (void)v; // only x is read +} + +// External linkage static data members set inside methods. +struct ExtSetInMethod { + static int x; + void set() { x = 1; } +}; +int ExtSetInMethod::x; + +void f9() { + ExtSetInMethod e; + e.set(); +} >From e199cde8feebd3a7be2295a6fd0f6c9788aba078 Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Tue, 17 Mar 2026 23:09:44 +0100 Subject: [PATCH 20/20] Don't warn on file-scope volatiles --- clang/lib/Sema/Sema.cpp | 2 +- clang/lib/Sema/SemaDecl.cpp | 5 +++++ clang/test/Sema/warn-unused-but-set-static-global.c | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index bb357ffd86be1..483c4c5f9cbc0 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -1637,7 +1637,7 @@ void Sema::ActOnEndOfTranslationUnit() { llvm::sort(DeclDiags, [](const LocAndDiag &LHS, const LocAndDiag &RHS) -> bool { // Sorting purely for determinism; matches behavior in - // Sema::ActOnPopScope (operator< compares raw encoding). + // Sema::ActOnPopScope. return LHS.Loc < RHS.Loc; }); for (const LocAndDiag &D : DeclDiags) diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index b5428431f6dd2..cf41eb701185a 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -2206,6 +2206,11 @@ void Sema::DiagnoseUnusedButSetDecl(const VarDecl *VD, return; } + // Don't warn on volatile file-scope variables. They are visible beyond their + // declaring function and writes to them could be observable side effects. + if (VD->getType().isVolatileQualified() && VD->isFileVarDecl()) + return; + // Don't warn about __block Objective-C pointer variables, as they might // be assigned in the block but not used elsewhere for the purpose of lifetime // extension. diff --git a/clang/test/Sema/warn-unused-but-set-static-global.c b/clang/test/Sema/warn-unused-but-set-static-global.c index f37d389b8208d..7c4d20a42b2d2 100644 --- a/clang/test/Sema/warn-unused-but-set-static-global.c +++ b/clang/test/Sema/warn-unused-but-set-static-global.c @@ -55,7 +55,7 @@ void f3() { (void)x; } -static volatile int vol_set; // expected-warning {{variable 'vol_set' set but not used}} +static volatile int vol_set; void f4() { vol_set = 1; } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
