clang-tools 23.0.0git
UseStdNumbersCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
11#include "clang/AST/ASTContext.h"
12#include "clang/AST/Decl.h"
13#include "clang/AST/Expr.h"
14#include "clang/AST/Stmt.h"
15#include "clang/AST/Type.h"
16#include "clang/ASTMatchers/ASTMatchFinder.h"
17#include "clang/ASTMatchers/ASTMatchers.h"
18#include "clang/ASTMatchers/ASTMatchersInternal.h"
19#include "clang/ASTMatchers/ASTMatchersMacros.h"
20#include "clang/Basic/Diagnostic.h"
21#include "clang/Basic/LLVM.h"
22#include "clang/Basic/LangOptions.h"
23#include "clang/Basic/SourceLocation.h"
24#include "clang/Basic/SourceManager.h"
25#include "clang/Lex/Lexer.h"
26#include "llvm/ADT/STLExtras.h"
27#include "llvm/ADT/SmallVector.h"
28#include "llvm/ADT/StringRef.h"
29#include "llvm/Support/FormatVariadic.h"
30#include <array>
31#include <cmath>
32#include <cstdint>
33#include <cstdlib>
34#include <initializer_list>
35#include <string>
36#include <tuple>
37#include <utility>
38
39using namespace clang::ast_matchers;
40using clang::ast_matchers::internal::Matcher;
41
42namespace clang::tidy::modernize {
43
44namespace {
45
46AST_MATCHER_P2(FloatingLiteral, near, double, Value, double, DiffThreshold) {
47 return std::abs(Node.getValueAsApproximateDouble() - Value) < DiffThreshold;
48}
49
50AST_MATCHER_P(QualType, hasCanonicalTypeUnqualified, Matcher<QualType>,
51 InnerMatcher) {
52 return !Node.isNull() &&
53 InnerMatcher.matches(Node->getCanonicalTypeUnqualified(), Finder,
54 Builder);
55}
56
57AST_MATCHER(QualType, isArithmetic) {
58 return !Node.isNull() && Node->isArithmeticType();
59}
60AST_MATCHER(QualType, isFloating) {
61 return !Node.isNull() && Node->isFloatingType();
62}
63
64AST_MATCHER_P(Expr, anyOfExhaustive, std::vector<Matcher<Stmt>>, Exprs) {
65 bool FoundMatch = false;
66 for (const auto &InnerMatcher : Exprs) {
67 ast_matchers::internal::BoundNodesTreeBuilder Result = *Builder;
68 if (InnerMatcher.matches(Node, Finder, &Result)) {
69 *Builder = std::move(Result);
70 FoundMatch = true;
71 }
72 }
73 return FoundMatch;
74}
75
76// Using this struct to store the 'DiffThreshold' config value to create the
77// matchers without the need to pass 'DiffThreshold' into every matcher.
78// 'DiffThreshold' is needed in the 'near' matcher, which is used for matching
79// the literal of every constant and for formulas' subexpressions that look at
80// literals.
81struct MatchBuilder {
82 auto ignoreParenAndArithmeticCasting(const Matcher<Expr> &Matcher) const {
83 return expr(hasType(qualType(isArithmetic())), ignoringParenCasts(Matcher));
84 }
85
86 auto ignoreParenAndFloatingCasting(const Matcher<Expr> &Matcher) const {
87 return expr(hasType(qualType(isFloating())), ignoringParenCasts(Matcher));
88 }
89
90 auto matchMathCall(const StringRef FunctionName,
91 const Matcher<Expr> &ArgumentMatcher) const {
92 auto HasAnyPrecisionName = hasAnyName(
93 FunctionName, (FunctionName + "l").str(),
94 (FunctionName + "f").str()); // Support long double(l) and float(f).
95 return expr(ignoreParenAndFloatingCasting(
96 callExpr(callee(functionDecl(HasAnyPrecisionName,
97 hasParameter(0, hasType(isArithmetic())))),
98 hasArgument(0, ArgumentMatcher))));
99 }
100
101 auto matchSqrt(const Matcher<Expr> &ArgumentMatcher) const {
102 return matchMathCall("sqrt", ArgumentMatcher);
103 }
104
105 // Used for top-level matchers (i.e. the match that replaces Val with its
106 // constant).
107 //
108 // E.g. The matcher of `std::numbers::pi` uses this matcher to look for
109 // floatLiterals that have the value of pi.
110 //
111 // If the match is for a top-level match, we only care about the literal.
112 auto matchFloatLiteralNear(const StringRef Constant, const double Val) const {
113 return expr(ignoreParenAndFloatingCasting(
114 floatLiteral(near(Val, DiffThreshold)).bind(Constant)));
115 }
116
117 // Used for non-top-level matchers (i.e. matchers that are used as inner
118 // matchers for top-level matchers).
119 //
120 // E.g.: The matcher of `std::numbers::log2e` uses this matcher to check if
121 // `e` of `log2(e)` is declared constant and initialized with the value for
122 // eulers number.
123 //
124 // Here, we do care about literals and about DeclRefExprs to variable
125 // declarations that are constant and initialized with `Val`. This allows
126 // top-level matchers to see through declared constants for their inner
127 // matches like the `std::numbers::log2e` matcher.
128 auto matchFloatValueNear(const double Val) const {
129 const auto Float = floatLiteral(near(Val, DiffThreshold));
130
131 const auto Dref = declRefExpr(
132 to(varDecl(hasType(qualType(isConstQualified(), isFloating())),
133 hasInitializer(ignoreParenAndFloatingCasting(Float)))));
134 return expr(ignoreParenAndFloatingCasting(anyOf(Float, Dref)));
135 }
136
137 auto matchValue(const int64_t ValInt) const {
138 const auto Int =
139 expr(ignoreParenAndArithmeticCasting(integerLiteral(equals(ValInt))));
140 const auto Float = expr(ignoreParenAndFloatingCasting(
141 matchFloatValueNear(static_cast<double>(ValInt))));
142 const auto Dref = declRefExpr(to(varDecl(
143 hasType(qualType(isConstQualified(), isArithmetic())),
144 hasInitializer(expr(anyOf(ignoringImplicit(Int),
145 ignoreParenAndFloatingCasting(Float)))))));
146 return expr(anyOf(Int, Float, Dref));
147 }
148
149 auto match1Div(const Matcher<Expr> &Match) const {
150 return binaryOperator(hasOperatorName("/"), hasLHS(matchValue(1)),
151 hasRHS(Match));
152 }
153
154 auto matchEuler() const {
155 return expr(anyOf(matchFloatValueNear(llvm::numbers::e),
156 matchMathCall("exp", matchValue(1))));
157 }
158 auto matchEulerTopLevel() const {
159 return expr(anyOf(matchFloatLiteralNear("e_literal", llvm::numbers::e),
160 matchMathCall("exp", matchValue(1)).bind("e_pattern")))
161 .bind("e");
162 }
163
164 auto matchLog2Euler() const {
165 return expr(
166 anyOf(
167 matchFloatLiteralNear("log2e_literal", llvm::numbers::log2e),
168 matchMathCall("log2", matchEuler()).bind("log2e_pattern")))
169 .bind("log2e");
170 }
171
172 auto matchLog10Euler() const {
173 return expr(
174 anyOf(
175 matchFloatLiteralNear("log10e_literal",
176 llvm::numbers::log10e),
177 matchMathCall("log10", matchEuler()).bind("log10e_pattern")))
178 .bind("log10e");
179 }
180
181 auto matchPi() const { return matchFloatValueNear(llvm::numbers::pi); }
182 auto matchPiTopLevel() const {
183 return matchFloatLiteralNear("pi_literal", llvm::numbers::pi).bind("pi");
184 }
185
186 auto matchEgamma() const {
187 return matchFloatLiteralNear("egamma_literal", llvm::numbers::egamma)
188 .bind("egamma");
189 }
190
191 auto matchInvPi() const {
192 return expr(anyOf(matchFloatLiteralNear("inv_pi_literal",
193 llvm::numbers::inv_pi),
194 match1Div(matchPi()).bind("inv_pi_pattern")))
195 .bind("inv_pi");
196 }
197
198 auto matchInvSqrtPi() const {
199 return expr(anyOf(
200 matchFloatLiteralNear("inv_sqrtpi_literal",
201 llvm::numbers::inv_sqrtpi),
202 match1Div(matchSqrt(matchPi())).bind("inv_sqrtpi_pattern")))
203 .bind("inv_sqrtpi");
204 }
205
206 auto matchLn2() const {
207 return expr(anyOf(matchFloatLiteralNear("ln2_literal", llvm::numbers::ln2),
208 matchMathCall("log", matchValue(2)).bind("ln2_pattern")))
209 .bind("ln2");
210 }
211
212 auto machterLn10() const {
213 return expr(
214 anyOf(matchFloatLiteralNear("ln10_literal", llvm::numbers::ln10),
215 matchMathCall("log", matchValue(10)).bind("ln10_pattern")))
216 .bind("ln10");
217 }
218
219 auto matchSqrt2() const {
220 return expr(anyOf(matchFloatLiteralNear("sqrt2_literal",
221 llvm::numbers::sqrt2),
222 matchSqrt(matchValue(2)).bind("sqrt2_pattern")))
223 .bind("sqrt2");
224 }
225
226 auto matchSqrt3() const {
227 return expr(anyOf(matchFloatLiteralNear("sqrt3_literal",
228 llvm::numbers::sqrt3),
229 matchSqrt(matchValue(3)).bind("sqrt3_pattern")))
230 .bind("sqrt3");
231 }
232
233 auto matchInvSqrt3() const {
234 return expr(anyOf(matchFloatLiteralNear("inv_sqrt3_literal",
235 llvm::numbers::inv_sqrt3),
236 match1Div(matchSqrt(matchValue(3)))
237 .bind("inv_sqrt3_pattern")))
238 .bind("inv_sqrt3");
239 }
240
241 auto matchPhi() const {
242 const auto PhiFormula = binaryOperator(
243 hasOperatorName("/"),
244 hasLHS(binaryOperator(
245 hasOperatorName("+"), hasEitherOperand(matchValue(1)),
246 hasEitherOperand(matchMathCall("sqrt", matchValue(5))))),
247 hasRHS(matchValue(2)));
248 return expr(anyOf(PhiFormula.bind("phi_pattern"),
249 matchFloatLiteralNear("phi_literal", llvm::numbers::phi)))
250 .bind("phi");
251 }
252
253 double DiffThreshold;
254};
255
256} // namespace
257
258static std::string getCode(const StringRef Constant, const bool IsFloat,
259 const bool IsLongDouble) {
260 if (IsFloat)
261 return ("std::numbers::" + Constant + "_v<float>").str();
262 if (IsLongDouble)
263 return ("std::numbers::" + Constant + "_v<long double>").str();
264 return ("std::numbers::" + Constant).str();
265}
266
267static bool isRangeOfCompleteMacro(const SourceRange &Range,
268 const SourceManager &SM,
269 const LangOptions &LO) {
270 if (!Range.getBegin().isMacroID())
271 return false;
272 if (!Lexer::isAtStartOfMacroExpansion(Range.getBegin(), SM, LO))
273 return false;
274
275 if (!Range.getEnd().isMacroID())
276 return false;
277
278 if (!Lexer::isAtEndOfMacroExpansion(Range.getEnd(), SM, LO))
279 return false;
280
281 return true;
282}
283
285 ClangTidyContext *const Context)
286 : ClangTidyCheck(Name, Context),
287 IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
288 utils::IncludeSorter::IS_LLVM),
289 areDiagsSelfContained()),
290 DiffThresholdString{Options.get("DiffThreshold", "0.001")} {
291 if (DiffThresholdString.getAsDouble(DiffThreshold)) {
292 configurationDiag(
293 "Invalid DiffThreshold config value: '%0', expected a double")
294 << DiffThresholdString;
295 DiffThreshold = 0.001;
296 }
297}
298
299void UseStdNumbersCheck::registerMatchers(MatchFinder *const Finder) {
300 const auto Matches = MatchBuilder{DiffThreshold};
301 const std::vector<Matcher<Stmt>> ConstantMatchers = {
302 Matches.matchLog2Euler(), Matches.matchLog10Euler(),
303 Matches.matchEulerTopLevel(), Matches.matchEgamma(),
304 Matches.matchInvSqrtPi(), Matches.matchInvPi(),
305 Matches.matchPiTopLevel(), Matches.matchLn2(),
306 Matches.machterLn10(), Matches.matchSqrt2(),
307 Matches.matchInvSqrt3(), Matches.matchSqrt3(),
308 Matches.matchPhi(),
309 };
310
311 Finder->addMatcher(
312 expr(
313 anyOfExhaustive(ConstantMatchers),
314 unless(hasParent(explicitCastExpr(hasDestinationType(isFloating())))),
315 hasType(qualType(hasCanonicalTypeUnqualified(
316 anyOf(qualType(asString("float")).bind("float"),
317 qualType(asString("double")),
318 qualType(asString("long double")).bind("long double")))))),
319 this);
320}
321
322void UseStdNumbersCheck::check(const MatchFinder::MatchResult &Result) {
323 /*
324 List of all math constants in the `<numbers>` header
325 + e
326 + log2e
327 + log10e
328 + pi
329 + inv_pi
330 + inv_sqrtpi
331 + ln2
332 + ln10
333 + sqrt2
334 + sqrt3
335 + inv_sqrt3
336 + egamma
337 + phi
338 */
339
340 // The ordering determines what constants are looked at first.
341 // E.g. look at 'inv_sqrt3' before 'sqrt3' to be able to replace the larger
342 // expression
343 constexpr auto Constants = std::array<std::pair<StringRef, double>, 13>{
344 std::pair{StringRef{"log2e"}, llvm::numbers::log2e},
345 std::pair{StringRef{"log10e"}, llvm::numbers::log10e},
346 std::pair{StringRef{"e"}, llvm::numbers::e},
347 std::pair{StringRef{"egamma"}, llvm::numbers::egamma},
348 std::pair{StringRef{"inv_sqrtpi"}, llvm::numbers::inv_sqrtpi},
349 std::pair{StringRef{"inv_pi"}, llvm::numbers::inv_pi},
350 std::pair{StringRef{"pi"}, llvm::numbers::pi},
351 std::pair{StringRef{"ln2"}, llvm::numbers::ln2},
352 std::pair{StringRef{"ln10"}, llvm::numbers::ln10},
353 std::pair{StringRef{"sqrt2"}, llvm::numbers::sqrt2},
354 std::pair{StringRef{"inv_sqrt3"}, llvm::numbers::inv_sqrt3},
355 std::pair{StringRef{"sqrt3"}, llvm::numbers::sqrt3},
356 std::pair{StringRef{"phi"}, llvm::numbers::phi},
357 };
358
359 auto MatchedLiterals =
361
362 const auto &SM = *Result.SourceManager;
363 const auto &LO = Result.Context->getLangOpts();
364
365 const auto IsFloat = Result.Nodes.getNodeAs<QualType>("float") != nullptr;
366 const auto IsLongDouble =
367 Result.Nodes.getNodeAs<QualType>("long double") != nullptr;
368
369 for (const auto &[ConstantName, ConstantValue] : Constants) {
370 const auto *const Match = Result.Nodes.getNodeAs<Expr>(ConstantName);
371 if (Match == nullptr)
372 continue;
373
374 const auto Range = Match->getSourceRange();
375
376 const auto IsMacro = Range.getBegin().isMacroID();
377
378 // We do not want to emit a diagnostic when we are matching a macro, but the
379 // match inside of the macro does not cover the whole macro.
380 if (IsMacro && !isRangeOfCompleteMacro(Range, SM, LO))
381 continue;
382
383 if (const auto PatternBindString = (ConstantName + "_pattern").str();
384 Result.Nodes.getNodeAs<Expr>(PatternBindString) != nullptr) {
385 const auto Code = getCode(ConstantName, IsFloat, IsLongDouble);
386 diag(Range.getBegin(), "prefer '%0' to this %select{formula|macro}1")
387 << Code << IsMacro << FixItHint::CreateReplacement(Range, Code);
388 return;
389 }
390
391 const auto LiteralBindString = (ConstantName + "_literal").str();
392 if (const auto *const Literal =
393 Result.Nodes.getNodeAs<FloatingLiteral>(LiteralBindString)) {
394 MatchedLiterals.emplace_back(
395 ConstantName,
396 std::abs(Literal->getValueAsApproximateDouble() - ConstantValue),
397 Match);
398 }
399 }
400
401 // We may have had no matches with literals, but a match with a pattern that
402 // was a part of a macro which was therefore skipped.
403 if (MatchedLiterals.empty())
404 return;
405
406 llvm::sort(MatchedLiterals, llvm::less_second());
407
408 const auto &[Constant, Diff, Node] = MatchedLiterals.front();
409
410 const auto Range = Node->getSourceRange();
411 const auto IsMacro = Range.getBegin().isMacroID();
412
413 // We do not want to emit a diagnostic when we are matching a macro, but the
414 // match inside of the macro does not cover the whole macro.
415 if (IsMacro && !isRangeOfCompleteMacro(Range, SM, LO))
416 return;
417
418 const auto Code = getCode(Constant, IsFloat, IsLongDouble);
419 diag(Range.getBegin(),
420 "prefer '%0' to this %select{literal|macro}1, differs by '%2'")
421 << Code << IsMacro << llvm::formatv("{0:e2}", Diff).str()
422 << FixItHint::CreateReplacement(Range, Code)
423 << IncludeInserter.createIncludeInsertion(
424 Result.SourceManager->getFileID(Range.getBegin()), "<numbers>");
425}
426
428 const SourceManager &SM, Preprocessor *const PP,
429 Preprocessor *const ModuleExpanderPP) {
430 IncludeInserter.registerPreprocessor(PP);
431}
432
434 Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
435 Options.store(Opts, "DiffThreshold", DiffThresholdString);
436}
437} // namespace clang::tidy::modernize
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
UseStdNumbersCheck(StringRef Name, ClangTidyContext *Context)
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
AST_MATCHER_P(Stmt, isStatementIdenticalToBoundNode, std::string, ID)
AST_MATCHER(BinaryOperator, isRelationalOperator)
static std::string getCode(const StringRef Constant, const bool IsFloat, const bool IsLongDouble)
static bool isRangeOfCompleteMacro(const SourceRange &Range, const SourceManager &SM, const LangOptions &LO)
llvm::StringMap< ClangTidyValue > OptionMap