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 | |
19 | using namespace clang::ast_matchers; |
20 | |
21 | namespace clang::tidy::readability { |
22 | |
23 | static 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 | |
29 | static 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 | |
40 | static 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. |
48 | static 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 | |
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()) |
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 | |
76 | namespace { |
77 | |
78 | AST_MATCHER(EnumDecl, isMacro) { |
79 | SourceLocation Loc = Node.getBeginLoc(); |
80 | return Loc.isMacroID(); |
81 | } |
82 | |
83 | AST_MATCHER(EnumDecl, hasConsistentInitialValues) { |
84 | return isNoneEnumeratorsInitialized(Node) || |
85 | isOnlyFirstEnumeratorInitialized(Node) || |
86 | areAllEnumeratorsInitialized(Node); |
87 | } |
88 | |
89 | AST_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). |
105 | AST_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 | |
128 | EnumInitialValueCheck::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 | |
136 | void 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 | |
143 | void 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 | |
158 | void 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 | |