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