reference, declarationdefinition
definition → references, declarations, derived classes, virtual overrides
reference to multiple definitions → definitions
unreferenced
    1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
//===--- TypePromotionInMathFnCheck.cpp - clang-tidy-----------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "TypePromotionInMathFnCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/Preprocessor.h"
#include "llvm/ADT/StringSet.h"

using namespace clang::ast_matchers;

namespace clang {
namespace tidy {
namespace performance {

namespace {
AST_MATCHER_P(Type, isBuiltinType, BuiltinType::Kind, Kind) {
  if (const auto *BT = dyn_cast<BuiltinType>(&Node)) {
    return BT->getKind() == Kind;
  }
  return false;
}
} // anonymous namespace

TypePromotionInMathFnCheck::TypePromotionInMathFnCheck(
    StringRef Name, ClangTidyContext *Context)
    : ClangTidyCheck(Name, Context),
      IncludeStyle(utils::IncludeSorter::parseIncludeStyle(
          Options.getLocalOrGlobal("IncludeStyle", "llvm"))) {}

void TypePromotionInMathFnCheck::registerPPCallbacks(
    const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
  IncludeInserter = std::make_unique<utils::IncludeInserter>(SM, getLangOpts(),
                                                              IncludeStyle);
  PP->addPPCallbacks(IncludeInserter->CreatePPCallbacks());
}

void TypePromotionInMathFnCheck::storeOptions(
    ClangTidyOptions::OptionMap &Opts) {
  Options.store(Opts, "IncludeStyle",
                utils::IncludeSorter::toString(IncludeStyle));
}

void TypePromotionInMathFnCheck::registerMatchers(MatchFinder *Finder) {
  constexpr BuiltinType::Kind IntTy = BuiltinType::Int;
  constexpr BuiltinType::Kind LongTy = BuiltinType::Long;
  constexpr BuiltinType::Kind FloatTy = BuiltinType::Float;
  constexpr BuiltinType::Kind DoubleTy = BuiltinType::Double;
  constexpr BuiltinType::Kind LongDoubleTy = BuiltinType::LongDouble;

  auto hasBuiltinTyParam = [](int Pos, BuiltinType::Kind Kind) {
    return hasParameter(Pos, hasType(isBuiltinType(Kind)));
  };
  auto hasBuiltinTyArg = [](int Pos, BuiltinType::Kind Kind) {
    return hasArgument(Pos, hasType(isBuiltinType(Kind)));
  };

  // Match calls to foo(double) with a float argument.
  auto OneDoubleArgFns = hasAnyName(
      "::acos", "::acosh", "::asin", "::asinh", "::atan", "::atanh", "::cbrt",
      "::ceil", "::cos", "::cosh", "::erf", "::erfc", "::exp", "::exp2",
      "::expm1", "::fabs", "::floor", "::ilogb", "::lgamma", "::llrint",
      "::log", "::log10", "::log1p", "::log2", "::logb", "::lrint", "::modf",
      "::nearbyint", "::rint", "::round", "::sin", "::sinh", "::sqrt", "::tan",
      "::tanh", "::tgamma", "::trunc", "::llround", "::lround");
  Finder->addMatcher(
      callExpr(callee(functionDecl(OneDoubleArgFns, parameterCountIs(1),
                                   hasBuiltinTyParam(0, DoubleTy))),
               hasBuiltinTyArg(0, FloatTy))
          .bind("call"),
      this);

  // Match calls to foo(double, double) where both args are floats.
  auto TwoDoubleArgFns = hasAnyName("::atan2", "::copysign", "::fdim", "::fmax",
                                    "::fmin", "::fmod", "::hypot", "::ldexp",
                                    "::nextafter", "::pow", "::remainder");
  Finder->addMatcher(
      callExpr(callee(functionDecl(TwoDoubleArgFns, parameterCountIs(2),
                                   hasBuiltinTyParam(0, DoubleTy),
                                   hasBuiltinTyParam(1, DoubleTy))),
               hasBuiltinTyArg(0, FloatTy), hasBuiltinTyArg(1, FloatTy))
          .bind("call"),
      this);

  // Match calls to fma(double, double, double) where all args are floats.
  Finder->addMatcher(
      callExpr(callee(functionDecl(hasName("::fma"), parameterCountIs(3),
                                   hasBuiltinTyParam(0, DoubleTy),
                                   hasBuiltinTyParam(1, DoubleTy),
                                   hasBuiltinTyParam(2, DoubleTy))),
               hasBuiltinTyArg(0, FloatTy), hasBuiltinTyArg(1, FloatTy),
               hasBuiltinTyArg(2, FloatTy))
          .bind("call"),
      this);

  // Match calls to frexp(double, int*) where the first arg is a float.
  Finder->addMatcher(
      callExpr(callee(functionDecl(
                   hasName("::frexp"), parameterCountIs(2),
                   hasBuiltinTyParam(0, DoubleTy),
                   hasParameter(1, parmVarDecl(hasType(pointerType(
                                       pointee(isBuiltinType(IntTy)))))))),
               hasBuiltinTyArg(0, FloatTy))
          .bind("call"),
      this);

  // Match calls to nexttoward(double, long double) where the first arg is a
  // float.
  Finder->addMatcher(
      callExpr(callee(functionDecl(hasName("::nexttoward"), parameterCountIs(2),
                                   hasBuiltinTyParam(0, DoubleTy),
                                   hasBuiltinTyParam(1, LongDoubleTy))),
               hasBuiltinTyArg(0, FloatTy))
          .bind("call"),
      this);

  // Match calls to remquo(double, double, int*) where the first two args are
  // floats.
  Finder->addMatcher(
      callExpr(
          callee(functionDecl(
              hasName("::remquo"), parameterCountIs(3),
              hasBuiltinTyParam(0, DoubleTy), hasBuiltinTyParam(1, DoubleTy),
              hasParameter(2, parmVarDecl(hasType(pointerType(
                                  pointee(isBuiltinType(IntTy)))))))),
          hasBuiltinTyArg(0, FloatTy), hasBuiltinTyArg(1, FloatTy))
          .bind("call"),
      this);

  // Match calls to scalbln(double, long) where the first arg is a float.
  Finder->addMatcher(
      callExpr(callee(functionDecl(hasName("::scalbln"), parameterCountIs(2),
                                   hasBuiltinTyParam(0, DoubleTy),
                                   hasBuiltinTyParam(1, LongTy))),
               hasBuiltinTyArg(0, FloatTy))
          .bind("call"),
      this);

  // Match calls to scalbn(double, int) where the first arg is a float.
  Finder->addMatcher(
      callExpr(callee(functionDecl(hasName("::scalbn"), parameterCountIs(2),
                                   hasBuiltinTyParam(0, DoubleTy),
                                   hasBuiltinTyParam(1, IntTy))),
               hasBuiltinTyArg(0, FloatTy))
          .bind("call"),
      this);

  // modf(double, double*) is omitted because the second parameter forces the
  // type -- there's no conversion from float* to double*.
}

void TypePromotionInMathFnCheck::check(const MatchFinder::MatchResult &Result) {
  const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
  assert(Call != nullptr);

  StringRef OldFnName = Call->getDirectCallee()->getName();

  // In C++ mode, we prefer std::foo to ::foof.  But some of these suggestions
  // are only valid in C++11 and newer.
  static llvm::StringSet<> Cpp11OnlyFns = {
      "acosh",     "asinh",      "atanh",     "cbrt",   "copysign", "erf",
      "erfc",      "exp2",       "expm1",     "fdim",   "fma",      "fmax",
      "fmin",      "hypot",      "ilogb",     "lgamma", "llrint",   "llround",
      "log1p",     "log2",       "logb",      "lrint",  "lround",   "nearbyint",
      "nextafter", "nexttoward", "remainder", "remquo", "rint",     "round",
      "scalbln",   "scalbn",     "tgamma",    "trunc"};
  bool StdFnRequiresCpp11 = Cpp11OnlyFns.count(OldFnName);

  std::string NewFnName;
  bool FnInCmath = false;
  if (getLangOpts().CPlusPlus &&
      (!StdFnRequiresCpp11 || getLangOpts().CPlusPlus11)) {
    NewFnName = ("std::" + OldFnName).str();
    FnInCmath = true;
  } else {
    NewFnName = (OldFnName + "f").str();
  }

  auto Diag = diag(Call->getExprLoc(), "call to '%0' promotes float to double")
              << OldFnName
              << FixItHint::CreateReplacement(
                     Call->getCallee()->getSourceRange(), NewFnName);

  // Suggest including <cmath> if the function we're suggesting is declared in
  // <cmath> and it's not already included.  We never have to suggest including
  // <math.h>, because the functions we're suggesting moving away from are all
  // declared in <math.h>.
  if (FnInCmath)
    if (auto IncludeFixit = IncludeInserter->CreateIncludeInsertion(
            Result.Context->getSourceManager().getFileID(Call->getBeginLoc()),
            "cmath", /*IsAngled=*/true))
      Diag << *IncludeFixit;
}

} // namespace performance
} // namespace tidy
} // namespace clang