1 //===--- TypePromotionInMathFnCheck.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 "TypePromotionInMathFnCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/Frontend/CompilerInstance.h"
13 #include "clang/Lex/Preprocessor.h"
14 #include "llvm/ADT/StringSet.h"
15 
16 using namespace clang::ast_matchers;
17 
18 namespace clang {
19 namespace tidy {
20 namespace performance {
21 
22 namespace {
AST_MATCHER_P(Type,isBuiltinType,BuiltinType::Kind,Kind)23 AST_MATCHER_P(Type, isBuiltinType, BuiltinType::Kind, Kind) {
24   if (const auto *BT = dyn_cast<BuiltinType>(&Node)) {
25     return BT->getKind() == Kind;
26   }
27   return false;
28 }
29 } // anonymous namespace
30 
TypePromotionInMathFnCheck(StringRef Name,ClangTidyContext * Context)31 TypePromotionInMathFnCheck::TypePromotionInMathFnCheck(
32     StringRef Name, ClangTidyContext *Context)
33     : ClangTidyCheck(Name, Context),
34       IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
35                                                utils::IncludeSorter::IS_LLVM)) {
36 }
37 
registerPPCallbacks(const SourceManager & SM,Preprocessor * PP,Preprocessor * ModuleExpanderPP)38 void TypePromotionInMathFnCheck::registerPPCallbacks(
39     const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
40   IncludeInserter.registerPreprocessor(PP);
41 }
42 
storeOptions(ClangTidyOptions::OptionMap & Opts)43 void TypePromotionInMathFnCheck::storeOptions(
44     ClangTidyOptions::OptionMap &Opts) {
45   Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
46 }
47 
registerMatchers(MatchFinder * Finder)48 void TypePromotionInMathFnCheck::registerMatchers(MatchFinder *Finder) {
49   constexpr BuiltinType::Kind IntTy = BuiltinType::Int;
50   constexpr BuiltinType::Kind LongTy = BuiltinType::Long;
51   constexpr BuiltinType::Kind FloatTy = BuiltinType::Float;
52   constexpr BuiltinType::Kind DoubleTy = BuiltinType::Double;
53   constexpr BuiltinType::Kind LongDoubleTy = BuiltinType::LongDouble;
54 
55   auto hasBuiltinTyParam = [](int Pos, BuiltinType::Kind Kind) {
56     return hasParameter(Pos, hasType(isBuiltinType(Kind)));
57   };
58   auto hasBuiltinTyArg = [](int Pos, BuiltinType::Kind Kind) {
59     return hasArgument(Pos, hasType(isBuiltinType(Kind)));
60   };
61 
62   // Match calls to foo(double) with a float argument.
63   auto OneDoubleArgFns = hasAnyName(
64       "::acos", "::acosh", "::asin", "::asinh", "::atan", "::atanh", "::cbrt",
65       "::ceil", "::cos", "::cosh", "::erf", "::erfc", "::exp", "::exp2",
66       "::expm1", "::fabs", "::floor", "::ilogb", "::lgamma", "::llrint",
67       "::log", "::log10", "::log1p", "::log2", "::logb", "::lrint", "::modf",
68       "::nearbyint", "::rint", "::round", "::sin", "::sinh", "::sqrt", "::tan",
69       "::tanh", "::tgamma", "::trunc", "::llround", "::lround");
70   Finder->addMatcher(
71       callExpr(callee(functionDecl(OneDoubleArgFns, parameterCountIs(1),
72                                    hasBuiltinTyParam(0, DoubleTy))),
73                hasBuiltinTyArg(0, FloatTy))
74           .bind("call"),
75       this);
76 
77   // Match calls to foo(double, double) where both args are floats.
78   auto TwoDoubleArgFns = hasAnyName("::atan2", "::copysign", "::fdim", "::fmax",
79                                     "::fmin", "::fmod", "::hypot", "::ldexp",
80                                     "::nextafter", "::pow", "::remainder");
81   Finder->addMatcher(
82       callExpr(callee(functionDecl(TwoDoubleArgFns, parameterCountIs(2),
83                                    hasBuiltinTyParam(0, DoubleTy),
84                                    hasBuiltinTyParam(1, DoubleTy))),
85                hasBuiltinTyArg(0, FloatTy), hasBuiltinTyArg(1, FloatTy))
86           .bind("call"),
87       this);
88 
89   // Match calls to fma(double, double, double) where all args are floats.
90   Finder->addMatcher(
91       callExpr(callee(functionDecl(hasName("::fma"), parameterCountIs(3),
92                                    hasBuiltinTyParam(0, DoubleTy),
93                                    hasBuiltinTyParam(1, DoubleTy),
94                                    hasBuiltinTyParam(2, DoubleTy))),
95                hasBuiltinTyArg(0, FloatTy), hasBuiltinTyArg(1, FloatTy),
96                hasBuiltinTyArg(2, FloatTy))
97           .bind("call"),
98       this);
99 
100   // Match calls to frexp(double, int*) where the first arg is a float.
101   Finder->addMatcher(
102       callExpr(callee(functionDecl(
103                    hasName("::frexp"), parameterCountIs(2),
104                    hasBuiltinTyParam(0, DoubleTy),
105                    hasParameter(1, parmVarDecl(hasType(pointerType(
106                                        pointee(isBuiltinType(IntTy)))))))),
107                hasBuiltinTyArg(0, FloatTy))
108           .bind("call"),
109       this);
110 
111   // Match calls to nexttoward(double, long double) where the first arg is a
112   // float.
113   Finder->addMatcher(
114       callExpr(callee(functionDecl(hasName("::nexttoward"), parameterCountIs(2),
115                                    hasBuiltinTyParam(0, DoubleTy),
116                                    hasBuiltinTyParam(1, LongDoubleTy))),
117                hasBuiltinTyArg(0, FloatTy))
118           .bind("call"),
119       this);
120 
121   // Match calls to remquo(double, double, int*) where the first two args are
122   // floats.
123   Finder->addMatcher(
124       callExpr(
125           callee(functionDecl(
126               hasName("::remquo"), parameterCountIs(3),
127               hasBuiltinTyParam(0, DoubleTy), hasBuiltinTyParam(1, DoubleTy),
128               hasParameter(2, parmVarDecl(hasType(pointerType(
129                                   pointee(isBuiltinType(IntTy)))))))),
130           hasBuiltinTyArg(0, FloatTy), hasBuiltinTyArg(1, FloatTy))
131           .bind("call"),
132       this);
133 
134   // Match calls to scalbln(double, long) where the first arg is a float.
135   Finder->addMatcher(
136       callExpr(callee(functionDecl(hasName("::scalbln"), parameterCountIs(2),
137                                    hasBuiltinTyParam(0, DoubleTy),
138                                    hasBuiltinTyParam(1, LongTy))),
139                hasBuiltinTyArg(0, FloatTy))
140           .bind("call"),
141       this);
142 
143   // Match calls to scalbn(double, int) where the first arg is a float.
144   Finder->addMatcher(
145       callExpr(callee(functionDecl(hasName("::scalbn"), parameterCountIs(2),
146                                    hasBuiltinTyParam(0, DoubleTy),
147                                    hasBuiltinTyParam(1, IntTy))),
148                hasBuiltinTyArg(0, FloatTy))
149           .bind("call"),
150       this);
151 
152   // modf(double, double*) is omitted because the second parameter forces the
153   // type -- there's no conversion from float* to double*.
154 }
155 
check(const MatchFinder::MatchResult & Result)156 void TypePromotionInMathFnCheck::check(const MatchFinder::MatchResult &Result) {
157   const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
158   assert(Call != nullptr);
159 
160   StringRef OldFnName = Call->getDirectCallee()->getName();
161 
162   // In C++ mode, we prefer std::foo to ::foof.  But some of these suggestions
163   // are only valid in C++11 and newer.
164   static llvm::StringSet<> Cpp11OnlyFns = {
165       "acosh",     "asinh",      "atanh",     "cbrt",   "copysign", "erf",
166       "erfc",      "exp2",       "expm1",     "fdim",   "fma",      "fmax",
167       "fmin",      "hypot",      "ilogb",     "lgamma", "llrint",   "llround",
168       "log1p",     "log2",       "logb",      "lrint",  "lround",   "nearbyint",
169       "nextafter", "nexttoward", "remainder", "remquo", "rint",     "round",
170       "scalbln",   "scalbn",     "tgamma",    "trunc"};
171   bool StdFnRequiresCpp11 = Cpp11OnlyFns.count(OldFnName);
172 
173   std::string NewFnName;
174   bool FnInCmath = false;
175   if (getLangOpts().CPlusPlus &&
176       (!StdFnRequiresCpp11 || getLangOpts().CPlusPlus11)) {
177     NewFnName = ("std::" + OldFnName).str();
178     FnInCmath = true;
179   } else {
180     NewFnName = (OldFnName + "f").str();
181   }
182 
183   auto Diag = diag(Call->getExprLoc(), "call to '%0' promotes float to double")
184               << OldFnName
185               << FixItHint::CreateReplacement(
186                      Call->getCallee()->getSourceRange(), NewFnName);
187 
188   // Suggest including <cmath> if the function we're suggesting is declared in
189   // <cmath> and it's not already included.  We never have to suggest including
190   // <math.h>, because the functions we're suggesting moving away from are all
191   // declared in <math.h>.
192   if (FnInCmath)
193     Diag << IncludeInserter.createIncludeInsertion(
194         Result.Context->getSourceManager().getFileID(Call->getBeginLoc()),
195         "<cmath>");
196 }
197 
198 } // namespace performance
199 } // namespace tidy
200 } // namespace clang
201