1 //===--- EnumInitialValueCheck.cpp - clang-tidy ---------------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
19 using namespace clang::ast_matchers
;
21 namespace clang::tidy::readability
{
23 static bool isNoneEnumeratorsInitialized(const EnumDecl
&Node
) {
24 return llvm::all_of(Node
.enumerators(), [](const EnumConstantDecl
*ECD
) {
25 return ECD
->getInitExpr() == nullptr;
29 static bool isOnlyFirstEnumeratorInitialized(const EnumDecl
&Node
) {
31 for (const EnumConstantDecl
*ECD
: Node
.enumerators()) {
32 if ((IsFirst
&& ECD
->getInitExpr() == nullptr) ||
33 (!IsFirst
&& ECD
->getInitExpr() != nullptr))
40 static bool areAllEnumeratorsInitialized(const EnumDecl
&Node
) {
41 return llvm::all_of(Node
.enumerators(), [](const EnumConstantDecl
*ECD
) {
42 return ECD
->getInitExpr() != nullptr;
46 /// Check if \p Enumerator is initialized with a (potentially negated) \c
48 static bool isInitializedByLiteral(const EnumConstantDecl
*Enumerator
) {
49 const Expr
*const Init
= Enumerator
->getInitExpr();
52 return Init
->isIntegerConstantExpr(Enumerator
->getASTContext());
55 static 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())
63 std::optional
<Token
> EqualToken
= utils::lexer::findNextTokenSkippingComments(
64 ECD
->getLocation(), SM
, LangOpts
);
65 if (!EqualToken
.has_value() ||
66 EqualToken
.value().getKind() != tok::TokenKind::equal
)
68 const SourceLocation EqualLoc
{EqualToken
->getLocation()};
69 if (EqualLoc
.isInvalid() || EqualLoc
.isMacroID())
71 Diag
<< FixItHint::CreateRemoval(EqualLoc
)
72 << FixItHint::CreateRemoval(InitExprRange
);
78 AST_MATCHER(EnumDecl
, isMacro
) {
79 SourceLocation Loc
= Node
.getBeginLoc();
80 return Loc
.isMacroID();
83 AST_MATCHER(EnumDecl
, hasConsistentInitialValues
) {
84 return isNoneEnumeratorsInitialized(Node
) ||
85 isOnlyFirstEnumeratorInitialized(Node
) ||
86 areAllEnumeratorsInitialized(Node
);
89 AST_MATCHER(EnumDecl
, hasZeroInitialValueForFirstEnumerator
) {
90 const EnumDecl::enumerator_range Enumerators
= Node
.enumerators();
91 if (Enumerators
.empty())
93 const EnumConstantDecl
*ECD
= *Enumerators
.begin();
94 return isOnlyFirstEnumeratorInitialized(Node
) &&
95 isInitializedByLiteral(ECD
) && ECD
->getInitVal().isZero();
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).
105 AST_MATCHER(EnumDecl
, hasSequentialInitialValues
) {
106 const EnumDecl::enumerator_range Enumerators
= Node
.enumerators();
107 if (Enumerators
.empty())
109 const EnumConstantDecl
*const FirstEnumerator
= *Node
.enumerator_begin();
110 llvm::APSInt PrevValue
= FirstEnumerator
->getInitVal();
111 if (!isInitializedByLiteral(FirstEnumerator
))
113 bool AllEnumeratorsArePowersOfTwo
= true;
114 for (const EnumConstantDecl
*Enumerator
: llvm::drop_begin(Enumerators
)) {
115 const llvm::APSInt NewValue
= Enumerator
->getInitVal();
116 if (NewValue
!= ++PrevValue
)
118 if (!isInitializedByLiteral(Enumerator
))
120 PrevValue
= NewValue
;
121 AllEnumeratorsArePowersOfTwo
&= NewValue
.isPowerOf2();
123 return !AllEnumeratorsArePowersOfTwo
;
126 std::string
getName(const EnumDecl
*Decl
) {
127 if (!Decl
->getDeclName())
130 return Decl
->getQualifiedNameAsString();
135 EnumInitialValueCheck::EnumInitialValueCheck(StringRef Name
,
136 ClangTidyContext
*Context
)
137 : ClangTidyCheck(Name
, Context
),
138 AllowExplicitZeroFirstInitialValue(
139 Options
.get("AllowExplicitZeroFirstInitialValue", true)),
140 AllowExplicitSequentialInitialValues(
141 Options
.get("AllowExplicitSequentialInitialValues", true)) {}
143 void EnumInitialValueCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
144 Options
.store(Opts
, "AllowExplicitZeroFirstInitialValue",
145 AllowExplicitZeroFirstInitialValue
);
146 Options
.store(Opts
, "AllowExplicitSequentialInitialValues",
147 AllowExplicitSequentialInitialValues
);
150 void EnumInitialValueCheck::registerMatchers(MatchFinder
*Finder
) {
151 Finder
->addMatcher(enumDecl(isDefinition(), unless(isMacro()),
152 unless(hasConsistentInitialValues()))
153 .bind("inconsistent"),
155 if (!AllowExplicitZeroFirstInitialValue
)
157 enumDecl(isDefinition(), hasZeroInitialValueForFirstEnumerator())
160 if (!AllowExplicitSequentialInitialValues
)
161 Finder
->addMatcher(enumDecl(isDefinition(), unless(isMacro()),
162 hasSequentialInitialValues())
167 void EnumInitialValueCheck::check(const MatchFinder::MatchResult
&Result
) {
168 if (const auto *Enum
= Result
.Nodes
.getNodeAs
<EnumDecl
>("inconsistent")) {
169 DiagnosticBuilder Diag
=
172 "initial values in enum '%0' are not consistent, consider explicit "
173 "initialization of all, none or only the first enumerator")
175 for (const EnumConstantDecl
*ECD
: Enum
->enumerators())
176 if (ECD
->getInitExpr() == nullptr) {
177 const SourceLocation EndLoc
= Lexer::getLocForEndOfToken(
178 ECD
->getLocation(), 0, *Result
.SourceManager
, getLangOpts());
179 if (EndLoc
.isMacroID())
181 llvm::SmallString
<8> Str
{" = "};
182 ECD
->getInitVal().toString(Str
);
183 Diag
<< FixItHint::CreateInsertion(EndLoc
, Str
);
188 if (const auto *Enum
= Result
.Nodes
.getNodeAs
<EnumDecl
>("zero_first")) {
189 const EnumConstantDecl
*ECD
= *Enum
->enumerator_begin();
190 const SourceLocation Loc
= ECD
->getLocation();
191 if (Loc
.isInvalid() || Loc
.isMacroID())
193 DiagnosticBuilder Diag
= diag(Loc
, "zero initial value for the first "
194 "enumerator in '%0' can be disregarded")
196 cleanInitialValue(Diag
, ECD
, *Result
.SourceManager
, getLangOpts());
199 if (const auto *Enum
= Result
.Nodes
.getNodeAs
<EnumDecl
>("sequential")) {
200 DiagnosticBuilder Diag
=
201 diag(Enum
->getBeginLoc(),
202 "sequential initial value in '%0' can be ignored")
204 for (const EnumConstantDecl
*ECD
: llvm::drop_begin(Enum
->enumerators()))
205 cleanInitialValue(Diag
, ECD
, *Result
.SourceManager
, getLangOpts());
210 } // namespace clang::tidy::readability