xref: /llvm-project/clang-tools-extra/clang-tidy/abseil/DurationFactoryScaleCheck.cpp (revision cbdc3e1bf9da09911ba353bcd20c6709bda43893)
1 //===--- DurationFactoryScaleCheck.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 "DurationFactoryScaleCheck.h"
10 #include "DurationRewriter.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/Tooling/FixIt.h"
14 #include <optional>
15 
16 using namespace clang::ast_matchers;
17 
18 namespace clang::tidy::abseil {
19 
20 // Given the name of a duration factory function, return the appropriate
21 // `DurationScale` for that factory.  If no factory can be found for
22 // `FactoryName`, return `std::nullopt`.
23 static std::optional<DurationScale>
getScaleForFactory(llvm::StringRef FactoryName)24 getScaleForFactory(llvm::StringRef FactoryName) {
25   return llvm::StringSwitch<std::optional<DurationScale>>(FactoryName)
26       .Case("Nanoseconds", DurationScale::Nanoseconds)
27       .Case("Microseconds", DurationScale::Microseconds)
28       .Case("Milliseconds", DurationScale::Milliseconds)
29       .Case("Seconds", DurationScale::Seconds)
30       .Case("Minutes", DurationScale::Minutes)
31       .Case("Hours", DurationScale::Hours)
32       .Default(std::nullopt);
33 }
34 
35 // Given either an integer or float literal, return its value.
36 // One and only one of `IntLit` and `FloatLit` should be provided.
getValue(const IntegerLiteral * IntLit,const FloatingLiteral * FloatLit)37 static double getValue(const IntegerLiteral *IntLit,
38                        const FloatingLiteral *FloatLit) {
39   if (IntLit)
40     return IntLit->getValue().getLimitedValue();
41 
42   assert(FloatLit != nullptr && "Neither IntLit nor FloatLit set");
43   return FloatLit->getValueAsApproximateDouble();
44 }
45 
46 // Given the scale of a duration and a `Multiplier`, determine if `Multiplier`
47 // would produce a new scale.  If so, return a tuple containing the new scale
48 // and a suitable Multiplier for that scale, otherwise `std::nullopt`.
49 static std::optional<std::tuple<DurationScale, double>>
getNewScaleSingleStep(DurationScale OldScale,double Multiplier)50 getNewScaleSingleStep(DurationScale OldScale, double Multiplier) {
51   switch (OldScale) {
52   case DurationScale::Hours:
53     if (Multiplier <= 1.0 / 60.0)
54       return std::make_tuple(DurationScale::Minutes, Multiplier * 60.0);
55     break;
56 
57   case DurationScale::Minutes:
58     if (Multiplier >= 60.0)
59       return std::make_tuple(DurationScale::Hours, Multiplier / 60.0);
60     if (Multiplier <= 1.0 / 60.0)
61       return std::make_tuple(DurationScale::Seconds, Multiplier * 60.0);
62     break;
63 
64   case DurationScale::Seconds:
65     if (Multiplier >= 60.0)
66       return std::make_tuple(DurationScale::Minutes, Multiplier / 60.0);
67     if (Multiplier <= 1e-3)
68       return std::make_tuple(DurationScale::Milliseconds, Multiplier * 1e3);
69     break;
70 
71   case DurationScale::Milliseconds:
72     if (Multiplier >= 1e3)
73       return std::make_tuple(DurationScale::Seconds, Multiplier / 1e3);
74     if (Multiplier <= 1e-3)
75       return std::make_tuple(DurationScale::Microseconds, Multiplier * 1e3);
76     break;
77 
78   case DurationScale::Microseconds:
79     if (Multiplier >= 1e3)
80       return std::make_tuple(DurationScale::Milliseconds, Multiplier / 1e3);
81     if (Multiplier <= 1e-3)
82       return std::make_tuple(DurationScale::Nanoseconds, Multiplier * 1e-3);
83     break;
84 
85   case DurationScale::Nanoseconds:
86     if (Multiplier >= 1e3)
87       return std::make_tuple(DurationScale::Microseconds, Multiplier / 1e3);
88     break;
89   }
90 
91   return std::nullopt;
92 }
93 
94 // Given the scale of a duration and a `Multiplier`, determine if `Multiplier`
95 // would produce a new scale.  If so, return it, otherwise `std::nullopt`.
getNewScale(DurationScale OldScale,double Multiplier)96 static std::optional<DurationScale> getNewScale(DurationScale OldScale,
97                                                 double Multiplier) {
98   while (Multiplier != 1.0) {
99     std::optional<std::tuple<DurationScale, double>> Result =
100         getNewScaleSingleStep(OldScale, Multiplier);
101     if (!Result)
102       break;
103     if (std::get<1>(*Result) == 1.0)
104       return std::get<0>(*Result);
105     Multiplier = std::get<1>(*Result);
106     OldScale = std::get<0>(*Result);
107   }
108 
109   return std::nullopt;
110 }
111 
registerMatchers(MatchFinder * Finder)112 void DurationFactoryScaleCheck::registerMatchers(MatchFinder *Finder) {
113   Finder->addMatcher(
114       callExpr(
115           callee(functionDecl(DurationFactoryFunction()).bind("call_decl")),
116           hasArgument(
117               0,
118               ignoringImpCasts(anyOf(
119                   cxxFunctionalCastExpr(
120                       hasDestinationType(
121                           anyOf(isInteger(), realFloatingPointType())),
122                       hasSourceExpression(initListExpr())),
123                   integerLiteral(equals(0)), floatLiteral(equals(0.0)),
124                   binaryOperator(hasOperatorName("*"),
125                                  hasEitherOperand(ignoringImpCasts(
126                                      anyOf(integerLiteral(), floatLiteral()))))
127                       .bind("mult_binop"),
128                   binaryOperator(hasOperatorName("/"), hasRHS(floatLiteral()))
129                       .bind("div_binop")))))
130           .bind("call"),
131       this);
132 }
133 
check(const MatchFinder::MatchResult & Result)134 void DurationFactoryScaleCheck::check(const MatchFinder::MatchResult &Result) {
135   const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
136 
137   // Don't try to replace things inside of macro definitions.
138   if (Call->getExprLoc().isMacroID())
139     return;
140 
141   const Expr *Arg = Call->getArg(0)->IgnoreParenImpCasts();
142   // Arguments which are macros are ignored.
143   if (Arg->getBeginLoc().isMacroID())
144     return;
145 
146   // We first handle the cases of literal zero (both float and integer).
147   if (isLiteralZero(Result, *Arg)) {
148     diag(Call->getBeginLoc(),
149          "use ZeroDuration() for zero-length time intervals")
150         << FixItHint::CreateReplacement(Call->getSourceRange(),
151                                         "absl::ZeroDuration()");
152     return;
153   }
154 
155   const auto *CallDecl = Result.Nodes.getNodeAs<FunctionDecl>("call_decl");
156   std::optional<DurationScale> MaybeScale =
157       getScaleForFactory(CallDecl->getName());
158   if (!MaybeScale)
159     return;
160 
161   DurationScale Scale = *MaybeScale;
162   const Expr *Remainder = nullptr;
163   std::optional<DurationScale> NewScale;
164 
165   // We next handle the cases of multiplication and division.
166   if (const auto *MultBinOp =
167           Result.Nodes.getNodeAs<BinaryOperator>("mult_binop")) {
168     // For multiplication, we need to look at both operands, and consider the
169     // cases where a user is multiplying by something such as 1e-3.
170 
171     // First check the LHS
172     const auto *IntLit = llvm::dyn_cast<IntegerLiteral>(MultBinOp->getLHS());
173     const auto *FloatLit = llvm::dyn_cast<FloatingLiteral>(MultBinOp->getLHS());
174     if (IntLit || FloatLit) {
175       NewScale = getNewScale(Scale, getValue(IntLit, FloatLit));
176       if (NewScale)
177         Remainder = MultBinOp->getRHS();
178     }
179 
180     // If we weren't able to scale based on the LHS, check the RHS
181     if (!NewScale) {
182       IntLit = llvm::dyn_cast<IntegerLiteral>(MultBinOp->getRHS());
183       FloatLit = llvm::dyn_cast<FloatingLiteral>(MultBinOp->getRHS());
184       if (IntLit || FloatLit) {
185         NewScale = getNewScale(Scale, getValue(IntLit, FloatLit));
186         if (NewScale)
187           Remainder = MultBinOp->getLHS();
188       }
189     }
190   } else if (const auto *DivBinOp =
191                  Result.Nodes.getNodeAs<BinaryOperator>("div_binop")) {
192     // We next handle division.
193     // For division, we only check the RHS.
194     const auto *FloatLit = llvm::cast<FloatingLiteral>(DivBinOp->getRHS());
195 
196     std::optional<DurationScale> NewScale =
197         getNewScale(Scale, 1.0 / FloatLit->getValueAsApproximateDouble());
198     if (NewScale) {
199       const Expr *Remainder = DivBinOp->getLHS();
200 
201       // We've found an appropriate scaling factor and the new scale, so output
202       // the relevant fix.
203       diag(Call->getBeginLoc(), "internal duration scaling can be removed")
204           << FixItHint::CreateReplacement(
205                  Call->getSourceRange(),
206                  (llvm::Twine(getDurationFactoryForScale(*NewScale)) + "(" +
207                   tooling::fixit::getText(*Remainder, *Result.Context) + ")")
208                      .str());
209     }
210   }
211 
212   if (NewScale) {
213     assert(Remainder && "No remainder found");
214     // We've found an appropriate scaling factor and the new scale, so output
215     // the relevant fix.
216     diag(Call->getBeginLoc(), "internal duration scaling can be removed")
217         << FixItHint::CreateReplacement(
218                Call->getSourceRange(),
219                (llvm::Twine(getDurationFactoryForScale(*NewScale)) + "(" +
220                 tooling::fixit::getText(*Remainder, *Result.Context) + ")")
221                    .str());
222   }
223 }
224 
225 } // namespace clang::tidy::abseil
226