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