123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225 |
- //===--- DurationFactoryScaleCheck.cpp - clang-tidy -----------------------===//
- //
- // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
- // See https://llvm.org/LICENSE.txt for license information.
- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- //
- //===----------------------------------------------------------------------===//
- #include "DurationFactoryScaleCheck.h"
- #include "DurationRewriter.h"
- #include "clang/AST/ASTContext.h"
- #include "clang/ASTMatchers/ASTMatchFinder.h"
- #include "clang/Tooling/FixIt.h"
- #include <optional>
- using namespace clang::ast_matchers;
- namespace clang::tidy::abseil {
- // Given the name of a duration factory function, return the appropriate
- // `DurationScale` for that factory. If no factory can be found for
- // `FactoryName`, return `std::nullopt`.
- static std::optional<DurationScale>
- getScaleForFactory(llvm::StringRef FactoryName) {
- return llvm::StringSwitch<std::optional<DurationScale>>(FactoryName)
- .Case("Nanoseconds", DurationScale::Nanoseconds)
- .Case("Microseconds", DurationScale::Microseconds)
- .Case("Milliseconds", DurationScale::Milliseconds)
- .Case("Seconds", DurationScale::Seconds)
- .Case("Minutes", DurationScale::Minutes)
- .Case("Hours", DurationScale::Hours)
- .Default(std::nullopt);
- }
- // Given either an integer or float literal, return its value.
- // One and only one of `IntLit` and `FloatLit` should be provided.
- static double getValue(const IntegerLiteral *IntLit,
- const FloatingLiteral *FloatLit) {
- if (IntLit)
- return IntLit->getValue().getLimitedValue();
- assert(FloatLit != nullptr && "Neither IntLit nor FloatLit set");
- return FloatLit->getValueAsApproximateDouble();
- }
- // Given the scale of a duration and a `Multiplier`, determine if `Multiplier`
- // would produce a new scale. If so, return a tuple containing the new scale
- // and a suitable Multiplier for that scale, otherwise `std::nullopt`.
- static std::optional<std::tuple<DurationScale, double>>
- getNewScaleSingleStep(DurationScale OldScale, double Multiplier) {
- switch (OldScale) {
- case DurationScale::Hours:
- if (Multiplier <= 1.0 / 60.0)
- return std::make_tuple(DurationScale::Minutes, Multiplier * 60.0);
- break;
- case DurationScale::Minutes:
- if (Multiplier >= 60.0)
- return std::make_tuple(DurationScale::Hours, Multiplier / 60.0);
- if (Multiplier <= 1.0 / 60.0)
- return std::make_tuple(DurationScale::Seconds, Multiplier * 60.0);
- break;
- case DurationScale::Seconds:
- if (Multiplier >= 60.0)
- return std::make_tuple(DurationScale::Minutes, Multiplier / 60.0);
- if (Multiplier <= 1e-3)
- return std::make_tuple(DurationScale::Milliseconds, Multiplier * 1e3);
- break;
- case DurationScale::Milliseconds:
- if (Multiplier >= 1e3)
- return std::make_tuple(DurationScale::Seconds, Multiplier / 1e3);
- if (Multiplier <= 1e-3)
- return std::make_tuple(DurationScale::Microseconds, Multiplier * 1e3);
- break;
- case DurationScale::Microseconds:
- if (Multiplier >= 1e3)
- return std::make_tuple(DurationScale::Milliseconds, Multiplier / 1e3);
- if (Multiplier <= 1e-3)
- return std::make_tuple(DurationScale::Nanoseconds, Multiplier * 1e-3);
- break;
- case DurationScale::Nanoseconds:
- if (Multiplier >= 1e3)
- return std::make_tuple(DurationScale::Microseconds, Multiplier / 1e3);
- break;
- }
- return std::nullopt;
- }
- // Given the scale of a duration and a `Multiplier`, determine if `Multiplier`
- // would produce a new scale. If so, return it, otherwise `std::nullopt`.
- static std::optional<DurationScale> getNewScale(DurationScale OldScale,
- double Multiplier) {
- while (Multiplier != 1.0) {
- std::optional<std::tuple<DurationScale, double>> Result =
- getNewScaleSingleStep(OldScale, Multiplier);
- if (!Result)
- break;
- if (std::get<1>(*Result) == 1.0)
- return std::get<0>(*Result);
- Multiplier = std::get<1>(*Result);
- OldScale = std::get<0>(*Result);
- }
- return std::nullopt;
- }
- void DurationFactoryScaleCheck::registerMatchers(MatchFinder *Finder) {
- Finder->addMatcher(
- callExpr(
- callee(functionDecl(DurationFactoryFunction()).bind("call_decl")),
- hasArgument(
- 0,
- ignoringImpCasts(anyOf(
- cxxFunctionalCastExpr(
- hasDestinationType(
- anyOf(isInteger(), realFloatingPointType())),
- hasSourceExpression(initListExpr())),
- integerLiteral(equals(0)), floatLiteral(equals(0.0)),
- binaryOperator(hasOperatorName("*"),
- hasEitherOperand(ignoringImpCasts(
- anyOf(integerLiteral(), floatLiteral()))))
- .bind("mult_binop"),
- binaryOperator(hasOperatorName("/"), hasRHS(floatLiteral()))
- .bind("div_binop")))))
- .bind("call"),
- this);
- }
- void DurationFactoryScaleCheck::check(const MatchFinder::MatchResult &Result) {
- const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
- // Don't try to replace things inside of macro definitions.
- if (Call->getExprLoc().isMacroID())
- return;
- const Expr *Arg = Call->getArg(0)->IgnoreParenImpCasts();
- // Arguments which are macros are ignored.
- if (Arg->getBeginLoc().isMacroID())
- return;
- // We first handle the cases of literal zero (both float and integer).
- if (isLiteralZero(Result, *Arg)) {
- diag(Call->getBeginLoc(),
- "use ZeroDuration() for zero-length time intervals")
- << FixItHint::CreateReplacement(Call->getSourceRange(),
- "absl::ZeroDuration()");
- return;
- }
- const auto *CallDecl = Result.Nodes.getNodeAs<FunctionDecl>("call_decl");
- std::optional<DurationScale> MaybeScale =
- getScaleForFactory(CallDecl->getName());
- if (!MaybeScale)
- return;
- DurationScale Scale = *MaybeScale;
- const Expr *Remainder;
- std::optional<DurationScale> NewScale;
- // We next handle the cases of multiplication and division.
- if (const auto *MultBinOp =
- Result.Nodes.getNodeAs<BinaryOperator>("mult_binop")) {
- // For multiplication, we need to look at both operands, and consider the
- // cases where a user is multiplying by something such as 1e-3.
- // First check the LHS
- const auto *IntLit = llvm::dyn_cast<IntegerLiteral>(MultBinOp->getLHS());
- const auto *FloatLit = llvm::dyn_cast<FloatingLiteral>(MultBinOp->getLHS());
- if (IntLit || FloatLit) {
- NewScale = getNewScale(Scale, getValue(IntLit, FloatLit));
- if (NewScale)
- Remainder = MultBinOp->getRHS();
- }
- // If we weren't able to scale based on the LHS, check the RHS
- if (!NewScale) {
- IntLit = llvm::dyn_cast<IntegerLiteral>(MultBinOp->getRHS());
- FloatLit = llvm::dyn_cast<FloatingLiteral>(MultBinOp->getRHS());
- if (IntLit || FloatLit) {
- NewScale = getNewScale(Scale, getValue(IntLit, FloatLit));
- if (NewScale)
- Remainder = MultBinOp->getLHS();
- }
- }
- } else if (const auto *DivBinOp =
- Result.Nodes.getNodeAs<BinaryOperator>("div_binop")) {
- // We next handle division.
- // For division, we only check the RHS.
- const auto *FloatLit = llvm::cast<FloatingLiteral>(DivBinOp->getRHS());
- std::optional<DurationScale> NewScale =
- getNewScale(Scale, 1.0 / FloatLit->getValueAsApproximateDouble());
- if (NewScale) {
- const Expr *Remainder = DivBinOp->getLHS();
- // We've found an appropriate scaling factor and the new scale, so output
- // the relevant fix.
- diag(Call->getBeginLoc(), "internal duration scaling can be removed")
- << FixItHint::CreateReplacement(
- Call->getSourceRange(),
- (llvm::Twine(getDurationFactoryForScale(*NewScale)) + "(" +
- tooling::fixit::getText(*Remainder, *Result.Context) + ")")
- .str());
- }
- }
- if (NewScale) {
- assert(Remainder && "No remainder found");
- // We've found an appropriate scaling factor and the new scale, so output
- // the relevant fix.
- diag(Call->getBeginLoc(), "internal duration scaling can be removed")
- << FixItHint::CreateReplacement(
- Call->getSourceRange(),
- (llvm::Twine(getDurationFactoryForScale(*NewScale)) + "(" +
- tooling::fixit::getText(*Remainder, *Result.Context) + ")")
- .str());
- }
- }
- } // namespace clang::tidy::abseil
|