[clang-tools-extra] [clang-tidy] Add `readability-redundant-zero-initializer` (PR #209367)

David Meng via cfe-commits cfe-commits at lists.llvm.org
Sat Aug 1 11:05:39 PDT 2026


https://github.com/davidmenggx updated https://github.com/llvm/llvm-project/pull/209367

>From 141f008aa375fdfe02314a1b5b806f8c5b79d077 Mon Sep 17 00:00:00 2001
From: David Meng <davidmenggx at gmail.com>
Date: Mon, 13 Jul 2026 21:09:06 -0700
Subject: [PATCH] [clang-tidy] Add `readability-redundant-zero-initializer`

Add a check that finds explicit single-element zero initializers of
arrays and rewrites them to empty braces, e.g. `char a[12] = {0};`
becomes `char a[12] = {};`. Empty-brace initialization zero-initializes
every element, so the explicit `{0}` is redundant. The check is only
enabled in C++ and in C23 or later.

The check is conservative and only rewrites a single-element `{0}` list
whose sole element is the integer literal `0` and whose array has an
explicit bound. It leaves alone, among others:
- arrays whose bound is deduced from the initializer (`char a[] = {0};`)
- multi-dimensional arrays (`int m[2][3] = {0};`)
- initializers with more than one element (`int a[3] = {0, 0};`);
- scalars and class/struct types
- zero written in another form such as `'\0'`, `0.0` or `nullptr`

Closes #209139
---
 .../clang-tidy/readability/CMakeLists.txt     |   1 +
 .../readability/ReadabilityTidyModule.cpp     |   3 +
 .../RedundantZeroInitializerCheck.cpp         |  65 ++++++++++
 .../RedundantZeroInitializerCheck.h           |  38 ++++++
 clang-tools-extra/docs/ReleaseNotes.rst       | 108 +++++++++++++++++
 .../docs/clang-tidy/checks/list.rst           |   1 +
 .../readability/redundant-zero-initializer.md |  36 ++++++
 .../readability/redundant-zero-initializer.c  |  16 +++
 .../redundant-zero-initializer.cpp            | 114 ++++++++++++++++++
 9 files changed, 382 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/readability/RedundantZeroInitializerCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/readability/RedundantZeroInitializerCheck.h
 create mode 100644 clang-tools-extra/docs/clang-tidy/checks/readability/redundant-zero-initializer.md
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/readability/redundant-zero-initializer.c
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/readability/redundant-zero-initializer.cpp

diff --git a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt
index 8a4b5753de890..11cadf9f39879 100644
--- a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt
@@ -54,6 +54,7 @@ add_clang_library(clangTidyReadabilityModule STATIC
   RedundantStringCStrCheck.cpp
   RedundantStringInitCheck.cpp
   RedundantTypenameCheck.cpp
+  RedundantZeroInitializerCheck.cpp
   ReferenceToConstructedTemporaryCheck.cpp
   SimplifyBooleanExprCheck.cpp
   SimplifySubscriptExprCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
index 69b31d6711bcd..0592ae5f1bf60 100644
--- a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
@@ -56,6 +56,7 @@
 #include "RedundantStringCStrCheck.h"
 #include "RedundantStringInitCheck.h"
 #include "RedundantTypenameCheck.h"
+#include "RedundantZeroInitializerCheck.h"
 #include "ReferenceToConstructedTemporaryCheck.h"
 #include "SimplifyBooleanExprCheck.h"
 #include "SimplifySubscriptExprCheck.h"
@@ -160,6 +161,8 @@ class ReadabilityModule : public ClangTidyModule {
         "readability-redundant-qualified-alias");
     CheckFactories.registerCheck<RedundantTypenameCheck>(
         "readability-redundant-typename");
+    CheckFactories.registerCheck<RedundantZeroInitializerCheck>(
+        "readability-redundant-zero-initializer");
     CheckFactories.registerCheck<ReferenceToConstructedTemporaryCheck>(
         "readability-reference-to-constructed-temporary");
     CheckFactories.registerCheck<SimplifySubscriptExprCheck>(
diff --git a/clang-tools-extra/clang-tidy/readability/RedundantZeroInitializerCheck.cpp b/clang-tools-extra/clang-tidy/readability/RedundantZeroInitializerCheck.cpp
new file mode 100644
index 0000000000000..7f1700edd2666
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/readability/RedundantZeroInitializerCheck.cpp
@@ -0,0 +1,65 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "RedundantZeroInitializerCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/Type.h"
+#include "clang/AST/TypeLoc.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::readability {
+
+namespace {
+AST_MATCHER(InitListExpr, isSingleElementBracedList) {
+  return Node.isExplicit() && Node.getNumInits() == 1;
+}
+
+AST_MATCHER(InitListExpr, isInMacro) {
+  return Node.getBeginLoc().isMacroID() || Node.getEndLoc().isMacroID();
+}
+
+AST_MATCHER(InitListExpr, hasScalarArrayType) {
+  const ConstantArrayType *CAT =
+      Finder->getASTContext().getAsConstantArrayType(Node.getType());
+  return CAT && CAT->getElementType()->isScalarType();
+}
+
+// Matches an array declarator whose bound is deduced from the initializer
+// (``T a[]``); replacing ``{0}`` with ``{}`` there would change the size.
+AST_MATCHER(TypeLoc, hasDeducedArrayBound) {
+  const auto ATL = Node.getAs<ArrayTypeLoc>();
+  return ATL && ATL.getSizeExpr() == nullptr;
+}
+} // namespace
+
+void RedundantZeroInitializerCheck::registerMatchers(MatchFinder *Finder) {
+  Finder->addMatcher(
+      initListExpr(
+          hasScalarArrayType(), isSingleElementBracedList(),
+          hasInit(0, ignoringParenImpCasts(integerLiteral(equals(0)))),
+          unless(isInMacro()),
+          unless(hasParent(varDecl(hasTypeLoc(hasDeducedArrayBound())))))
+          .bind("init"),
+      this);
+}
+
+void RedundantZeroInitializerCheck::check(
+    const MatchFinder::MatchResult &Result) {
+  const auto *ILE = Result.Nodes.getNodeAs<InitListExpr>("init");
+  const SourceRange Range = ILE->getSourceRange();
+  diag(Range.getBegin(),
+       "redundant zero initializer; replace with empty braces")
+      << FixItHint::CreateReplacement(Range, "{}");
+}
+
+} // namespace clang::tidy::readability
diff --git a/clang-tools-extra/clang-tidy/readability/RedundantZeroInitializerCheck.h b/clang-tools-extra/clang-tidy/readability/RedundantZeroInitializerCheck.h
new file mode 100644
index 0000000000000..54906c4e07977
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/readability/RedundantZeroInitializerCheck.h
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTZEROINITIALIZERCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTZEROINITIALIZERCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::readability {
+
+/// Finds explicit zero initializers of arrays that can be replaced with empty
+/// braces, e.g. ``char a[12] = {0};`` becomes ``char a[12] = {};``.
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/readability/redundant-zero-initializer.html
+class RedundantZeroInitializerCheck : public ClangTidyCheck {
+public:
+  RedundantZeroInitializerCheck(StringRef Name, ClangTidyContext *Context)
+      : ClangTidyCheck(Name, Context) {}
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+    // Empty braces are only valid in C++ and, for C, since C23.
+    return LangOpts.CPlusPlus || LangOpts.C23;
+  }
+  std::optional<TraversalKind> getCheckTraversalKind() const override {
+    return TK_IgnoreUnlessSpelledInSource;
+  }
+};
+
+} // namespace clang::tidy::readability
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTZEROINITIALIZERCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 69c3bcf67b8db..157edac9127fd 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -97,6 +97,114 @@ Improvements to clang-tidy
 New checks
 ^^^^^^^^^^
 
+- New :doc:`bugprone-assignment-in-selection-statement
+  <clang-tidy/checks/bugprone/assignment-in-selection-statement>` check.
+
+  Finds assignments within selection statements.
+
+- New :doc:`bugprone-missing-end-comparison
+  <clang-tidy/checks/bugprone/missing-end-comparison>` check.
+
+  Finds instances where the result of a standard algorithm is used in a Boolean
+  context without being compared to the end iterator.
+
+- New :doc:`bugprone-unsafe-to-allow-exceptions
+  <clang-tidy/checks/bugprone/unsafe-to-allow-exceptions>` check.
+
+  Finds functions where throwing exceptions is unsafe but the function is still
+  marked as potentially throwing.
+
+- New :doc:`llvm-formatv-string
+  <clang-tidy/checks/llvm/formatv-string>` check.
+
+  Validates ``llvm::formatv`` format strings against the provided arguments,
+  diagnosing mismatched argument counts, unused arguments, and mixed index styles.
+
+- New :doc:`llvm-redundant-casting
+  <clang-tidy/checks/llvm/redundant-casting>` check.
+
+  Points out uses of ``cast<>``, ``dyn_cast<>`` and their ``or_null`` variants
+  that are unnecessary because the argument already is of the target type, or a
+  derived type thereof. Also does similar analysis for calls to ``isa<>`` that
+  always return ``true``.
+
+- New :doc:`llvm-type-switch-case-types
+  <clang-tidy/checks/llvm/type-switch-case-types>` check.
+
+  Finds ``llvm::TypeSwitch::Case`` calls with redundant explicit template
+  arguments that can be inferred from the lambda parameter type.
+
+- New :doc:`llvm-use-vector-utils
+  <clang-tidy/checks/llvm/use-vector-utils>` check.
+
+  Finds calls to ``llvm::to_vector(llvm::map_range(...))`` and
+  ``llvm::to_vector(llvm::make_filter_range(...))`` that can be replaced with
+  ``llvm::map_to_vector`` and ``llvm::filter_to_vector``.
+
+- New :doc:`misc-static-initialization-cycle
+  <clang-tidy/checks/misc/static-initialization-cycle>` check.
+
+  Finds cyclical initialization of static variables.
+
+- New :doc:`modernize-use-std-bit
+  <clang-tidy/checks/modernize/use-std-bit>` check.
+
+  Finds common idioms which can be replaced by standard functions from the
+  ``<bit>`` C++20 header.
+
+- New :doc:`modernize-use-string-view
+  <clang-tidy/checks/modernize/use-string-view>` check.
+
+  Looks for functions returning ``std::[w|u8|u16|u32]string`` and suggests to
+  change it to ``std::[...]string_view`` for performance reasons if possible.
+
+- New :doc:`modernize-use-structured-binding
+  <clang-tidy/checks/modernize/use-structured-binding>` check.
+
+  Finds places where structured bindings could be used to decompose pairs and
+  suggests replacing them.
+
+- New :doc:`performance-string-view-conversions
+  <clang-tidy/checks/performance/string-view-conversions>` check.
+
+  Finds and removes redundant conversions from ``std::[w|u8|u16|u32]string_view`` to
+  ``std::[...]string`` in call expressions expecting ``std::[...]string_view``.
+
+- New :doc:`performance-use-std-move
+  <clang-tidy/checks/performance/use-std-move>` check.
+
+  Suggests insertion of ``std::move(...)`` to turn copy assignment operator
+  calls into move assignment ones, when deemed valid and profitable.
+
+- New :doc:`readability-redundant-lambda-parameter-list
+  <clang-tidy/checks/readability/redundant-lambda-parameter-list>` check.
+
+  Finds lambda expressions with a redundant empty parameter list and removes it.
+
+- New :doc:`readability-redundant-nested-if
+  <clang-tidy/checks/readability/redundant-nested-if>` check.
+
+  Finds nested ``if`` statements that can be merged into a single ``if`` by
+  combining conditions with ``&&``.
+
+- New :doc:`readability-redundant-qualified-alias
+  <clang-tidy/checks/readability/redundant-qualified-alias>` check.
+
+  Finds redundant identity type aliases that re-expose a qualified name and can
+  be replaced with a ``using`` declaration.
+
+- New :doc:`readability-redundant-zero-initializer
+  <clang-tidy/checks/readability/redundant-zero-initializer>` check.
+
+  Finds explicit zero initializers of arrays that can be replaced with empty
+  braces.
+
+- New :doc:`readability-trailing-comma
+  <clang-tidy/checks/readability/trailing-comma>` check.
+
+  Checks for presence or absence of trailing commas in enum definitions and
+  initializer lists.
+
 New check aliases
 ^^^^^^^^^^^^^^^^^
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 2a44dc78fbc89..f5bfc586aec0e 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -426,6 +426,7 @@ Clang-Tidy Checks
    :doc:`readability-redundant-string-cstr <readability/redundant-string-cstr>`, "Yes"
    :doc:`readability-redundant-string-init <readability/redundant-string-init>`, "Yes"
    :doc:`readability-redundant-typename <readability/redundant-typename>`, "Yes"
+   :doc:`readability-redundant-zero-initializer <readability/redundant-zero-initializer>`, "Yes"
    :doc:`readability-reference-to-constructed-temporary <readability/reference-to-constructed-temporary>`,
    :doc:`readability-simplify-boolean-expr <readability/simplify-boolean-expr>`, "Yes"
    :doc:`readability-simplify-subscript-expr <readability/simplify-subscript-expr>`, "Yes"
diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/redundant-zero-initializer.md b/clang-tools-extra/docs/clang-tidy/checks/readability/redundant-zero-initializer.md
new file mode 100644
index 0000000000000..88d7b074d27aa
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/readability/redundant-zero-initializer.md
@@ -0,0 +1,36 @@
+```{title} clang-tidy - readability-redundant-zero-initializer
+```
+
+# readability-redundant-zero-initializer
+
+Finds explicit zero initializers of arrays that can be replaced with empty
+braces.
+
+In C++ and since C23, an empty braced initializer zero-initializes every element
+of an array, so an explicit `{0}` is redundant.
+
+```cpp
+char a[12] = {0};
+int b[5] = {0};
+
+// becomes
+
+char a[12] = {};
+int b[5] = {};
+```
+
+The check is only enabled in C++ and in C23 or later.
+
+## Limitations
+
+To keep the fix always safe, the check is intentionally conservative and only
+handles single-element `{0}` initializers of arrays with an explicit bound.
+It does not flag, among others:
+
+- scalars (`int x = {0};`) and class or struct types (`S s = {0};`);
+- arrays whose bound is deduced from the initializer (`char a[] = {0};`),
+  where `{}` would change the deduced size;
+- multi-dimensional arrays (`int m[2][3] = {0};`);
+- initializers with more than one element (`int a[3] = {0, 0};`);
+- zero written as something other than the integer literal `0`, for example
+  `'\0'`, `0.0` or `nullptr`.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-zero-initializer.c b/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-zero-initializer.c
new file mode 100644
index 0000000000000..18a72bd4db507
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-zero-initializer.c
@@ -0,0 +1,16 @@
+// RUN: %check_clang_tidy -std=c17 -check-suffixes=C17 %s readability-redundant-zero-initializer %t
+// RUN: %check_clang_tidy -std=c23-or-later -check-suffixes=C23 %s readability-redundant-zero-initializer %t
+
+char a[12] = {0};
+// CHECK-MESSAGES-C23: :[[@LINE-1]]:14: warning: redundant zero initializer; replace with empty braces [readability-redundant-zero-initializer]
+// CHECK-FIXES-C23: char a[12] = {};
+
+int b[5] = {0};
+// CHECK-MESSAGES-C23: :[[@LINE-1]]:12: warning: redundant zero initializer; replace with empty braces
+// CHECK-FIXES-C23: int b[5] = {};
+
+char deduced[] = {0};
+int multiZero[3] = {0, 0};
+int mixed[4] = {0, 5};
+int oneByOne[1][1] = {0};
+char nullChar[4] = {'\0'};
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-zero-initializer.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-zero-initializer.cpp
new file mode 100644
index 0000000000000..b2f6ff9de6682
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-zero-initializer.cpp
@@ -0,0 +1,114 @@
+// RUN: %check_clang_tidy -std=c++14-or-later %s readability-redundant-zero-initializer %t
+
+char a[12] = {0};
+// CHECK-MESSAGES: :[[@LINE-1]]:14: warning: redundant zero initializer; replace with empty braces [readability-redundant-zero-initializer]
+// CHECK-FIXES: char a[12] = {};
+
+int b[5] = {0};
+// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: redundant zero initializer; replace with empty braces
+// CHECK-FIXES: int b[5] = {};
+
+double d[3] = {0};
+// CHECK-MESSAGES: :[[@LINE-1]]:15: warning: redundant zero initializer; replace with empty braces
+// CHECK-FIXES: double d[3] = {};
+
+void *p[4] = {0};
+// CHECK-MESSAGES: :[[@LINE-1]]:14: warning: redundant zero initializer; replace with empty braces
+// CHECK-FIXES: void *p[4] = {};
+
+char one[1] = {0};
+// CHECK-MESSAGES: :[[@LINE-1]]:15: warning: redundant zero initializer; replace with empty braces
+// CHECK-FIXES: char one[1] = {};
+
+const char cq[8] = {0};
+// CHECK-MESSAGES: :[[@LINE-1]]:20: warning: redundant zero initializer; replace with empty braces
+// CHECK-FIXES: const char cq[8] = {};
+
+int spaced[2] = { 0 };
+// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: redundant zero initializer; replace with empty braces
+// CHECK-FIXES: int spaced[2] = {};
+
+int trailingComma[3] = {0,};
+// CHECK-MESSAGES: :[[@LINE-1]]:24: warning: redundant zero initializer; replace with empty braces
+// CHECK-FIXES: int trailingComma[3] = {};
+
+int paren[2] = {(0)};
+// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: redundant zero initializer; replace with empty braces
+// CHECK-FIXES: int paren[2] = {};
+
+double nestedParen[2] = {((0))};
+// CHECK-MESSAGES: :[[@LINE-1]]:25: warning: redundant zero initializer; replace with empty braces
+// CHECK-FIXES: double nestedParen[2] = {};
+
+struct S {
+  char buf[4] = {0};
+  // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: redundant zero initializer; replace with empty braces
+  // CHECK-FIXES: char buf[4] = {};
+};
+
+void localVariables() {
+  int local[4] = {0};
+  // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: redundant zero initializer; replace with empty braces
+  // CHECK-FIXES: int local[4] = {};
+  static int staticLocal[2] = {0};
+  // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: redundant zero initializer; replace with empty braces
+  // CHECK-FIXES: static int staticLocal[2] = {};
+}
+
+using Arr = int[2];
+void use(const int (&)[2]);
+void arrayPrvalue() {
+  use(Arr{0});
+  // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: redundant zero initializer; replace with empty braces
+  // CHECK-FIXES: use(Arr{});
+}
+
+char emptyBraces[12] = {};
+int nonZero[3] = {1};
+int multiZero[3] = {0, 0};
+int mixed[4] = {0, 5};
+int noInit[3];
+
+// Multi-dimensional arrays without inner braces: the single `0` initializes a
+// subobject through brace elision, so there is no written single-element `{0}`
+// list to rewrite.
+int twoD[2][3] = {0};
+int oneByOne[1][1] = {0};
+int rowVector[1][3] = {0};
+int columnVector[3][1] = {0};
+
+// The array bound is deduced from the initializer, `{}` would change the size.
+char deduced[] = {0};
+
+char nullChar[4] = {'\0'};
+double zeroDouble[3] = {0.0};
+void *nullPtr[4] = {nullptr};
+
+int scalar = {0};
+
+struct P { int x; int y; };
+P pod = {0};
+
+// Array of a class type with a default member initializer: `{0}` and `{}` are
+// not equivalent, so it must not be rewritten.
+struct WithDefault { int v = 7; };
+WithDefault wd[2] = {0};
+
+// Template instantiations share the pattern's written braces. Rewriting `{0}`
+// in the pattern would break the `templateFn<X>` instantiation, whose element
+// type is not default-constructible, so neither the pattern nor any of its
+// instantiations may be flagged.
+struct X {
+  X(int);
+  X() = delete;
+};
+
+template <class T>
+void templateFn() {
+  T arr[1] = {0};
+}
+
+void instantiate() {
+  templateFn<int>();
+  templateFn<X>();
+}



More information about the cfe-commits mailing list