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
16using namespace clang::ast_matchers;
17
18namespace 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`.
23static std::optional<DurationScale>
24getScaleForFactory(llvm::StringRef FactoryName) {
25 return llvm::StringSwitch<std::optional<DurationScale>>(FactoryName)
26 .Case(S: "Nanoseconds", Value: DurationScale::Nanoseconds)
27 .Case(S: "Microseconds", Value: DurationScale::Microseconds)
28 .Case(S: "Milliseconds", Value: DurationScale::Milliseconds)
29 .Case(S: "Seconds", Value: DurationScale::Seconds)
30 .Case(S: "Minutes", Value: DurationScale::Minutes)
31 .Case(S: "Hours", Value: DurationScale::Hours)
32 .Default(Value: 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.
37static 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`.
49static std::optional<std::tuple<DurationScale, double>>
50getNewScaleSingleStep(DurationScale OldScale, double Multiplier) {
51 switch (OldScale) {
52 case DurationScale::Hours:
53 if (Multiplier <= 1.0 / 60.0)
54 return std::make_tuple(args: DurationScale::Minutes, args: Multiplier * 60.0);
55 break;
56
57 case DurationScale::Minutes:
58 if (Multiplier >= 60.0)
59 return std::make_tuple(args: DurationScale::Hours, args: Multiplier / 60.0);
60 if (Multiplier <= 1.0 / 60.0)
61 return std::make_tuple(args: DurationScale::Seconds, args: Multiplier * 60.0);
62 break;
63
64 case DurationScale::Seconds:
65 if (Multiplier >= 60.0)
66 return std::make_tuple(args: DurationScale::Minutes, args: Multiplier / 60.0);
67 if (Multiplier <= 1e-3)
68 return std::make_tuple(args: DurationScale::Milliseconds, args: Multiplier * 1e3);
69 break;
70
71 case DurationScale::Milliseconds:
72 if (Multiplier >= 1e3)
73 return std::make_tuple(args: DurationScale::Seconds, args: Multiplier / 1e3);
74 if (Multiplier <= 1e-3)
75 return std::make_tuple(args: DurationScale::Microseconds, args: Multiplier * 1e3);
76 break;
77
78 case DurationScale::Microseconds:
79 if (Multiplier >= 1e3)
80 return std::make_tuple(args: DurationScale::Milliseconds, args: Multiplier / 1e3);
81 if (Multiplier <= 1e-3)
82 return std::make_tuple(args: DurationScale::Nanoseconds, args: Multiplier * 1e-3);
83 break;
84
85 case DurationScale::Nanoseconds:
86 if (Multiplier >= 1e3)
87 return std::make_tuple(args: DurationScale::Microseconds, args: 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`.
96static 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>(t&: *Result) == 1.0)
104 return std::get<0>(t&: *Result);
105 Multiplier = std::get<1>(t&: *Result);
106 OldScale = std::get<0>(t&: *Result);
107 }
108
109 return std::nullopt;
110}
111
112void DurationFactoryScaleCheck::registerMatchers(MatchFinder *Finder) {
113 Finder->addMatcher(
114 NodeMatch: callExpr(
115 callee(InnerMatcher: functionDecl(DurationFactoryFunction()).bind(ID: "call_decl")),
116 hasArgument(
117 N: 0,
118 InnerMatcher: ignoringImpCasts(InnerMatcher: anyOf(
119 cxxFunctionalCastExpr(
120 hasDestinationType(
121 InnerMatcher: anyOf(isInteger(), realFloatingPointType())),
122 hasSourceExpression(InnerMatcher: initListExpr())),
123 integerLiteral(equals(Value: 0)), floatLiteral(equals(Value: 0.0)),
124 binaryOperator(hasOperatorName(Name: "*"),
125 hasEitherOperand(InnerMatcher: ignoringImpCasts(
126 InnerMatcher: anyOf(integerLiteral(), floatLiteral()))))
127 .bind(ID: "mult_binop"),
128 binaryOperator(hasOperatorName(Name: "/"), hasRHS(InnerMatcher: floatLiteral()))
129 .bind(ID: "div_binop")))))
130 .bind(ID: "call"),
131 Action: this);
132}
133
134void DurationFactoryScaleCheck::check(const MatchFinder::MatchResult &Result) {
135 const auto *Call = Result.Nodes.getNodeAs<CallExpr>(ID: "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(Arg: 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, Node: *Arg)) {
148 diag(Loc: Call->getBeginLoc(),
149 Description: "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>(ID: "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>(ID: "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>(Val: MultBinOp->getLHS());
173 const auto *FloatLit = llvm::dyn_cast<FloatingLiteral>(Val: MultBinOp->getLHS());
174 if (IntLit || FloatLit) {
175 NewScale = getNewScale(OldScale: Scale, Multiplier: 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>(Val: MultBinOp->getRHS());
183 FloatLit = llvm::dyn_cast<FloatingLiteral>(Val: MultBinOp->getRHS());
184 if (IntLit || FloatLit) {
185 NewScale = getNewScale(OldScale: Scale, Multiplier: getValue(IntLit, FloatLit));
186 if (NewScale)
187 Remainder = MultBinOp->getLHS();
188 }
189 }
190 } else if (const auto *DivBinOp =
191 Result.Nodes.getNodeAs<BinaryOperator>(ID: "div_binop")) {
192 // We next handle division.
193 // For division, we only check the RHS.
194 const auto *FloatLit = llvm::cast<FloatingLiteral>(Val: DivBinOp->getRHS());
195
196 std::optional<DurationScale> NewScale =
197 getNewScale(OldScale: Scale, Multiplier: 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(Loc: Call->getBeginLoc(), Description: "internal duration scaling can be removed")
204 << FixItHint::CreateReplacement(
205 Call->getSourceRange(),
206 (llvm::Twine(getDurationFactoryForScale(Scale: *NewScale)) + "(" +
207 tooling::fixit::getText(Node: *Remainder, Context: *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(Loc: Call->getBeginLoc(), Description: "internal duration scaling can be removed")
217 << FixItHint::CreateReplacement(
218 Call->getSourceRange(),
219 (llvm::Twine(getDurationFactoryForScale(Scale: *NewScale)) + "(" +
220 tooling::fixit::getText(Node: *Remainder, Context: *Result.Context) + ")")
221 .str());
222 }
223}
224
225} // namespace clang::tidy::abseil
226

source code of clang-tools-extra/clang-tidy/abseil/DurationFactoryScaleCheck.cpp