xref: /llvm-project/clang-tools-extra/clang-tidy/readability/MagicNumbersCheck.cpp (revision 4b5366c9512aa273a5272af1d833961e1ed156e7)
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 
isUsedToInitializeAConstant(const MatchFinder::MatchResult & Result,const DynTypedNode & Node)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(Result.Context->getParents(Node),
42                       [&Result](const DynTypedNode &Parent) {
43                         return isUsedToInitializeAConstant(Result, Parent);
44                       });
45 }
46 
isUsedToDefineATypeAlias(const MatchFinder::MatchResult & Result,const DynTypedNode & Node)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(Result.Context->getParents(Node),
54                       [&Result](const DynTypedNode &Parent) {
55                         return isUsedToDefineATypeAlias(Result, Parent);
56                       });
57 }
58 
isUsedToDefineABitField(const MatchFinder::MatchResult & Result,const DynTypedNode & Node)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(Result.Context->getParents(Node),
66                       [&Result](const DynTypedNode &Parent) {
67                         return isUsedToDefineABitField(Result, 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 
MagicNumbersCheck(StringRef Name,ClangTidyContext * Context)76 MagicNumbersCheck::MagicNumbersCheck(StringRef Name, ClangTidyContext *Context)
77     : ClangTidyCheck(Name, Context),
78       IgnoreAllFloatingPointValues(
79           Options.get("IgnoreAllFloatingPointValues", false)),
80       IgnoreBitFieldsWidths(Options.get("IgnoreBitFieldsWidths", true)),
81       IgnorePowersOf2IntegerValues(
82           Options.get("IgnorePowersOf2IntegerValues", false)),
83       IgnoreTypeAliases(Options.get("IgnoreTypeAliases", false)),
84       IgnoreUserDefinedLiterals(
85           Options.get("IgnoreUserDefinedLiterals", false)),
86       RawIgnoredIntegerValues(
87           Options.get("IgnoredIntegerValues", DefaultIgnoredIntegerValues)),
88       RawIgnoredFloatingPointValues(Options.get(
89           "IgnoredFloatingPointValues", DefaultIgnoredFloatingPointValues)) {
90   // Process the set of ignored integer values.
91   const std::vector<StringRef> IgnoredIntegerValuesInput =
92       utils::options::parseStringList(RawIgnoredIntegerValues);
93   IgnoredIntegerValues.resize(IgnoredIntegerValuesInput.size());
94   llvm::transform(IgnoredIntegerValuesInput, IgnoredIntegerValues.begin(),
95                   [](StringRef Value) {
96                     int64_t Res = 0;
97                     Value.getAsInteger(10, Res);
98                     return Res;
99                   });
100   llvm::sort(IgnoredIntegerValues);
101 
102   if (!IgnoreAllFloatingPointValues) {
103     // Process the set of ignored floating point values.
104     const std::vector<StringRef> IgnoredFloatingPointValuesInput =
105         utils::options::parseStringList(RawIgnoredFloatingPointValues);
106     IgnoredFloatingPointValues.reserve(IgnoredFloatingPointValuesInput.size());
107     IgnoredDoublePointValues.reserve(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(StatusOrErr.takeError());
114       IgnoredFloatingPointValues.push_back(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(StatusOrErr.takeError());
121       IgnoredDoublePointValues.push_back(DoubleValue.convertToDouble());
122     }
123     llvm::sort(IgnoredFloatingPointValues);
124     llvm::sort(IgnoredDoublePointValues);
125   }
126 }
127 
storeOptions(ClangTidyOptions::OptionMap & Opts)128 void MagicNumbersCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
129   Options.store(Opts, "IgnoreAllFloatingPointValues",
130                 IgnoreAllFloatingPointValues);
131   Options.store(Opts, "IgnoreBitFieldsWidths", IgnoreBitFieldsWidths);
132   Options.store(Opts, "IgnorePowersOf2IntegerValues",
133                 IgnorePowersOf2IntegerValues);
134   Options.store(Opts, "IgnoreTypeAliases", IgnoreTypeAliases);
135   Options.store(Opts, "IgnoreUserDefinedLiterals", IgnoreUserDefinedLiterals);
136   Options.store(Opts, "IgnoredIntegerValues", RawIgnoredIntegerValues);
137   Options.store(Opts, "IgnoredFloatingPointValues",
138                 RawIgnoredFloatingPointValues);
139 }
140 
registerMatchers(MatchFinder * Finder)141 void MagicNumbersCheck::registerMatchers(MatchFinder *Finder) {
142   Finder->addMatcher(integerLiteral().bind("integer"), this);
143   if (!IgnoreAllFloatingPointValues)
144     Finder->addMatcher(floatLiteral().bind("float"), this);
145 }
146 
check(const MatchFinder::MatchResult & Result)147 void MagicNumbersCheck::check(const MatchFinder::MatchResult &Result) {
148 
149   TraversalKindScope RAII(*Result.Context, TK_AsIs);
150 
151   checkBoundMatch<IntegerLiteral>(Result, "integer");
152   checkBoundMatch<FloatingLiteral>(Result, "float");
153 }
154 
isConstant(const MatchFinder::MatchResult & Result,const Expr & ExprResult) const155 bool MagicNumbersCheck::isConstant(const MatchFinder::MatchResult &Result,
156                                    const Expr &ExprResult) const {
157   return llvm::any_of(
158       Result.Context->getParents(ExprResult),
159       [this, &Result](const DynTypedNode &Parent) {
160         if (isUsedToInitializeAConstant(Result, Parent))
161           return true;
162 
163         if (IgnoreTypeAliases && isUsedToDefineATypeAlias(Result, 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                 Result.Context->getParents(Parent),
171                 [](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 
isIgnoredValue(const IntegerLiteral * Literal) const193 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(IgnoredIntegerValues.begin(),
206                             IgnoredIntegerValues.end(), Value);
207 }
208 
isIgnoredValue(const FloatingLiteral * Literal) const209 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(IgnoredFloatingPointValues.begin(),
217                               IgnoredFloatingPointValues.end(), Value);
218   }
219 
220   if (&FloatValue.getSemantics() == &llvm::APFloat::IEEEdouble()) {
221     const double Value = FloatValue.convertToDouble();
222     return std::binary_search(IgnoredDoublePointValues.begin(),
223                               IgnoredDoublePointValues.end(), Value);
224   }
225 
226   return false;
227 }
228 
isSyntheticValue(const SourceManager * SourceManager,const IntegerLiteral * Literal) const229 bool MagicNumbersCheck::isSyntheticValue(const SourceManager *SourceManager,
230                                          const IntegerLiteral *Literal) const {
231   const std::pair<FileID, unsigned> FileOffset =
232       SourceManager->getDecomposedLoc(Literal->getLocation());
233   if (FileOffset.first.isInvalid())
234     return false;
235 
236   const StringRef BufferIdentifier =
237       SourceManager->getBufferOrFake(FileOffset.first).getBufferIdentifier();
238 
239   return BufferIdentifier.empty();
240 }
241 
isBitFieldWidth(const clang::ast_matchers::MatchFinder::MatchResult & Result,const IntegerLiteral & Literal) const242 bool MagicNumbersCheck::isBitFieldWidth(
243     const clang::ast_matchers::MatchFinder::MatchResult &Result,
244     const IntegerLiteral &Literal) const {
245   return IgnoreBitFieldsWidths &&
246          llvm::any_of(Result.Context->getParents(Literal),
247                       [&Result](const DynTypedNode &Parent) {
248                         return isUsedToDefineABitField(Result, Parent);
249                       });
250 }
251 
isUserDefinedLiteral(const clang::ast_matchers::MatchFinder::MatchResult & Result,const clang::Expr & Literal) const252 bool MagicNumbersCheck::isUserDefinedLiteral(
253     const clang::ast_matchers::MatchFinder::MatchResult &Result,
254     const clang::Expr &Literal) const {
255   DynTypedNodeList Parents = Result.Context->getParents(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