1 //===--- DurationFactoryScaleCheck.cpp - clang-tidy -----------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
16 using namespace clang::ast_matchers
;
18 namespace clang::tidy::abseil
{
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
>
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
);
35 // Given either an integer or float literal, return its value.
36 // One and only one of `IntLit` and `FloatLit` should be provided.
37 static double getValue(const IntegerLiteral
*IntLit
,
38 const FloatingLiteral
*FloatLit
) {
40 return IntLit
->getValue().getLimitedValue();
42 assert(FloatLit
!= nullptr && "Neither IntLit nor FloatLit set");
43 return FloatLit
->getValueAsApproximateDouble();
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>>
50 getNewScaleSingleStep(DurationScale OldScale
, double Multiplier
) {
52 case DurationScale::Hours
:
53 if (Multiplier
<= 1.0 / 60.0)
54 return std::make_tuple(DurationScale::Minutes
, Multiplier
* 60.0);
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);
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
);
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
);
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);
85 case DurationScale::Nanoseconds
:
86 if (Multiplier
>= 1e3
)
87 return std::make_tuple(DurationScale::Microseconds
, Multiplier
/ 1e3
);
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`.
96 static std::optional
<DurationScale
> getNewScale(DurationScale OldScale
,
98 while (Multiplier
!= 1.0) {
99 std::optional
<std::tuple
<DurationScale
, double>> Result
=
100 getNewScaleSingleStep(OldScale
, Multiplier
);
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
);
112 void DurationFactoryScaleCheck::registerMatchers(MatchFinder
*Finder
) {
115 callee(functionDecl(DurationFactoryFunction()).bind("call_decl")),
118 ignoringImpCasts(anyOf(
119 cxxFunctionalCastExpr(
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()))))
128 binaryOperator(hasOperatorName("/"), hasRHS(floatLiteral()))
129 .bind("div_binop")))))
134 void DurationFactoryScaleCheck::check(const MatchFinder::MatchResult
&Result
) {
135 const auto *Call
= Result
.Nodes
.getNodeAs
<CallExpr
>("call");
137 // Don't try to replace things inside of macro definitions.
138 if (Call
->getExprLoc().isMacroID())
141 const Expr
*Arg
= Call
->getArg(0)->IgnoreParenImpCasts();
142 // Arguments which are macros are ignored.
143 if (Arg
->getBeginLoc().isMacroID())
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()");
155 const auto *CallDecl
= Result
.Nodes
.getNodeAs
<FunctionDecl
>("call_decl");
156 std::optional
<DurationScale
> MaybeScale
=
157 getScaleForFactory(CallDecl
->getName());
161 DurationScale Scale
= *MaybeScale
;
162 const Expr
*Remainder
= nullptr;
163 std::optional
<DurationScale
> NewScale
;
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.
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
));
177 Remainder
= MultBinOp
->getRHS();
180 // If we weren't able to scale based on the LHS, check the RHS
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
));
187 Remainder
= MultBinOp
->getLHS();
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());
196 std::optional
<DurationScale
> NewScale
=
197 getNewScale(Scale
, 1.0 / FloatLit
->getValueAsApproximateDouble());
199 const Expr
*Remainder
= DivBinOp
->getLHS();
201 // We've found an appropriate scaling factor and the new scale, so output
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
) + ")")
213 assert(Remainder
&& "No remainder found");
214 // We've found an appropriate scaling factor and the new scale, so output
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
) + ")")
225 } // namespace clang::tidy::abseil