1 //===--- DurationRewriter.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 <cmath> 10 #include <optional> 11 12 #include "DurationRewriter.h" 13 #include "clang/Tooling/FixIt.h" 14 #include "llvm/ADT/IndexedMap.h" 15 16 using namespace clang::ast_matchers; 17 18 namespace clang::tidy::abseil { 19 20 struct DurationScale2IndexFunctor { 21 using argument_type = DurationScale; 22 unsigned operator()(DurationScale Scale) const { 23 return static_cast<unsigned>(Scale); 24 } 25 }; 26 27 /// Returns an integer if the fractional part of a `FloatingLiteral` is `0`. 28 static std::optional<llvm::APSInt> 29 truncateIfIntegral(const FloatingLiteral &FloatLiteral) { 30 double Value = FloatLiteral.getValueAsApproximateDouble(); 31 if (std::fmod(Value, 1) == 0) { 32 if (Value >= static_cast<double>(1U << 31)) 33 return std::nullopt; 34 35 return llvm::APSInt::get(static_cast<int64_t>(Value)); 36 } 37 return std::nullopt; 38 } 39 40 const std::pair<llvm::StringRef, llvm::StringRef> & 41 getDurationInverseForScale(DurationScale Scale) { 42 static const llvm::IndexedMap<std::pair<llvm::StringRef, llvm::StringRef>, 43 DurationScale2IndexFunctor> 44 InverseMap = []() { 45 // TODO: Revisit the immediately invoked lambda technique when 46 // IndexedMap gets an initializer list constructor. 47 llvm::IndexedMap<std::pair<llvm::StringRef, llvm::StringRef>, 48 DurationScale2IndexFunctor> 49 InverseMap; 50 InverseMap.resize(6); 51 InverseMap[DurationScale::Hours] = 52 std::make_pair("::absl::ToDoubleHours", "::absl::ToInt64Hours"); 53 InverseMap[DurationScale::Minutes] = 54 std::make_pair("::absl::ToDoubleMinutes", "::absl::ToInt64Minutes"); 55 InverseMap[DurationScale::Seconds] = 56 std::make_pair("::absl::ToDoubleSeconds", "::absl::ToInt64Seconds"); 57 InverseMap[DurationScale::Milliseconds] = std::make_pair( 58 "::absl::ToDoubleMilliseconds", "::absl::ToInt64Milliseconds"); 59 InverseMap[DurationScale::Microseconds] = std::make_pair( 60 "::absl::ToDoubleMicroseconds", "::absl::ToInt64Microseconds"); 61 InverseMap[DurationScale::Nanoseconds] = std::make_pair( 62 "::absl::ToDoubleNanoseconds", "::absl::ToInt64Nanoseconds"); 63 return InverseMap; 64 }(); 65 66 return InverseMap[Scale]; 67 } 68 69 /// If `Node` is a call to the inverse of `Scale`, return that inverse's 70 /// argument, otherwise std::nullopt. 71 static std::optional<std::string> 72 rewriteInverseDurationCall(const MatchFinder::MatchResult &Result, 73 DurationScale Scale, const Expr &Node) { 74 const std::pair<llvm::StringRef, llvm::StringRef> &InverseFunctions = 75 getDurationInverseForScale(Scale); 76 if (const auto *MaybeCallArg = selectFirst<const Expr>( 77 "e", 78 match(callExpr(callee(functionDecl(hasAnyName( 79 InverseFunctions.first, InverseFunctions.second))), 80 hasArgument(0, expr().bind("e"))), 81 Node, *Result.Context))) { 82 return tooling::fixit::getText(*MaybeCallArg, *Result.Context).str(); 83 } 84 85 return std::nullopt; 86 } 87 88 /// If `Node` is a call to the inverse of `Scale`, return that inverse's 89 /// argument, otherwise std::nullopt. 90 static std::optional<std::string> 91 rewriteInverseTimeCall(const MatchFinder::MatchResult &Result, 92 DurationScale Scale, const Expr &Node) { 93 llvm::StringRef InverseFunction = getTimeInverseForScale(Scale); 94 if (const auto *MaybeCallArg = selectFirst<const Expr>( 95 "e", match(callExpr(callee(functionDecl(hasName(InverseFunction))), 96 hasArgument(0, expr().bind("e"))), 97 Node, *Result.Context))) { 98 return tooling::fixit::getText(*MaybeCallArg, *Result.Context).str(); 99 } 100 101 return std::nullopt; 102 } 103 104 /// Returns the factory function name for a given `Scale`. 105 llvm::StringRef getDurationFactoryForScale(DurationScale Scale) { 106 switch (Scale) { 107 case DurationScale::Hours: 108 return "absl::Hours"; 109 case DurationScale::Minutes: 110 return "absl::Minutes"; 111 case DurationScale::Seconds: 112 return "absl::Seconds"; 113 case DurationScale::Milliseconds: 114 return "absl::Milliseconds"; 115 case DurationScale::Microseconds: 116 return "absl::Microseconds"; 117 case DurationScale::Nanoseconds: 118 return "absl::Nanoseconds"; 119 } 120 llvm_unreachable("unknown scaling factor"); 121 } 122 123 llvm::StringRef getTimeFactoryForScale(DurationScale Scale) { 124 switch (Scale) { 125 case DurationScale::Hours: 126 return "absl::FromUnixHours"; 127 case DurationScale::Minutes: 128 return "absl::FromUnixMinutes"; 129 case DurationScale::Seconds: 130 return "absl::FromUnixSeconds"; 131 case DurationScale::Milliseconds: 132 return "absl::FromUnixMillis"; 133 case DurationScale::Microseconds: 134 return "absl::FromUnixMicros"; 135 case DurationScale::Nanoseconds: 136 return "absl::FromUnixNanos"; 137 } 138 llvm_unreachable("unknown scaling factor"); 139 } 140 141 /// Returns the Time factory function name for a given `Scale`. 142 llvm::StringRef getTimeInverseForScale(DurationScale Scale) { 143 switch (Scale) { 144 case DurationScale::Hours: 145 return "absl::ToUnixHours"; 146 case DurationScale::Minutes: 147 return "absl::ToUnixMinutes"; 148 case DurationScale::Seconds: 149 return "absl::ToUnixSeconds"; 150 case DurationScale::Milliseconds: 151 return "absl::ToUnixMillis"; 152 case DurationScale::Microseconds: 153 return "absl::ToUnixMicros"; 154 case DurationScale::Nanoseconds: 155 return "absl::ToUnixNanos"; 156 } 157 llvm_unreachable("unknown scaling factor"); 158 } 159 160 /// Returns `true` if `Node` is a value which evaluates to a literal `0`. 161 bool isLiteralZero(const MatchFinder::MatchResult &Result, const Expr &Node) { 162 auto ZeroMatcher = 163 anyOf(integerLiteral(equals(0)), floatLiteral(equals(0.0))); 164 165 // Check to see if we're using a zero directly. 166 if (selectFirst<const clang::Expr>( 167 "val", match(expr(ignoringImpCasts(ZeroMatcher)).bind("val"), Node, 168 *Result.Context)) != nullptr) 169 return true; 170 171 // Now check to see if we're using a functional cast with a scalar 172 // initializer expression, e.g. `int{0}`. 173 if (selectFirst<const clang::Expr>( 174 "val", match(cxxFunctionalCastExpr( 175 hasDestinationType( 176 anyOf(isInteger(), realFloatingPointType())), 177 hasSourceExpression(initListExpr( 178 hasInit(0, ignoringParenImpCasts(ZeroMatcher))))) 179 .bind("val"), 180 Node, *Result.Context)) != nullptr) 181 return true; 182 183 return false; 184 } 185 186 std::optional<std::string> 187 stripFloatCast(const ast_matchers::MatchFinder::MatchResult &Result, 188 const Expr &Node) { 189 if (const Expr *MaybeCastArg = selectFirst<const Expr>( 190 "cast_arg", 191 match(expr(anyOf(cxxStaticCastExpr( 192 hasDestinationType(realFloatingPointType()), 193 hasSourceExpression(expr().bind("cast_arg"))), 194 cStyleCastExpr( 195 hasDestinationType(realFloatingPointType()), 196 hasSourceExpression(expr().bind("cast_arg"))), 197 cxxFunctionalCastExpr( 198 hasDestinationType(realFloatingPointType()), 199 hasSourceExpression(expr().bind("cast_arg"))))), 200 Node, *Result.Context))) 201 return tooling::fixit::getText(*MaybeCastArg, *Result.Context).str(); 202 203 return std::nullopt; 204 } 205 206 std::optional<std::string> 207 stripFloatLiteralFraction(const MatchFinder::MatchResult &Result, 208 const Expr &Node) { 209 if (const auto *LitFloat = llvm::dyn_cast<FloatingLiteral>(&Node)) 210 // Attempt to simplify a `Duration` factory call with a literal argument. 211 if (std::optional<llvm::APSInt> IntValue = truncateIfIntegral(*LitFloat)) 212 return toString(*IntValue, /*radix=*/10); 213 214 return std::nullopt; 215 } 216 217 std::string simplifyDurationFactoryArg(const MatchFinder::MatchResult &Result, 218 const Expr &Node) { 219 // Check for an explicit cast to `float` or `double`. 220 if (std::optional<std::string> MaybeArg = stripFloatCast(Result, Node)) 221 return *MaybeArg; 222 223 // Check for floats without fractional components. 224 if (std::optional<std::string> MaybeArg = 225 stripFloatLiteralFraction(Result, Node)) 226 return *MaybeArg; 227 228 // We couldn't simplify any further, so return the argument text. 229 return tooling::fixit::getText(Node, *Result.Context).str(); 230 } 231 232 std::optional<DurationScale> getScaleForDurationInverse(llvm::StringRef Name) { 233 static const llvm::StringMap<DurationScale> ScaleMap( 234 {{"ToDoubleHours", DurationScale::Hours}, 235 {"ToInt64Hours", DurationScale::Hours}, 236 {"ToDoubleMinutes", DurationScale::Minutes}, 237 {"ToInt64Minutes", DurationScale::Minutes}, 238 {"ToDoubleSeconds", DurationScale::Seconds}, 239 {"ToInt64Seconds", DurationScale::Seconds}, 240 {"ToDoubleMilliseconds", DurationScale::Milliseconds}, 241 {"ToInt64Milliseconds", DurationScale::Milliseconds}, 242 {"ToDoubleMicroseconds", DurationScale::Microseconds}, 243 {"ToInt64Microseconds", DurationScale::Microseconds}, 244 {"ToDoubleNanoseconds", DurationScale::Nanoseconds}, 245 {"ToInt64Nanoseconds", DurationScale::Nanoseconds}}); 246 247 auto ScaleIter = ScaleMap.find(Name); 248 if (ScaleIter == ScaleMap.end()) 249 return std::nullopt; 250 251 return ScaleIter->second; 252 } 253 254 std::optional<DurationScale> getScaleForTimeInverse(llvm::StringRef Name) { 255 static const llvm::StringMap<DurationScale> ScaleMap( 256 {{"ToUnixHours", DurationScale::Hours}, 257 {"ToUnixMinutes", DurationScale::Minutes}, 258 {"ToUnixSeconds", DurationScale::Seconds}, 259 {"ToUnixMillis", DurationScale::Milliseconds}, 260 {"ToUnixMicros", DurationScale::Microseconds}, 261 {"ToUnixNanos", DurationScale::Nanoseconds}}); 262 263 auto ScaleIter = ScaleMap.find(Name); 264 if (ScaleIter == ScaleMap.end()) 265 return std::nullopt; 266 267 return ScaleIter->second; 268 } 269 270 std::string rewriteExprFromNumberToDuration( 271 const ast_matchers::MatchFinder::MatchResult &Result, DurationScale Scale, 272 const Expr *Node) { 273 const Expr &RootNode = *Node->IgnoreParenImpCasts(); 274 275 // First check to see if we can undo a complementary function call. 276 if (std::optional<std::string> MaybeRewrite = 277 rewriteInverseDurationCall(Result, Scale, RootNode)) 278 return *MaybeRewrite; 279 280 if (isLiteralZero(Result, RootNode)) 281 return {"absl::ZeroDuration()"}; 282 283 return (llvm::Twine(getDurationFactoryForScale(Scale)) + "(" + 284 simplifyDurationFactoryArg(Result, RootNode) + ")") 285 .str(); 286 } 287 288 std::string rewriteExprFromNumberToTime( 289 const ast_matchers::MatchFinder::MatchResult &Result, DurationScale Scale, 290 const Expr *Node) { 291 const Expr &RootNode = *Node->IgnoreParenImpCasts(); 292 293 // First check to see if we can undo a complementary function call. 294 if (std::optional<std::string> MaybeRewrite = 295 rewriteInverseTimeCall(Result, Scale, RootNode)) 296 return *MaybeRewrite; 297 298 if (isLiteralZero(Result, RootNode)) 299 return {"absl::UnixEpoch()"}; 300 301 return (llvm::Twine(getTimeFactoryForScale(Scale)) + "(" + 302 tooling::fixit::getText(RootNode, *Result.Context) + ")") 303 .str(); 304 } 305 306 bool isInMacro(const MatchFinder::MatchResult &Result, const Expr *E) { 307 if (!E->getBeginLoc().isMacroID()) 308 return false; 309 310 SourceLocation Loc = E->getBeginLoc(); 311 // We want to get closer towards the initial macro typed into the source only 312 // if the location is being expanded as a macro argument. 313 while (Result.SourceManager->isMacroArgExpansion(Loc)) { 314 // We are calling getImmediateMacroCallerLoc, but note it is essentially 315 // equivalent to calling getImmediateSpellingLoc in this context according 316 // to Clang implementation. We are not calling getImmediateSpellingLoc 317 // because Clang comment says it "should not generally be used by clients." 318 Loc = Result.SourceManager->getImmediateMacroCallerLoc(Loc); 319 } 320 return Loc.isMacroID(); 321 } 322 323 } // namespace clang::tidy::abseil 324