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