summaryrefslogtreecommitdiffstats
path: root/clang-tools-extra/clang-tidy/abseil/DurationComparisonCheck.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'clang-tools-extra/clang-tidy/abseil/DurationComparisonCheck.cpp')
-rw-r--r--clang-tools-extra/clang-tidy/abseil/DurationComparisonCheck.cpp164
1 files changed, 164 insertions, 0 deletions
diff --git a/clang-tools-extra/clang-tidy/abseil/DurationComparisonCheck.cpp b/clang-tools-extra/clang-tidy/abseil/DurationComparisonCheck.cpp
new file mode 100644
index 00000000000..d5eb81b0f49
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/abseil/DurationComparisonCheck.cpp
@@ -0,0 +1,164 @@
+//===--- DurationComparisonCheck.cpp - clang-tidy -------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "DurationComparisonCheck.h"
+#include "DurationRewriter.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Tooling/FixIt.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace tidy {
+namespace abseil {
+
+/// Given the name of an inverse Duration function (e.g., `ToDoubleSeconds`),
+/// return its `DurationScale`, or `None` if a match is not found.
+static llvm::Optional<DurationScale> getScaleForInverse(llvm::StringRef Name) {
+ static const llvm::DenseMap<llvm::StringRef, DurationScale> ScaleMap(
+ {{"ToDoubleHours", DurationScale::Hours},
+ {"ToInt64Hours", DurationScale::Hours},
+ {"ToDoubleMinutes", DurationScale::Minutes},
+ {"ToInt64Minutes", DurationScale::Minutes},
+ {"ToDoubleSeconds", DurationScale::Seconds},
+ {"ToInt64Seconds", DurationScale::Seconds},
+ {"ToDoubleMilliseconds", DurationScale::Milliseconds},
+ {"ToInt64Milliseconds", DurationScale::Milliseconds},
+ {"ToDoubleMicroseconds", DurationScale::Microseconds},
+ {"ToInt64Microseconds", DurationScale::Microseconds},
+ {"ToDoubleNanoseconds", DurationScale::Nanoseconds},
+ {"ToInt64Nanoseconds", DurationScale::Nanoseconds}});
+
+ auto ScaleIter = ScaleMap.find(std::string(Name));
+ if (ScaleIter == ScaleMap.end())
+ return llvm::None;
+
+ return ScaleIter->second;
+}
+
+/// Given a `Scale` return the inverse functions for it.
+static const std::pair<llvm::StringRef, llvm::StringRef> &
+getInverseForScale(DurationScale Scale) {
+ static const std::unordered_map<DurationScale,
+ std::pair<llvm::StringRef, llvm::StringRef>>
+ InverseMap(
+ {{DurationScale::Hours,
+ std::make_pair("::absl::ToDoubleHours", "::absl::ToInt64Hours")},
+ {DurationScale::Minutes, std::make_pair("::absl::ToDoubleMinutes",
+ "::absl::ToInt64Minutes")},
+ {DurationScale::Seconds, std::make_pair("::absl::ToDoubleSeconds",
+ "::absl::ToInt64Seconds")},
+ {DurationScale::Milliseconds,
+ std::make_pair("::absl::ToDoubleMilliseconds",
+ "::absl::ToInt64Milliseconds")},
+ {DurationScale::Microseconds,
+ std::make_pair("::absl::ToDoubleMicroseconds",
+ "::absl::ToInt64Microseconds")},
+ {DurationScale::Nanoseconds,
+ std::make_pair("::absl::ToDoubleNanoseconds",
+ "::absl::ToInt64Nanoseconds")}});
+
+ // We know our map contains all the Scale values, so we can skip the
+ // nonexistence check.
+ auto InverseIter = InverseMap.find(Scale);
+ assert(InverseIter != InverseMap.end() && "Unexpected scale found");
+ return InverseIter->second;
+}
+
+/// If `Node` is a call to the inverse of `Scale`, return that inverse's
+/// argument, otherwise None.
+static llvm::Optional<std::string>
+maybeRewriteInverseDurationCall(const MatchFinder::MatchResult &Result,
+ DurationScale Scale, const Expr &Node) {
+ const std::pair<std::string, std::string> &InverseFunctions =
+ getInverseForScale(Scale);
+ if (const Expr *MaybeCallArg = selectFirst<const Expr>(
+ "e", match(callExpr(callee(functionDecl(
+ hasAnyName(InverseFunctions.first.c_str(),
+ InverseFunctions.second.c_str()))),
+ hasArgument(0, expr().bind("e"))),
+ Node, *Result.Context))) {
+ return tooling::fixit::getText(*MaybeCallArg, *Result.Context).str();
+ }
+
+ return llvm::None;
+}
+
+/// Assuming `Node` has type `double` or `int` representing a time interval of
+/// `Scale`, return the expression to make it a suitable `Duration`.
+static llvm::Optional<std::string> rewriteExprFromNumberToDuration(
+ const ast_matchers::MatchFinder::MatchResult &Result, DurationScale Scale,
+ const Expr *Node) {
+ const Expr &RootNode = *Node->IgnoreParenImpCasts();
+
+ if (RootNode.getBeginLoc().isMacroID())
+ return llvm::None;
+
+ // First check to see if we can undo a complimentary function call.
+ if (llvm::Optional<std::string> MaybeRewrite =
+ maybeRewriteInverseDurationCall(Result, Scale, RootNode))
+ return *MaybeRewrite;
+
+ if (IsLiteralZero(Result, RootNode))
+ return std::string("absl::ZeroDuration()");
+
+ return (llvm::Twine(getFactoryForScale(Scale)) + "(" +
+ simplifyDurationFactoryArg(Result, RootNode) + ")")
+ .str();
+}
+
+void DurationComparisonCheck::registerMatchers(MatchFinder *Finder) {
+ auto Matcher =
+ binaryOperator(anyOf(hasOperatorName(">"), hasOperatorName(">="),
+ hasOperatorName("=="), hasOperatorName("<="),
+ hasOperatorName("<")),
+ hasEitherOperand(ignoringImpCasts(callExpr(
+ callee(functionDecl(DurationConversionFunction())
+ .bind("function_decl"))))))
+ .bind("binop");
+
+ Finder->addMatcher(Matcher, this);
+}
+
+void DurationComparisonCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *Binop = Result.Nodes.getNodeAs<BinaryOperator>("binop");
+
+ // Don't try to replace things inside of macro definitions.
+ if (Binop->getExprLoc().isMacroID())
+ return;
+
+ llvm::Optional<DurationScale> Scale = getScaleForInverse(
+ Result.Nodes.getNodeAs<FunctionDecl>("function_decl")->getName());
+ if (!Scale)
+ return;
+
+ // In most cases, we'll only need to rewrite one of the sides, but we also
+ // want to handle the case of rewriting both sides. This is much simpler if
+ // we unconditionally try and rewrite both, and let the rewriter determine
+ // if nothing needs to be done.
+ llvm::Optional<std::string> LhsReplacement =
+ rewriteExprFromNumberToDuration(Result, *Scale, Binop->getLHS());
+ llvm::Optional<std::string> RhsReplacement =
+ rewriteExprFromNumberToDuration(Result, *Scale, Binop->getRHS());
+
+ if (!(LhsReplacement && RhsReplacement))
+ return;
+
+ diag(Binop->getBeginLoc(), "perform comparison in the duration domain")
+ << FixItHint::CreateReplacement(Binop->getSourceRange(),
+ (llvm::Twine(*LhsReplacement) + " " +
+ Binop->getOpcodeStr() + " " +
+ *RhsReplacement)
+ .str());
+}
+
+} // namespace abseil
+} // namespace tidy
+} // namespace clang
OpenPOWER on IntegriCloud