clang-tools 24.0.0git
EnumInitialValueCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
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
22
23/// Check if \p ECD is initialized by referencing another enumerator in the
24/// same enum (e.g., `last = first`).
25static bool isSelfReference(const EnumConstantDecl *ECD) {
26 const auto *CE = dyn_cast_if_present<ConstantExpr>(ECD->getInitExpr());
27 const auto *DRE =
28 dyn_cast_if_present<DeclRefExpr>(CE ? CE->getSubExpr() : nullptr);
29 const auto *RefECD =
30 dyn_cast_if_present<EnumConstantDecl>(DRE ? DRE->getDecl() : nullptr);
31 return RefECD && RefECD->getDeclContext() == ECD->getDeclContext();
32}
33
34static bool isAllowedSelfReference(const EnumConstantDecl *ECD,
35 bool AllowSelfRefs) {
36 return AllowSelfRefs && isSelfReference(ECD);
37}
38
39static bool isNoneEnumeratorsInitialized(const EnumDecl &Node,
40 bool AllowSelfRefs) {
41 return llvm::all_of(Node.enumerators(),
42 [AllowSelfRefs](const EnumConstantDecl *ECD) {
43 return isAllowedSelfReference(ECD, AllowSelfRefs) ||
44 ECD->getInitExpr() == nullptr;
45 });
46}
47
48static bool isOnlyFirstEnumeratorInitialized(const EnumDecl &Node,
49 bool AllowSelfRefs) {
50 bool IsFirst = true;
51 for (const EnumConstantDecl *ECD : Node.enumerators()) {
52 if (isAllowedSelfReference(ECD, AllowSelfRefs))
53 continue;
54 if ((IsFirst && ECD->getInitExpr() == nullptr) ||
55 (!IsFirst && ECD->getInitExpr() != nullptr))
56 return false;
57 IsFirst = false;
58 }
59 return !IsFirst;
60}
61
62static bool areAllEnumeratorsInitialized(const EnumDecl &Node) {
63 return llvm::all_of(Node.enumerators(), [](const EnumConstantDecl *ECD) {
64 return ECD->getInitExpr() != nullptr;
65 });
66}
67
68/// Check if \p Enumerator is initialized with a (potentially negated) \c
69/// IntegerLiteral.
70static bool isInitializedByLiteral(const EnumConstantDecl *Enumerator) {
71 const Expr *const Init = Enumerator->getInitExpr();
72 if (!Init)
73 return false;
74 return Init->isIntegerConstantExpr(Enumerator->getASTContext());
75}
76
77static void cleanInitialValue(const DiagnosticBuilder &Diag,
78 const EnumConstantDecl *ECD,
79 const SourceManager &SM,
80 const LangOptions &LangOpts) {
81 const SourceRange InitExprRange = ECD->getInitExpr()->getSourceRange();
82 if (InitExprRange.isInvalid() || InitExprRange.getBegin().isMacroID() ||
83 InitExprRange.getEnd().isMacroID())
84 return;
85 std::optional<Token> EqualToken = utils::lexer::findNextTokenSkippingComments(
86 ECD->getLocation(), SM, LangOpts);
87 if (!EqualToken.has_value() ||
88 EqualToken.value().getKind() != tok::TokenKind::equal)
89 return;
90 const SourceLocation EqualLoc{EqualToken->getLocation()};
91 if (EqualLoc.isInvalid() || EqualLoc.isMacroID())
92 return;
93 Diag << FixItHint::CreateRemoval(EqualLoc)
94 << FixItHint::CreateRemoval(InitExprRange);
95}
96
97namespace {
98
99AST_MATCHER(EnumDecl, isMacro) {
100 const SourceLocation Loc = Node.getBeginLoc();
101 return Loc.isMacroID();
102}
103
104AST_MATCHER_P(EnumDecl, hasConsistentInitialValues, bool, AllowSelfRefs) {
105 return isNoneEnumeratorsInitialized(Node, AllowSelfRefs) ||
106 isOnlyFirstEnumeratorInitialized(Node, AllowSelfRefs) ||
108}
109
110AST_MATCHER_P(EnumDecl, hasZeroInitialValueForFirstEnumerator, bool,
111 AllowSelfRefs) {
112 const EnumDecl::enumerator_range Enumerators = Node.enumerators();
113 if (Enumerators.empty())
114 return false;
115 const EnumConstantDecl *ECD = *Enumerators.begin();
116 return isOnlyFirstEnumeratorInitialized(Node, AllowSelfRefs) &&
117 isInitializedByLiteral(ECD) && ECD->getInitVal().isZero();
118}
119
120/// Excludes bitfields because enumerators initialized with the result of a
121/// bitwise operator on enumeration values or any other expr that is not a
122/// potentially negative integer literal.
123/// Enumerations where it is not directly clear if they are used with
124/// bitmask, evident when enumerators are only initialized with (potentially
125/// negative) integer literals, are ignored. This is also the case when all
126/// enumerators are powers of two (e.g., 0, 1, 2).
127AST_MATCHER_P(EnumDecl, hasSequentialInitialValues, bool, AllowSelfRefs) {
128 const EnumDecl::enumerator_range Enumerators = Node.enumerators();
129 if (Enumerators.empty())
130 return false;
131 const EnumConstantDecl *const FirstEnumerator = *Node.enumerator_begin();
132 llvm::APSInt PrevValue = FirstEnumerator->getInitVal();
133 if (!isInitializedByLiteral(FirstEnumerator))
134 return false;
135 bool AllEnumeratorsArePowersOfTwo = true;
136 for (const EnumConstantDecl *Enumerator : llvm::drop_begin(Enumerators)) {
137 if (isAllowedSelfReference(Enumerator, AllowSelfRefs))
138 continue;
139 const llvm::APSInt NewValue = Enumerator->getInitVal();
140 if (NewValue != ++PrevValue)
141 return false;
142 if (!isInitializedByLiteral(Enumerator))
143 return false;
144 PrevValue = NewValue;
145 AllEnumeratorsArePowersOfTwo &= NewValue.isPowerOf2();
146 }
147 return !AllEnumeratorsArePowersOfTwo;
148}
149
150} // namespace
151
152static std::string getName(const EnumDecl *Decl) {
153 if (!Decl->getDeclName())
154 return "<unnamed>";
155
156 return Decl->getQualifiedNameAsString();
157}
158
160 ClangTidyContext *Context)
161 : ClangTidyCheck(Name, Context),
162 AllowExplicitZeroFirstInitialValue(
163 Options.get("AllowExplicitZeroFirstInitialValue", true)),
164 AllowExplicitSequentialInitialValues(
165 Options.get("AllowExplicitSequentialInitialValues", true)),
166 AllowReferencedInitialValues(
167 Options.get("AllowReferencedInitialValues", false)) {}
168
170 Options.store(Opts, "AllowExplicitZeroFirstInitialValue",
171 AllowExplicitZeroFirstInitialValue);
172 Options.store(Opts, "AllowExplicitSequentialInitialValues",
173 AllowExplicitSequentialInitialValues);
174 Options.store(Opts, "AllowReferencedInitialValues",
175 AllowReferencedInitialValues);
176}
177
179 const bool AllowSelfRefs = AllowReferencedInitialValues;
180 Finder->addMatcher(enumDecl(isDefinition(), unless(isMacro()),
181 unless(hasConsistentInitialValues(AllowSelfRefs)))
182 .bind("inconsistent"),
183 this);
184 if (!AllowExplicitZeroFirstInitialValue)
185 Finder->addMatcher(
186 enumDecl(isDefinition(),
187 hasZeroInitialValueForFirstEnumerator(AllowSelfRefs))
188 .bind("zero_first"),
189 this);
190 if (!AllowExplicitSequentialInitialValues)
191 Finder->addMatcher(enumDecl(isDefinition(), unless(isMacro()),
192 hasSequentialInitialValues(AllowSelfRefs))
193 .bind("sequential"),
194 this);
195}
196
197void EnumInitialValueCheck::check(const MatchFinder::MatchResult &Result) {
198 if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("inconsistent")) {
199 // Emit warning first (DiagnosticBuilder emits on destruction), then notes.
200 // Notes must follow the primary diagnostic or they may be dropped.
201 {
202 const DiagnosticBuilder Diag =
203 diag(Enum->getBeginLoc(), "initial values in enum '%0' are not "
204 "consistent, consider explicit "
205 "initialization of all, none or only the "
206 "first enumerator")
207 << getName(Enum);
208
209 for (const EnumConstantDecl *ECD : Enum->enumerators()) {
210 if (ECD->getInitExpr() == nullptr) {
211 const SourceLocation EndLoc = Lexer::getLocForEndOfToken(
212 ECD->getLocation(), 0, *Result.SourceManager, getLangOpts());
213 if (EndLoc.isMacroID())
214 continue;
215 SmallString<8> Str{" = "};
216 ECD->getInitVal().toString(Str);
217 Diag << FixItHint::CreateInsertion(EndLoc, Str);
218 }
219 }
220 }
221
222 for (const EnumConstantDecl *ECD : Enum->enumerators()) {
223 if (ECD->getInitExpr() == nullptr) {
224 diag(ECD->getLocation(), "uninitialized enumerator '%0' defined here",
225 DiagnosticIDs::Note)
226 << ECD->getName();
227 }
228 }
229 return;
230 }
231
232 if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("zero_first")) {
233 const EnumConstantDecl *ECD = *Enum->enumerator_begin();
234 const SourceLocation Loc = ECD->getLocation();
235 if (Loc.isInvalid() || Loc.isMacroID())
236 return;
237 const DiagnosticBuilder Diag =
238 diag(Loc, "zero initial value for the first "
239 "enumerator in '%0' can be disregarded")
240 << getName(Enum);
241 cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts());
242 return;
243 }
244 if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("sequential")) {
245 const DiagnosticBuilder Diag =
246 diag(Enum->getBeginLoc(),
247 "sequential initial value in '%0' can be ignored")
248 << getName(Enum);
249 // Only remove the explicit value of an enumerator when the preceding
250 // declared enumerator equals `current - 1`, so the implicit value stays
251 // the same. An interleaved self-reference can break this, in which case
252 // the value must be kept.
253 const EnumConstantDecl *PrevECD = nullptr;
254 for (const EnumConstantDecl *ECD : Enum->enumerators()) {
255 if (PrevECD != nullptr &&
256 !isAllowedSelfReference(ECD, AllowReferencedInitialValues)) {
257 llvm::APSInt Expected = PrevECD->getInitVal();
258 ++Expected;
259 if (llvm::APSInt::isSameValue(Expected, ECD->getInitVal()))
260 cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts());
261 }
262 PrevECD = ECD;
263 }
264 return;
265 }
266}
267
268} // namespace clang::tidy::readability
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
EnumInitialValueCheck(StringRef Name, ClangTidyContext *Context)
AST_MATCHER_P(Stmt, isStatementIdenticalToBoundNode, std::string, ID)
static void cleanInitialValue(const DiagnosticBuilder &Diag, const EnumConstantDecl *ECD, const SourceManager &SM, const LangOptions &LangOpts)
static bool isInitializedByLiteral(const EnumConstantDecl *Enumerator)
Check if Enumerator is initialized with a (potentially negated) IntegerLiteral.
static std::string getName(const EnumDecl *Decl)
static bool areAllEnumeratorsInitialized(const EnumDecl &Node)
static bool isNoneEnumeratorsInitialized(const EnumDecl &Node, bool AllowSelfRefs)
static bool isOnlyFirstEnumeratorInitialized(const EnumDecl &Node, bool AllowSelfRefs)
static bool isAllowedSelfReference(const EnumConstantDecl *ECD, bool AllowSelfRefs)
static bool isSelfReference(const EnumConstantDecl *ECD)
Check if ECD is initialized by referencing another enumerator in the same enum (e....
std::optional< Token > findNextTokenSkippingComments(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts)
Definition LexerUtils.h:106
llvm::StringMap< ClangTidyValue > OptionMap