1 | //===--- MagicNumbersCheck.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 | // A checker for magic numbers: integer or floating point literals embedded |
10 | // in the code, outside the definition of a constant or an enumeration. |
11 | // |
12 | //===----------------------------------------------------------------------===// |
13 | |
14 | #include "MagicNumbersCheck.h" |
15 | #include "../utils/OptionsUtils.h" |
16 | #include "clang/AST/ASTContext.h" |
17 | #include "clang/AST/ASTTypeTraits.h" |
18 | #include "clang/AST/Type.h" |
19 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
20 | #include "llvm/ADT/STLExtras.h" |
21 | #include <algorithm> |
22 | |
23 | using namespace clang::ast_matchers; |
24 | |
25 | namespace clang { |
26 | |
27 | static bool isUsedToInitializeAConstant(const MatchFinder::MatchResult &Result, |
28 | const DynTypedNode &Node) { |
29 | |
30 | const auto *AsDecl = Node.get<DeclaratorDecl>(); |
31 | if (AsDecl) { |
32 | if (AsDecl->getType().isConstQualified()) |
33 | return true; |
34 | |
35 | return AsDecl->isImplicit(); |
36 | } |
37 | |
38 | if (Node.get<EnumConstantDecl>()) |
39 | return true; |
40 | |
41 | return llvm::any_of(Range: Result.Context->getParents(Node), |
42 | P: [&Result](const DynTypedNode &Parent) { |
43 | return isUsedToInitializeAConstant(Result, Node: Parent); |
44 | }); |
45 | } |
46 | |
47 | static bool isUsedToDefineATypeAlias(const MatchFinder::MatchResult &Result, |
48 | const DynTypedNode &Node) { |
49 | |
50 | if (Node.get<TypeAliasDecl>() || Node.get<TypedefNameDecl>()) |
51 | return true; |
52 | |
53 | return llvm::any_of(Range: Result.Context->getParents(Node), |
54 | P: [&Result](const DynTypedNode &Parent) { |
55 | return isUsedToDefineATypeAlias(Result, Node: Parent); |
56 | }); |
57 | } |
58 | |
59 | static bool isUsedToDefineABitField(const MatchFinder::MatchResult &Result, |
60 | const DynTypedNode &Node) { |
61 | const auto *AsFieldDecl = Node.get<FieldDecl>(); |
62 | if (AsFieldDecl && AsFieldDecl->isBitField()) |
63 | return true; |
64 | |
65 | return llvm::any_of(Range: Result.Context->getParents(Node), |
66 | P: [&Result](const DynTypedNode &Parent) { |
67 | return isUsedToDefineABitField(Result, Node: Parent); |
68 | }); |
69 | } |
70 | |
71 | namespace tidy::readability { |
72 | |
73 | const char DefaultIgnoredIntegerValues[] = "1;2;3;4;" ; |
74 | const char DefaultIgnoredFloatingPointValues[] = "1.0;100.0;" ; |
75 | |
76 | MagicNumbersCheck::MagicNumbersCheck(StringRef Name, ClangTidyContext *Context) |
77 | : ClangTidyCheck(Name, Context), |
78 | IgnoreAllFloatingPointValues( |
79 | Options.get(LocalName: "IgnoreAllFloatingPointValues" , Default: false)), |
80 | IgnoreBitFieldsWidths(Options.get(LocalName: "IgnoreBitFieldsWidths" , Default: true)), |
81 | IgnorePowersOf2IntegerValues( |
82 | Options.get(LocalName: "IgnorePowersOf2IntegerValues" , Default: false)), |
83 | IgnoreTypeAliases(Options.get(LocalName: "IgnoreTypeAliases" , Default: false)), |
84 | IgnoreUserDefinedLiterals( |
85 | Options.get(LocalName: "IgnoreUserDefinedLiterals" , Default: false)), |
86 | RawIgnoredIntegerValues( |
87 | Options.get(LocalName: "IgnoredIntegerValues" , Default: DefaultIgnoredIntegerValues)), |
88 | RawIgnoredFloatingPointValues(Options.get( |
89 | LocalName: "IgnoredFloatingPointValues" , Default: DefaultIgnoredFloatingPointValues)) { |
90 | // Process the set of ignored integer values. |
91 | const std::vector<StringRef> IgnoredIntegerValuesInput = |
92 | utils::options::parseStringList(Option: RawIgnoredIntegerValues); |
93 | IgnoredIntegerValues.resize(N: IgnoredIntegerValuesInput.size()); |
94 | llvm::transform(Range: IgnoredIntegerValuesInput, d_first: IgnoredIntegerValues.begin(), |
95 | F: [](StringRef Value) { |
96 | int64_t Res = 0; |
97 | Value.getAsInteger(Radix: 10, Result&: Res); |
98 | return Res; |
99 | }); |
100 | llvm::sort(C&: IgnoredIntegerValues); |
101 | |
102 | if (!IgnoreAllFloatingPointValues) { |
103 | // Process the set of ignored floating point values. |
104 | const std::vector<StringRef> IgnoredFloatingPointValuesInput = |
105 | utils::options::parseStringList(Option: RawIgnoredFloatingPointValues); |
106 | IgnoredFloatingPointValues.reserve(N: IgnoredFloatingPointValuesInput.size()); |
107 | IgnoredDoublePointValues.reserve(N: IgnoredFloatingPointValuesInput.size()); |
108 | for (const auto &InputValue : IgnoredFloatingPointValuesInput) { |
109 | llvm::APFloat FloatValue(llvm::APFloat::IEEEsingle()); |
110 | auto StatusOrErr = |
111 | FloatValue.convertFromString(InputValue, DefaultRoundingMode); |
112 | assert(StatusOrErr && "Invalid floating point representation" ); |
113 | consumeError(Err: StatusOrErr.takeError()); |
114 | IgnoredFloatingPointValues.push_back(Elt: FloatValue.convertToFloat()); |
115 | |
116 | llvm::APFloat DoubleValue(llvm::APFloat::IEEEdouble()); |
117 | StatusOrErr = |
118 | DoubleValue.convertFromString(InputValue, DefaultRoundingMode); |
119 | assert(StatusOrErr && "Invalid floating point representation" ); |
120 | consumeError(Err: StatusOrErr.takeError()); |
121 | IgnoredDoublePointValues.push_back(Elt: DoubleValue.convertToDouble()); |
122 | } |
123 | llvm::sort(C&: IgnoredFloatingPointValues); |
124 | llvm::sort(C&: IgnoredDoublePointValues); |
125 | } |
126 | } |
127 | |
128 | void MagicNumbersCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
129 | Options.store(Options&: Opts, LocalName: "IgnoreAllFloatingPointValues" , |
130 | Value: IgnoreAllFloatingPointValues); |
131 | Options.store(Options&: Opts, LocalName: "IgnoreBitFieldsWidths" , Value: IgnoreBitFieldsWidths); |
132 | Options.store(Options&: Opts, LocalName: "IgnorePowersOf2IntegerValues" , |
133 | Value: IgnorePowersOf2IntegerValues); |
134 | Options.store(Options&: Opts, LocalName: "IgnoreTypeAliases" , Value: IgnoreTypeAliases); |
135 | Options.store(Options&: Opts, LocalName: "IgnoreUserDefinedLiterals" , Value: IgnoreUserDefinedLiterals); |
136 | Options.store(Options&: Opts, LocalName: "IgnoredIntegerValues" , Value: RawIgnoredIntegerValues); |
137 | Options.store(Options&: Opts, LocalName: "IgnoredFloatingPointValues" , |
138 | Value: RawIgnoredFloatingPointValues); |
139 | } |
140 | |
141 | void MagicNumbersCheck::registerMatchers(MatchFinder *Finder) { |
142 | Finder->addMatcher(NodeMatch: integerLiteral().bind(ID: "integer" ), Action: this); |
143 | if (!IgnoreAllFloatingPointValues) |
144 | Finder->addMatcher(NodeMatch: floatLiteral().bind(ID: "float" ), Action: this); |
145 | } |
146 | |
147 | void MagicNumbersCheck::check(const MatchFinder::MatchResult &Result) { |
148 | |
149 | TraversalKindScope RAII(*Result.Context, TK_AsIs); |
150 | |
151 | checkBoundMatch<IntegerLiteral>(Result, BoundName: "integer" ); |
152 | checkBoundMatch<FloatingLiteral>(Result, BoundName: "float" ); |
153 | } |
154 | |
155 | bool MagicNumbersCheck::isConstant(const MatchFinder::MatchResult &Result, |
156 | const Expr &ExprResult) const { |
157 | return llvm::any_of( |
158 | Range: Result.Context->getParents(Node: ExprResult), |
159 | P: [this, &Result](const DynTypedNode &Parent) { |
160 | if (isUsedToInitializeAConstant(Result, Node: Parent)) |
161 | return true; |
162 | |
163 | if (IgnoreTypeAliases && isUsedToDefineATypeAlias(Result, Node: Parent)) |
164 | return true; |
165 | |
166 | // Ignore this instance, because this matches an |
167 | // expanded class enumeration value. |
168 | if (Parent.get<CStyleCastExpr>() && |
169 | llvm::any_of( |
170 | Range: Result.Context->getParents(Node: Parent), |
171 | P: [](const DynTypedNode &GrandParent) { |
172 | return GrandParent.get<SubstNonTypeTemplateParmExpr>() != |
173 | nullptr; |
174 | })) |
175 | return true; |
176 | |
177 | // Ignore this instance, because this match reports the |
178 | // location where the template is defined, not where it |
179 | // is instantiated. |
180 | if (Parent.get<SubstNonTypeTemplateParmExpr>()) |
181 | return true; |
182 | |
183 | // Don't warn on string user defined literals: |
184 | // std::string s = "Hello World"s; |
185 | if (const auto *UDL = Parent.get<UserDefinedLiteral>()) |
186 | if (UDL->getLiteralOperatorKind() == UserDefinedLiteral::LOK_String) |
187 | return true; |
188 | |
189 | return false; |
190 | }); |
191 | } |
192 | |
193 | bool MagicNumbersCheck::isIgnoredValue(const IntegerLiteral *Literal) const { |
194 | if (Literal->getType()->isBitIntType()) { |
195 | return true; |
196 | } |
197 | const llvm::APInt IntValue = Literal->getValue(); |
198 | const int64_t Value = IntValue.getZExtValue(); |
199 | if (Value == 0) |
200 | return true; |
201 | |
202 | if (IgnorePowersOf2IntegerValues && IntValue.isPowerOf2()) |
203 | return true; |
204 | |
205 | return std::binary_search(first: IgnoredIntegerValues.begin(), |
206 | last: IgnoredIntegerValues.end(), val: Value); |
207 | } |
208 | |
209 | bool MagicNumbersCheck::isIgnoredValue(const FloatingLiteral *Literal) const { |
210 | const llvm::APFloat FloatValue = Literal->getValue(); |
211 | if (FloatValue.isZero()) |
212 | return true; |
213 | |
214 | if (&FloatValue.getSemantics() == &llvm::APFloat::IEEEsingle()) { |
215 | const float Value = FloatValue.convertToFloat(); |
216 | return std::binary_search(first: IgnoredFloatingPointValues.begin(), |
217 | last: IgnoredFloatingPointValues.end(), val: Value); |
218 | } |
219 | |
220 | if (&FloatValue.getSemantics() == &llvm::APFloat::IEEEdouble()) { |
221 | const double Value = FloatValue.convertToDouble(); |
222 | return std::binary_search(first: IgnoredDoublePointValues.begin(), |
223 | last: IgnoredDoublePointValues.end(), val: Value); |
224 | } |
225 | |
226 | return false; |
227 | } |
228 | |
229 | bool MagicNumbersCheck::isSyntheticValue(const SourceManager *SourceManager, |
230 | const IntegerLiteral *Literal) const { |
231 | const std::pair<FileID, unsigned> FileOffset = |
232 | SourceManager->getDecomposedLoc(Loc: Literal->getLocation()); |
233 | if (FileOffset.first.isInvalid()) |
234 | return false; |
235 | |
236 | const StringRef BufferIdentifier = |
237 | SourceManager->getBufferOrFake(FID: FileOffset.first).getBufferIdentifier(); |
238 | |
239 | return BufferIdentifier.empty(); |
240 | } |
241 | |
242 | bool MagicNumbersCheck::isBitFieldWidth( |
243 | const clang::ast_matchers::MatchFinder::MatchResult &Result, |
244 | const IntegerLiteral &Literal) const { |
245 | return IgnoreBitFieldsWidths && |
246 | llvm::any_of(Range: Result.Context->getParents(Node: Literal), |
247 | P: [&Result](const DynTypedNode &Parent) { |
248 | return isUsedToDefineABitField(Result, Node: Parent); |
249 | }); |
250 | } |
251 | |
252 | bool MagicNumbersCheck::isUserDefinedLiteral( |
253 | const clang::ast_matchers::MatchFinder::MatchResult &Result, |
254 | const clang::Expr &Literal) const { |
255 | DynTypedNodeList Parents = Result.Context->getParents(Node: Literal); |
256 | if (Parents.empty()) |
257 | return false; |
258 | return Parents[0].get<UserDefinedLiteral>() != nullptr; |
259 | } |
260 | |
261 | } // namespace tidy::readability |
262 | } // namespace clang |
263 | |