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