[clang-tools-extra] [clang-tidy] add new check readability-enum-initial-value (PR #86129)
Julian Schmidt via cfe-commits
cfe-commits at lists.llvm.org
Fri Mar 22 18:42:42 PDT 2024
================
@@ -0,0 +1,82 @@
+//===--- EnumInitialValueCheck.cpp - clang-tidy ---------------------------===//
+//
+// 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 "EnumInitialValueCheck.h"
+#include "../utils/LexerUtils.h"
+#include "clang/AST/Decl.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Basic/Diagnostic.h"
+#include "llvm/ADT/SmallString.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::readability {
+
+namespace {
+
+AST_MATCHER(EnumDecl, isNoneEnumeratorsInitialized) {
+ return llvm::all_of(Node.enumerators(), [](EnumConstantDecl const *ECD) {
+ return ECD->getInitExpr() == nullptr;
+ });
+}
+
+AST_MATCHER(EnumDecl, isOnlyFirstEnumeratorsInitialized) {
+ for (EnumConstantDecl const *ECD : Node.enumerators())
+ if (ECD == *Node.enumerator_begin()) {
+ if (ECD->getInitExpr() == nullptr)
+ return false;
+ } else {
+ if (ECD->getInitExpr() != nullptr)
+ return false;
+ }
+ return true;
+}
+
+AST_MATCHER(EnumDecl, isAllEnumeratorsInitialized) {
+ return llvm::all_of(Node.enumerators(), [](EnumConstantDecl const *ECD) {
+ return ECD->getInitExpr() != nullptr;
+ });
+}
+
+} // namespace
+
+void EnumInitialValueCheck::registerMatchers(MatchFinder *Finder) {
+ Finder->addMatcher(enumDecl(unless(anyOf(isNoneEnumeratorsInitialized(),
+ isOnlyFirstEnumeratorsInitialized(),
+ isAllEnumeratorsInitialized())))
----------------
5chmidti wrote:
I think it's possible to detect these linear enum initializations and remove a lot of false positives by placing some restrictions on which ones to diagnose.
I came up with:
- must be linear
- must include an enumerator whose value is not a power of two
- every enumeration value must be initialized with a (potentially negative) integer literal
Code:
```c++
/// Check if \p Enumerator is initialized with a (potentially negated)
/// \c IntegerLiteral.
bool isInitializedByLiteral(const EnumConstantDecl *Enumerator) {
const Expr *const Init = Enumerator->getInitExpr();
if (!Init)
return false;
const auto *const Constant = llvm::dyn_cast<ConstantExpr>(Init);
if (!Constant)
return false;
if (llvm::isa<IntegerLiteral>(Constant->getSubExpr()))
return true;
const auto *const NegativeValue =
llvm::dyn_cast<UnaryOperator>(Constant->getSubExpr());
if (!NegativeValue)
return false;
return llvm::isa<IntegerLiteral>(NegativeValue->getSubExpr());
}
/// Excludes bitfields because enumerators initialized with the result of a
/// bitwise operator on enumeration values or any other expr that is not a
/// potentially negative integer literal.
/// Enumerations where it is not directly clear if they are used with
/// bitmasking, evident when enumerators are only inintialized with (potentially
/// negative) integer literals, are ignored. This is also the case when all
/// enumerators are powers of two (e.g., 0, 1, 2).
bool isLinearAndDiagnosable(const EnumDecl *Enum) {
if (Enum->enumerators().empty())
return false;
const EnumConstantDecl *const FirstEnumerator = *Enum->enumerator_begin();
llvm::APSInt PrevValue = FirstEnumerator->getInitVal();
if (!isInitializedByLiteral(FirstEnumerator))
return false;
bool AllEnumeratorsArePowersOfTwo = true;
for (const EnumConstantDecl *Enumerator :
llvm::drop_begin(Enum->enumerators())) {
const llvm::APSInt NewValue = Enumerator->getInitVal();
if (NewValue != ++PrevValue)
return false;
if (!isInitializedByLiteral(Enumerator))
return false;
PrevValue = NewValue;
AllEnumeratorsArePowersOfTwo &= NewValue.isPowerOf2();
}
return !AllEnumeratorsArePowersOfTwo;
}
```
to test:
Add
```c++
if (isLinearAndDiagnosable(Enum)) {
diag(Loc, "linear enum %0") << Enum;
}
return; // to only test matching linear enums
```
after the `Loc.isInvalid()...` check in `check` and remove `isAllEnumeratorsInitialized` from the matcher.
<details><summary>Example Enums</summary>
enum class EAllUnclear {
EAll_a = 0,
EAll_b = 1,
EAll_c = 2,
};
enum class EAllBitmask {
EAll_a = 1,
EAll_b = 2,
EAll_c = EAll_a | EAll_b,
};
enum class EAllWithNegative {
EAll_n2 = -2,
EAll_n1 = -1,
EAll_0 = 0,
EAll_a = 1,
EAll_b = 2,
};
enum class EAllWithNegative1 {
EAll_n1 = -1,
EAll_0 = 0,
EAll_a = 1,
EAll_b = 2,
};
enum class EAllWithNegative3 {
EAll_n3 = -3,
EAll_n2 = -2,
EAll_n1 = -1,
EAll_0 = 0,
EAll_a = 1,
EAll_b = 2,
};
enum class EAllOffset {
EAll_a = 8,
EAll_b = 9,
EAll_c = 10,
EAll_d = 11,
};
</details>
Findings when running clang-tidy over `clang/lib/Basic` with `-header-filter="-*"`:
https://github.com/llvm/llvm-project/blob/2f6b1b4b30e3a719b1744baa4cd1ece504998c6e/clang/include/clang/Basic/LangOptions.h#L276
https://github.com/llvm/llvm-project/blob/2f6b1b4b30e3a719b1744baa4cd1ece504998c6e/clang/include/clang/Basic/LangOptions.h#L388
https://github.com/llvm/llvm-project/blob/2f6b1b4b30e3a719b1744baa4cd1ece504998c6e/clang/include/clang/Basic/DiagnosticIDs.h#L85
https://github.com/llvm/llvm-project/blob/2f6b1b4b30e3a719b1744baa4cd1ece504998c6e/clang/include/clang/Basic/Specifiers.h#L388
https://github.com/llvm/llvm-project/blob/2f6b1b4b30e3a719b1744baa4cd1ece504998c6e/llvm/include/llvm/Support/CodeGen.h#L54
https://github.com/llvm/llvm-project/blob/2f6b1b4b30e3a719b1744baa4cd1ece504998c6e/llvm/include/llvm/Support/Unicode.h#L27
(and three in `OMP.h.inc` (table-gen)).
https://github.com/llvm/llvm-project/pull/86129
More information about the cfe-commits
mailing list