clang-tools 19.0.0git
EnumInitialValueCheck.cpp
Go to the documentation of this file.
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
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
23static bool isNoneEnumeratorsInitialized(const EnumDecl &Node) {
24 return llvm::all_of(Node.enumerators(), [](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(Node.enumerators(), [](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(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 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(EqualLoc)
72 << FixItHint::CreateRemoval(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) ||
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();
95 isInitializedByLiteral(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(FirstEnumerator))
112 return false;
113 bool AllEnumeratorsArePowersOfTwo = true;
114 for (const EnumConstantDecl *Enumerator : llvm::drop_begin(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
129 ClangTidyContext *Context)
130 : ClangTidyCheck(Name, Context),
131 AllowExplicitZeroFirstInitialValue(
132 Options.get("AllowExplicitZeroFirstInitialValue", true)),
133 AllowExplicitSequentialInitialValues(
134 Options.get("AllowExplicitSequentialInitialValues", true)) {}
135
137 Options.store(Opts, "AllowExplicitZeroFirstInitialValue",
138 AllowExplicitZeroFirstInitialValue);
139 Options.store(Opts, "AllowExplicitSequentialInitialValues",
140 AllowExplicitSequentialInitialValues);
141}
142
144 Finder->addMatcher(
145 enumDecl(unless(isMacro()), unless(hasConsistentInitialValues()))
146 .bind("inconsistent"),
147 this);
148 if (!AllowExplicitZeroFirstInitialValue)
149 Finder->addMatcher(
150 enumDecl(hasZeroInitialValueForFirstEnumerator()).bind("zero_first"),
151 this);
152 if (!AllowExplicitSequentialInitialValues)
153 Finder->addMatcher(enumDecl(unless(isMacro()), hasSequentialInitialValues())
154 .bind("sequential"),
155 this);
156}
157
158void EnumInitialValueCheck::check(const MatchFinder::MatchResult &Result) {
159 if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("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 ECD->getLocation(), 0, *Result.SourceManager, getLangOpts());
169 if (EndLoc.isMacroID())
170 continue;
171 llvm::SmallString<8> Str{" = "};
172 ECD->getInitVal().toString(Str);
173 Diag << FixItHint::CreateInsertion(EndLoc, Str);
174 }
175 return;
176 }
177
178 if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("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, "zero initial value for the first "
184 "enumerator in %0 can be disregarded")
185 << Enum;
186 cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts());
187 return;
188 }
189 if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("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(Enum->enumerators()))
195 cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts());
196 return;
197 }
198}
199
200} // namespace clang::tidy::readability
llvm::SmallString< 256U > Name
SourceLocation Loc
::clang::DynTypedNode Node
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
const LangOptions & getLangOpts() const
Returns the language options from the context.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
EnumInitialValueCheck(StringRef Name, ClangTidyContext *Context)
static bool isInitializedByLiteral(const EnumConstantDecl *Enumerator)
Check if Enumerator is initialized with a (potentially negated) IntegerLiteral.
static bool isNoneEnumeratorsInitialized(const EnumDecl &Node)
static bool areAllEnumeratorsInitialized(const EnumDecl &Node)
static void cleanInitialValue(DiagnosticBuilder &Diag, const EnumConstantDecl *ECD, const SourceManager &SM, const LangOptions &LangOpts)
static bool isOnlyFirstEnumeratorInitialized(const EnumDecl &Node)
std::optional< Token > findNextTokenSkippingComments(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts)
Definition: LexerUtils.cpp:111
llvm::StringMap< ClangTidyValue > OptionMap