1//===--- EnumInitialValueCheck.cpp - clang-tidy ---------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "EnumInitialValueCheck.h"
10#include "../utils/LexerUtils.h"
11#include "clang/AST/Decl.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "clang/ASTMatchers/ASTMatchers.h"
14#include "clang/Basic/Diagnostic.h"
15#include "clang/Basic/SourceLocation.h"
16#include "llvm/ADT/STLExtras.h"
17#include "llvm/ADT/SmallString.h"
18
19using namespace clang::ast_matchers;
20
21namespace clang::tidy::readability {
22
23static bool isNoneEnumeratorsInitialized(const EnumDecl &Node) {
24 return llvm::all_of(Range: Node.enumerators(), P: [](const EnumConstantDecl *ECD) {
25 return ECD->getInitExpr() == nullptr;
26 });
27}
28
29static bool isOnlyFirstEnumeratorInitialized(const EnumDecl &Node) {
30 bool IsFirst = true;
31 for (const EnumConstantDecl *ECD : Node.enumerators()) {
32 if ((IsFirst && ECD->getInitExpr() == nullptr) ||
33 (!IsFirst && ECD->getInitExpr() != nullptr))
34 return false;
35 IsFirst = false;
36 }
37 return !IsFirst;
38}
39
40static bool areAllEnumeratorsInitialized(const EnumDecl &Node) {
41 return llvm::all_of(Range: Node.enumerators(), P: [](const EnumConstantDecl *ECD) {
42 return ECD->getInitExpr() != nullptr;
43 });
44}
45
46/// Check if \p Enumerator is initialized with a (potentially negated) \c
47/// IntegerLiteral.
48static bool isInitializedByLiteral(const EnumConstantDecl *Enumerator) {
49 const Expr *const Init = Enumerator->getInitExpr();
50 if (!Init)
51 return false;
52 return Init->isIntegerConstantExpr(Ctx: Enumerator->getASTContext());
53}
54
55static void cleanInitialValue(DiagnosticBuilder &Diag,
56 const EnumConstantDecl *ECD,
57 const SourceManager &SM,
58 const LangOptions &LangOpts) {
59 const SourceRange InitExprRange = ECD->getInitExpr()->getSourceRange();
60 if (InitExprRange.isInvalid() || InitExprRange.getBegin().isMacroID() ||
61 InitExprRange.getEnd().isMacroID())
62 return;
63 std::optional<Token> EqualToken = utils::lexer::findNextTokenSkippingComments(
64 Start: ECD->getLocation(), SM, LangOpts);
65 if (!EqualToken.has_value() ||
66 EqualToken.value().getKind() != tok::TokenKind::equal)
67 return;
68 const SourceLocation EqualLoc{EqualToken->getLocation()};
69 if (EqualLoc.isInvalid() || EqualLoc.isMacroID())
70 return;
71 Diag << FixItHint::CreateRemoval(RemoveRange: EqualLoc)
72 << FixItHint::CreateRemoval(RemoveRange: InitExprRange);
73 return;
74}
75
76namespace {
77
78AST_MATCHER(EnumDecl, isMacro) {
79 SourceLocation Loc = Node.getBeginLoc();
80 return Loc.isMacroID();
81}
82
83AST_MATCHER(EnumDecl, hasConsistentInitialValues) {
84 return isNoneEnumeratorsInitialized(Node) ||
85 isOnlyFirstEnumeratorInitialized(Node) ||
86 areAllEnumeratorsInitialized(Node);
87}
88
89AST_MATCHER(EnumDecl, hasZeroInitialValueForFirstEnumerator) {
90 const EnumDecl::enumerator_range Enumerators = Node.enumerators();
91 if (Enumerators.empty())
92 return false;
93 const EnumConstantDecl *ECD = *Enumerators.begin();
94 return isOnlyFirstEnumeratorInitialized(Node) &&
95 isInitializedByLiteral(Enumerator: ECD) && ECD->getInitVal().isZero();
96}
97
98/// Excludes bitfields because enumerators initialized with the result of a
99/// bitwise operator on enumeration values or any other expr that is not a
100/// potentially negative integer literal.
101/// Enumerations where it is not directly clear if they are used with
102/// bitmask, evident when enumerators are only initialized with (potentially
103/// negative) integer literals, are ignored. This is also the case when all
104/// enumerators are powers of two (e.g., 0, 1, 2).
105AST_MATCHER(EnumDecl, hasSequentialInitialValues) {
106 const EnumDecl::enumerator_range Enumerators = Node.enumerators();
107 if (Enumerators.empty())
108 return false;
109 const EnumConstantDecl *const FirstEnumerator = *Node.enumerator_begin();
110 llvm::APSInt PrevValue = FirstEnumerator->getInitVal();
111 if (!isInitializedByLiteral(Enumerator: FirstEnumerator))
112 return false;
113 bool AllEnumeratorsArePowersOfTwo = true;
114 for (const EnumConstantDecl *Enumerator : llvm::drop_begin(RangeOrContainer: Enumerators)) {
115 const llvm::APSInt NewValue = Enumerator->getInitVal();
116 if (NewValue != ++PrevValue)
117 return false;
118 if (!isInitializedByLiteral(Enumerator))
119 return false;
120 PrevValue = NewValue;
121 AllEnumeratorsArePowersOfTwo &= NewValue.isPowerOf2();
122 }
123 return !AllEnumeratorsArePowersOfTwo;
124}
125
126} // namespace
127
128EnumInitialValueCheck::EnumInitialValueCheck(StringRef Name,
129 ClangTidyContext *Context)
130 : ClangTidyCheck(Name, Context),
131 AllowExplicitZeroFirstInitialValue(
132 Options.get(LocalName: "AllowExplicitZeroFirstInitialValue", Default: true)),
133 AllowExplicitSequentialInitialValues(
134 Options.get(LocalName: "AllowExplicitSequentialInitialValues", Default: true)) {}
135
136void EnumInitialValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
137 Options.store(Options&: Opts, LocalName: "AllowExplicitZeroFirstInitialValue",
138 Value: AllowExplicitZeroFirstInitialValue);
139 Options.store(Options&: Opts, LocalName: "AllowExplicitSequentialInitialValues",
140 Value: AllowExplicitSequentialInitialValues);
141}
142
143void EnumInitialValueCheck::registerMatchers(MatchFinder *Finder) {
144 Finder->addMatcher(
145 NodeMatch: enumDecl(unless(isMacro()), unless(hasConsistentInitialValues()))
146 .bind(ID: "inconsistent"),
147 Action: this);
148 if (!AllowExplicitZeroFirstInitialValue)
149 Finder->addMatcher(
150 NodeMatch: enumDecl(hasZeroInitialValueForFirstEnumerator()).bind(ID: "zero_first"),
151 Action: this);
152 if (!AllowExplicitSequentialInitialValues)
153 Finder->addMatcher(NodeMatch: enumDecl(unless(isMacro()), hasSequentialInitialValues())
154 .bind(ID: "sequential"),
155 Action: this);
156}
157
158void EnumInitialValueCheck::check(const MatchFinder::MatchResult &Result) {
159 if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>(ID: "inconsistent")) {
160 DiagnosticBuilder Diag =
161 diag(Enum->getBeginLoc(),
162 "inital values in enum %0 are not consistent, consider explicit "
163 "initialization of all, none or only the first enumerator")
164 << Enum;
165 for (const EnumConstantDecl *ECD : Enum->enumerators())
166 if (ECD->getInitExpr() == nullptr) {
167 const SourceLocation EndLoc = Lexer::getLocForEndOfToken(
168 Loc: ECD->getLocation(), Offset: 0, SM: *Result.SourceManager, LangOpts: getLangOpts());
169 if (EndLoc.isMacroID())
170 continue;
171 llvm::SmallString<8> Str{" = "};
172 ECD->getInitVal().toString(Str);
173 Diag << FixItHint::CreateInsertion(InsertionLoc: EndLoc, Code: Str);
174 }
175 return;
176 }
177
178 if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>(ID: "zero_first")) {
179 const EnumConstantDecl *ECD = *Enum->enumerator_begin();
180 const SourceLocation Loc = ECD->getLocation();
181 if (Loc.isInvalid() || Loc.isMacroID())
182 return;
183 DiagnosticBuilder Diag = diag(Loc, Description: "zero initial value for the first "
184 "enumerator in %0 can be disregarded")
185 << Enum;
186 cleanInitialValue(Diag, ECD, SM: *Result.SourceManager, LangOpts: getLangOpts());
187 return;
188 }
189 if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>(ID: "sequential")) {
190 DiagnosticBuilder Diag =
191 diag(Enum->getBeginLoc(),
192 "sequential initial value in %0 can be ignored")
193 << Enum;
194 for (const EnumConstantDecl *ECD : llvm::drop_begin(RangeOrContainer: Enum->enumerators()))
195 cleanInitialValue(Diag, ECD, SM: *Result.SourceManager, LangOpts: getLangOpts());
196 return;
197 }
198}
199
200} // namespace clang::tidy::readability
201

source code of clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.cpp