[clang] [Clang] Add -Wtrivial-auto-var-init warning for unreachable variables (PR #178318)
Justin Stitt via cfe-commits
cfe-commits at lists.llvm.org
Fri Jan 30 14:49:35 PST 2026
https://github.com/JustinStitt updated https://github.com/llvm/llvm-project/pull/178318
>From 12fff88c959586d97f74a164c512053dfa4a775b Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Tue, 27 Jan 2026 13:59:28 -0800
Subject: [PATCH] [Clang] Add -Wtrivial-auto-var-init warning for unreachable
variables
Add an off-by-default warning -Wtrivial-auto-var-init which warns when
-ftrivial-auto-init-var cannot initialize a variable because it is in
unreachable code. This usually happens due to pre-case switch
declarations and goto-bypassed declarations.
The spelling and functionality of this warning matches GCC 12+ [1].
[1]: https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wtrivial-auto-var-init
Signed-off-by: Justin Stitt <justinstitt at google.com>
---
clang/docs/ReleaseNotes.rst | 3 +
.../clang/Analysis/Analyses/ReachableCode.h | 1 +
.../clang/Basic/DiagnosticSemaKinds.td | 4 +
clang/lib/Analysis/ReachableCode.cpp | 2 +
clang/lib/Sema/AnalysisBasedWarnings.cpp | 166 +++++++++++-------
clang/test/Sema/trivial-auto-var-init-warn.c | 35 ++++
.../SemaCXX/trivial-auto-var-init-warn.cpp | 38 ++++
7 files changed, 190 insertions(+), 59 deletions(-)
create mode 100644 clang/test/Sema/trivial-auto-var-init-warn.c
create mode 100644 clang/test/SemaCXX/trivial-auto-var-init-warn.cpp
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 489a91d439133..18e4b7e118c5a 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -187,6 +187,9 @@ Improvements to Clang's diagnostics
int* p(int *in [[clang::noescape]]) { return in; }
^~
+- Added ``-Wtrivial-auto-var-init`` to detect when ``-ftrivial-auto-var-init``
+ cannot initialize a variable because it is in unreachable code.
+
Improvements to Clang's time-trace
----------------------------------
diff --git a/clang/include/clang/Analysis/Analyses/ReachableCode.h b/clang/include/clang/Analysis/Analyses/ReachableCode.h
index f1b63f74b6c80..0772dd8c7f18a 100644
--- a/clang/include/clang/Analysis/Analyses/ReachableCode.h
+++ b/clang/include/clang/Analysis/Analyses/ReachableCode.h
@@ -51,6 +51,7 @@ class Callback {
virtual void HandleUnreachable(UnreachableKind UK, SourceLocation L,
SourceRange ConditionVal, SourceRange R1,
SourceRange R2, bool HasFallThroughAttr) = 0;
+ virtual void HandleUnreachableBlock(const CFGBlock *B) {}
};
/// ScanReachableFromBlock - Mark all blocks reachable from Start.
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index c786eb4486829..48108c7fd9ebb 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -814,6 +814,10 @@ def warn_unreachable_association : Warning<
"due to lvalue conversion of the controlling expression, association of type "
"%0 will never be selected because it is %select{of array type|qualified}1">,
InGroup<UnreachableCodeGenericAssoc>;
+def warn_trivial_auto_var_init_unreachable : Warning<
+ "variable %0 is uninitialized and cannot be initialized with "
+ "'-ftrivial-auto-var-init' because it is unreachable">,
+ InGroup<TrivialAutoVarInit>, DefaultIgnore;
/// Built-in functions.
def ext_implicit_lib_function_decl : ExtWarn<
diff --git a/clang/lib/Analysis/ReachableCode.cpp b/clang/lib/Analysis/ReachableCode.cpp
index 4a9ab5d9f0f73..391ca07253c2c 100644
--- a/clang/lib/Analysis/ReachableCode.cpp
+++ b/clang/lib/Analysis/ReachableCode.cpp
@@ -761,6 +761,8 @@ void FindUnreachableCode(AnalysisDeclContext &AC, Preprocessor &PP,
if (reachable[block->getBlockID()])
continue;
+ CB.HandleUnreachableBlock(block);
+
DeadCodeScan DS(reachable, PP, AC.getASTContext());
numReachable += DS.scanBackwards(block, CB);
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 913962dc0c3e0..e631f5930a418 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -69,77 +69,123 @@ using namespace clang;
// Unreachable code analysis.
//===----------------------------------------------------------------------===//
+static bool isTrivialInitializer(const Expr *Init) {
+ if (!Init)
+ return true;
+ const auto *CE = dyn_cast<CXXConstructExpr>(Init);
+ if (!CE)
+ return false;
+ const auto *Ctor = CE->getConstructor();
+ return Ctor && Ctor->isTrivial() && Ctor->isDefaultConstructor() &&
+ !CE->requiresZeroInitialization();
+}
+
+static bool wouldTrivialAutoVarInitApply(const LangOptions &LangOpts,
+ const VarDecl *VD) {
+ if (LangOpts.getTrivialAutoVarInit() ==
+ LangOptions::TrivialAutoVarInitKind::Uninitialized)
+ return false;
+ if (VD->isConstexpr() || VD->hasAttr<UninitializedAttr>())
+ return false;
+ if (const auto *TD = VD->getType()->getAsTagDecl())
+ if (TD->hasAttr<NoTrivialAutoVarInitAttr>())
+ return false;
+ if (const auto *FD = dyn_cast<FunctionDecl>(VD->getDeclContext()))
+ if (FD->hasAttr<NoTrivialAutoVarInitAttr>())
+ return false;
+ return true;
+}
+
namespace {
- class UnreachableCodeHandler : public reachable_code::Callback {
- Sema &S;
- SourceRange PreviousSilenceableCondVal;
-
- public:
- UnreachableCodeHandler(Sema &s) : S(s) {}
-
- void HandleUnreachable(reachable_code::UnreachableKind UK, SourceLocation L,
- SourceRange SilenceableCondVal, SourceRange R1,
- SourceRange R2, bool HasFallThroughAttr) override {
- // If the diagnosed code is `[[fallthrough]];` and
- // `-Wunreachable-code-fallthrough` is enabled, suppress `code will never
- // be executed` warning to avoid generating diagnostic twice
- if (HasFallThroughAttr &&
- !S.getDiagnostics().isIgnored(diag::warn_unreachable_fallthrough_attr,
- SourceLocation()))
- return;
+class UnreachableCodeHandler : public reachable_code::Callback {
+ Sema &S;
+ SourceRange PreviousSilenceableCondVal;
+ bool CheckTrivialAutoVarInit;
- // Avoid reporting multiple unreachable code diagnostics that are
- // triggered by the same conditional value.
- if (PreviousSilenceableCondVal.isValid() &&
- SilenceableCondVal.isValid() &&
- PreviousSilenceableCondVal == SilenceableCondVal)
- return;
- PreviousSilenceableCondVal = SilenceableCondVal;
+public:
+ UnreachableCodeHandler(Sema &S, bool CheckTrivialAutoVarInit)
+ : S(S), CheckTrivialAutoVarInit(CheckTrivialAutoVarInit) {}
+
+ void HandleUnreachable(reachable_code::UnreachableKind UK, SourceLocation L,
+ SourceRange SilenceableCondVal, SourceRange R1,
+ SourceRange R2, bool HasFallThroughAttr) override {
+ // If the diagnosed code is `[[fallthrough]];` and
+ // `-Wunreachable-code-fallthrough` is enabled, suppress `code will never
+ // be executed` warning to avoid generating diagnostic twice
+ if (HasFallThroughAttr &&
+ !S.getDiagnostics().isIgnored(diag::warn_unreachable_fallthrough_attr,
+ SourceLocation()))
+ return;
- unsigned diag = diag::warn_unreachable;
- switch (UK) {
- case reachable_code::UK_Break:
- diag = diag::warn_unreachable_break;
- break;
- case reachable_code::UK_Return:
- diag = diag::warn_unreachable_return;
- break;
- case reachable_code::UK_Loop_Increment:
- diag = diag::warn_unreachable_loop_increment;
- break;
- case reachable_code::UK_Other:
- break;
- }
+ // Avoid reporting multiple unreachable code diagnostics that are
+ // triggered by the same conditional value.
+ if (PreviousSilenceableCondVal.isValid() && SilenceableCondVal.isValid() &&
+ PreviousSilenceableCondVal == SilenceableCondVal)
+ return;
+ PreviousSilenceableCondVal = SilenceableCondVal;
- S.Diag(L, diag) << R1 << R2;
+ unsigned DiagID = diag::warn_unreachable;
+ switch (UK) {
+ case reachable_code::UK_Break:
+ DiagID = diag::warn_unreachable_break;
+ break;
+ case reachable_code::UK_Return:
+ DiagID = diag::warn_unreachable_return;
+ break;
+ case reachable_code::UK_Loop_Increment:
+ DiagID = diag::warn_unreachable_loop_increment;
+ break;
+ case reachable_code::UK_Other:
+ break;
+ }
+
+ S.Diag(L, DiagID) << R1 << R2;
- SourceLocation Open = SilenceableCondVal.getBegin();
- if (Open.isValid()) {
- SourceLocation Close = SilenceableCondVal.getEnd();
- Close = S.getLocForEndOfToken(Close);
- if (Close.isValid()) {
- S.Diag(Open, diag::note_unreachable_silence)
+ SourceLocation Open = SilenceableCondVal.getBegin();
+ if (Open.isValid()) {
+ SourceLocation Close = S.getLocForEndOfToken(SilenceableCondVal.getEnd());
+ if (Close.isValid()) {
+ S.Diag(Open, diag::note_unreachable_silence)
<< FixItHint::CreateInsertion(Open, "/* DISABLES CODE */ (")
<< FixItHint::CreateInsertion(Close, ")");
- }
}
}
- };
+ }
+
+ void HandleUnreachableBlock(const CFGBlock *B) override {
+ // We only currently use this method for -Wtrivial-auto-var-init
+ if (!CheckTrivialAutoVarInit)
+ return;
+
+ for (const CFGElement &Elem : *B) {
+ auto CS = Elem.getAs<CFGStmt>();
+ if (!CS)
+ continue;
+ const auto *DS = dyn_cast<DeclStmt>(CS->getStmt());
+ if (!DS)
+ continue;
+ for (const Decl *DI : DS->decls()) {
+ const auto *VD = dyn_cast<VarDecl>(DI);
+ if (!VD || !VD->getDeclName() || !VD->hasLocalStorage())
+ continue;
+ if (!isTrivialInitializer(VD->getInit()))
+ continue;
+ if (!wouldTrivialAutoVarInitApply(S.getLangOpts(), VD))
+ continue;
+ S.Diag(VD->getLocation(), diag::warn_trivial_auto_var_init_unreachable)
+ << VD;
+ }
+ }
+ }
+};
} // anonymous namespace
-/// CheckUnreachable - Check for unreachable code.
-static void CheckUnreachable(Sema &S, AnalysisDeclContext &AC) {
- // As a heuristic prune all diagnostics not in the main file. Currently
- // the majority of warnings in headers are false positives. These
- // are largely caused by configuration state, e.g. preprocessor
- // defined code, etc.
- //
- // Note that this is also a performance optimization. Analyzing
- // headers many times can be expensive.
+static void CheckUnreachable(Sema &S, AnalysisDeclContext &AC,
+ bool CheckTrivialAutoVarInit) {
if (!S.getSourceManager().isInMainFile(AC.getDecl()->getBeginLoc()))
return;
- UnreachableCodeHandler UC(S);
+ UnreachableCodeHandler UC(S, CheckTrivialAutoVarInit);
reachable_code::FindUnreachableCode(AC, S.getPreprocessor(), UC);
}
@@ -3156,7 +3202,9 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
}
// Warning: check for unreachable code
- if (P.enableCheckUnreachable) {
+ bool CheckTrivialAutoVarInit = !Diags.isIgnored(
+ diag::warn_trivial_auto_var_init_unreachable, D->getBeginLoc());
+ if (P.enableCheckUnreachable || CheckTrivialAutoVarInit) {
// Only check for unreachable code on non-template instantiations.
// Different template instantiations can effectively change the control-flow
// and it is very difficult to prove that a snippet of code in a template
@@ -3165,7 +3213,7 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
if (const FunctionDecl *Function = dyn_cast<FunctionDecl>(D))
isTemplateInstantiation = Function->isTemplateInstantiation();
if (!isTemplateInstantiation)
- CheckUnreachable(S, AC);
+ CheckUnreachable(S, AC, CheckTrivialAutoVarInit);
}
// Check for thread safety violations
diff --git a/clang/test/Sema/trivial-auto-var-init-warn.c b/clang/test/Sema/trivial-auto-var-init-warn.c
new file mode 100644
index 0000000000000..732fa1179f48c
--- /dev/null
+++ b/clang/test/Sema/trivial-auto-var-init-warn.c
@@ -0,0 +1,35 @@
+// RUN: %clang_cc1 -ftrivial-auto-var-init=zero -Wtrivial-auto-var-init -fsyntax-only -verify=zero %s
+// RUN: %clang_cc1 -ftrivial-auto-var-init=pattern -Wtrivial-auto-var-init -fsyntax-only -verify=pattern %s
+// RUN: %clang_cc1 -fsyntax-only -verify=noflag %s
+// RUN: %clang_cc1 -ftrivial-auto-var-init=zero -Wno-trivial-auto-var-init -fsyntax-only -verify=suppressed %s
+// RUN: %clang_cc1 -ftrivial-auto-var-init=zero -fsyntax-only -verify=default %s
+
+// noflag-no-diagnostics
+// suppressed-no-diagnostics
+// default-no-diagnostics
+
+void use(int *);
+
+void switch_precase(int c) {
+ switch (c) {
+ int x; // zero-warning{{variable 'x' is uninitialized and cannot be initialized with '-ftrivial-auto-var-init' because it is unreachable}}
+ // pattern-warning at -1{{variable 'x' is uninitialized and cannot be initialized with '-ftrivial-auto-var-init' because it is unreachable}}
+ case 0:
+ x = 1;
+ use(&x);
+ break;
+ }
+}
+
+void goto_bypass(void) {
+ goto skip;
+ int y; // zero-warning{{variable 'y' is uninitialized and cannot be initialized with '-ftrivial-auto-var-init' because it is unreachable}}
+ // pattern-warning at -1{{variable 'y' is uninitialized and cannot be initialized with '-ftrivial-auto-var-init' because it is unreachable}}
+skip:
+ use(&y);
+}
+
+void normal_var(void) {
+ int x;
+ use(&x);
+}
diff --git a/clang/test/SemaCXX/trivial-auto-var-init-warn.cpp b/clang/test/SemaCXX/trivial-auto-var-init-warn.cpp
new file mode 100644
index 0000000000000..9c21e77c901ca
--- /dev/null
+++ b/clang/test/SemaCXX/trivial-auto-var-init-warn.cpp
@@ -0,0 +1,38 @@
+// RUN: %clang_cc1 -std=c++17 -ftrivial-auto-var-init=zero -Wtrivial-auto-var-init -fsyntax-only -verify %s
+
+void use(int *);
+void use(void *);
+
+struct Trivial {
+ int a, b;
+};
+
+void uninitialized_attr(int c) {
+ switch (c) {
+ [[clang::uninitialized]] int x;
+ case 0:
+ x = 1;
+ use(&x);
+ break;
+ }
+}
+
+void struct_precase(int c) {
+ switch (c) {
+ Trivial t; // expected-warning{{variable 't' is uninitialized and cannot be initialized with '-ftrivial-auto-var-init' because it is unreachable}}
+ case 0:
+ t.a = 1;
+ use(&t);
+ break;
+ }
+}
+
+void int_precase(int c) {
+ switch (c) {
+ int x; // expected-warning{{variable 'x' is uninitialized and cannot be initialized with '-ftrivial-auto-var-init' because it is unreachable}}
+ case 0:
+ x = 1;
+ use(&x);
+ break;
+ }
+}
More information about the cfe-commits
mailing list