[clang] [Clang] Add -Wtrivial-auto-var-init warning for unreachable variables (PR #178318)

Justin Stitt via cfe-commits cfe-commits at lists.llvm.org
Thu Feb 5 16:49:40 PST 2026


================
@@ -69,66 +69,120 @@ 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;
+
+    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, diag) << R1 << R2;
+    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())
----------------
JustinStitt wrote:

I think this example can produce a nameless vardecl:

```cpp
  auto [x, y] = std::make_pair(1, 2);
```

where the AST shows us 
```cpp
      `-DecompositionDecl 0x55b7137b6118 <col:3> col:8 invalid 'auto'
        |-BindingDecl ...
        `-BindingDecl ...
```

noting that `DecompositionDecl` inherits from `VarDecl` proper. I could perceive a code path where `VD->getDeclName()` can return `''`.

To be frank, I copied the code from `Sema::DiagnoseUnusedButSetDecl` before proving to myself that it was even possible.
```c
Sema::DiagnoseUnusedButSetDecl:
  if (!VD->isReferenced() || !VD->getDeclName() || VD->hasAttr<CleanupAttr>())
    return;
```



https://github.com/llvm/llvm-project/pull/178318


More information about the cfe-commits mailing list