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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of clang-tools-extra/clang-tidy/modernize/UseStdNumbersCheck.cpp