226 lines · cpp
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-exception6//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 appropriate21// `DurationScale` for that factory. If no factory can be found for22// `FactoryName`, return `std::nullopt`.23static std::optional<DurationScale>24getScaleForFactory(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.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 scale48// 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(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`.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>(*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 112void 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 134void 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 const 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 the169 // cases where a user is multiplying by something such as 1e-3.170 171 // First check the LHS172 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 RHS181 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 output202 // 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 output215 // 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::abseil226