brintos

brintos / llvm-project-archived public Read only

0
0
Text · 16.2 KiB · 0315728 Raw
452 lines · cpp
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_exception6//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(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 the78// matchers without the need to pass 'DiffThreshold' into every matcher.79// 'DiffThreshold' is needed in the 'near' matcher, which is used for matching80// the literal of every constant and for formulas' subexpressions that look at81// literals.82struct MatchBuilder {83  auto84  ignoreParenAndArithmeticCasting(const Matcher<clang::Expr> &Matcher) const {85    return expr(hasType(qualType(isArithmetic())), ignoringParenCasts(Matcher));86  }87 88  auto89  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 its109  // constant).110  //111  // E.g. The matcher of `std::numbers::pi` uses this matcher to look for112  // 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 inner121  // matchers for top-level matchers).122  //123  // E.g.: The matcher of `std::numbers::log2e` uses this matcher to check if124  // `e` of `log2(e)` is declared constant and initialized with the value for125  // eulers number.126  //127  // Here, we do care about literals and about DeclRefExprs to variable128  // declarations that are constant and initialized with `Val`. This allows129  // top-level matchers to see through declared constants for their inner130  // 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} // namespace260 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 {294UseStdNumbersCheck::UseStdNumbersCheck(const StringRef Name,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>` header335    + e336    + log2e337    + log10e338    + pi339    + inv_pi340    + inv_sqrtpi341    + ln2342    + ln10343    + sqrt2344    + sqrt3345    + inv_sqrt3346    + egamma347    + phi348  */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 larger352  // expression353  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 the390    // 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 that414  // 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 the427  // 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 441void UseStdNumbersCheck::registerPPCallbacks(442    const SourceManager &SM, Preprocessor *const PP,443    Preprocessor *const ModuleExpanderPP) {444  IncludeInserter.registerPreprocessor(PP);445}446 447void UseStdNumbersCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {448  Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());449  Options.store(Opts, "DiffThreshold", DiffThresholdString);450}451} // namespace clang::tidy::modernize452