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 <[email protected]>
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 <[email protected]>
---
 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@-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@-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;
+  }
+}

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

Reply via email to