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}
74
75namespace {
76
77AST_MATCHER(EnumDecl, isMacro) {
78 SourceLocation Loc = Node.getBeginLoc();
79 return Loc.isMacroID();
80}
81
82AST_MATCHER(EnumDecl, hasConsistentInitialValues) {
83 return isNoneEnumeratorsInitialized(Node) ||
84 isOnlyFirstEnumeratorInitialized(Node) ||
85 areAllEnumeratorsInitialized(Node);
86}
87
88AST_MATCHER(EnumDecl, hasZeroInitialValueForFirstEnumerator) {
89 const EnumDecl::enumerator_range Enumerators = Node.enumerators();
90 if (Enumerators.empty())
91 return false;
92 const EnumConstantDecl *ECD = *Enumerators.begin();
93 return isOnlyFirstEnumeratorInitialized(Node) &&
94 isInitializedByLiteral(Enumerator: ECD) && ECD->getInitVal().isZero();
95}
96
97/// Excludes bitfields because enumerators initialized with the result of a
98/// bitwise operator on enumeration values or any other expr that is not a
99/// potentially negative integer literal.
100/// Enumerations where it is not directly clear if they are used with
101/// bitmask, evident when enumerators are only initialized with (potentially
102/// negative) integer literals, are ignored. This is also the case when all
103/// enumerators are powers of two (e.g., 0, 1, 2).
104AST_MATCHER(EnumDecl, hasSequentialInitialValues) {
105 const EnumDecl::enumerator_range Enumerators = Node.enumerators();
106 if (Enumerators.empty())
107 return false;
108 const EnumConstantDecl *const FirstEnumerator = *Node.enumerator_begin();
109 llvm::APSInt PrevValue = FirstEnumerator->getInitVal();
110 if (!isInitializedByLiteral(Enumerator: FirstEnumerator))
111 return false;
112 bool AllEnumeratorsArePowersOfTwo = true;
113 for (const EnumConstantDecl *Enumerator : llvm::drop_begin(RangeOrContainer: Enumerators)) {
114 const llvm::APSInt NewValue = Enumerator->getInitVal();
115 if (NewValue != ++PrevValue)
116 return false;
117 if (!isInitializedByLiteral(Enumerator))
118 return false;
119 PrevValue = NewValue;
120 AllEnumeratorsArePowersOfTwo &= NewValue.isPowerOf2();
121 }
122 return !AllEnumeratorsArePowersOfTwo;
123}
124
125std::string getName(const EnumDecl *Decl) {
126 if (!Decl->getDeclName())
127 return "<unnamed>";
128
129 return Decl->getQualifiedNameAsString();
130}
131
132} // namespace
133
134EnumInitialValueCheck::EnumInitialValueCheck(StringRef Name,
135 ClangTidyContext *Context)
136 : ClangTidyCheck(Name, Context),
137 AllowExplicitZeroFirstInitialValue(
138 Options.get(LocalName: "AllowExplicitZeroFirstInitialValue", Default: true)),
139 AllowExplicitSequentialInitialValues(
140 Options.get(LocalName: "AllowExplicitSequentialInitialValues", Default: true)) {}
141
142void EnumInitialValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
143 Options.store(Options&: Opts, LocalName: "AllowExplicitZeroFirstInitialValue",
144 Value: AllowExplicitZeroFirstInitialValue);
145 Options.store(Options&: Opts, LocalName: "AllowExplicitSequentialInitialValues",
146 Value: AllowExplicitSequentialInitialValues);
147}
148
149void EnumInitialValueCheck::registerMatchers(MatchFinder *Finder) {
150 Finder->addMatcher(NodeMatch: enumDecl(isDefinition(), unless(isMacro()),
151 unless(hasConsistentInitialValues()))
152 .bind(ID: "inconsistent"),
153 Action: this);
154 if (!AllowExplicitZeroFirstInitialValue)
155 Finder->addMatcher(
156 NodeMatch: enumDecl(isDefinition(), hasZeroInitialValueForFirstEnumerator())
157 .bind(ID: "zero_first"),
158 Action: this);
159 if (!AllowExplicitSequentialInitialValues)
160 Finder->addMatcher(NodeMatch: enumDecl(isDefinition(), unless(isMacro()),
161 hasSequentialInitialValues())
162 .bind(ID: "sequential"),
163 Action: this);
164}
165
166void EnumInitialValueCheck::check(const MatchFinder::MatchResult &Result) {
167 if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>(ID: "inconsistent")) {
168 DiagnosticBuilder Diag =
169 diag(
170 Enum->getBeginLoc(),
171 "initial values in enum '%0' are not consistent, consider explicit "
172 "initialization of all, none or only the first enumerator")
173 << getName(Decl: Enum);
174 for (const EnumConstantDecl *ECD : Enum->enumerators())
175 if (ECD->getInitExpr() == nullptr) {
176 const SourceLocation EndLoc = Lexer::getLocForEndOfToken(
177 Loc: ECD->getLocation(), Offset: 0, SM: *Result.SourceManager, LangOpts: getLangOpts());
178 if (EndLoc.isMacroID())
179 continue;
180 llvm::SmallString<8> Str{" = "};
181 ECD->getInitVal().toString(Str);
182 Diag << FixItHint::CreateInsertion(InsertionLoc: EndLoc, Code: Str);
183 }
184 return;
185 }
186
187 if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>(ID: "zero_first")) {
188 const EnumConstantDecl *ECD = *Enum->enumerator_begin();
189 const SourceLocation Loc = ECD->getLocation();
190 if (Loc.isInvalid() || Loc.isMacroID())
191 return;
192 DiagnosticBuilder Diag = diag(Loc, Description: "zero initial value for the first "
193 "enumerator in '%0' can be disregarded")
194 << getName(Decl: Enum);
195 cleanInitialValue(Diag, ECD, SM: *Result.SourceManager, LangOpts: getLangOpts());
196 return;
197 }
198 if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>(ID: "sequential")) {
199 DiagnosticBuilder Diag =
200 diag(Enum->getBeginLoc(),
201 "sequential initial value in '%0' can be ignored")
202 << getName(Decl: Enum);
203 for (const EnumConstantDecl *ECD : llvm::drop_begin(RangeOrContainer: Enum->enumerators()))
204 cleanInitialValue(Diag, ECD, SM: *Result.SourceManager, LangOpts: getLangOpts());
205 return;
206 }
207}
208
209} // namespace clang::tidy::readability
210

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

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