summaryrefslogtreecommitdiffstats
path: root/clang-tidy/abseil/TimeSubtractionCheck.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'clang-tidy/abseil/TimeSubtractionCheck.cpp')
-rw-r--r--clang-tidy/abseil/TimeSubtractionCheck.cpp181
1 files changed, 181 insertions, 0 deletions
diff --git a/clang-tidy/abseil/TimeSubtractionCheck.cpp b/clang-tidy/abseil/TimeSubtractionCheck.cpp
new file mode 100644
index 00000000..0a7c4016
--- /dev/null
+++ b/clang-tidy/abseil/TimeSubtractionCheck.cpp
@@ -0,0 +1,181 @@
+//===--- TimeSubtractionCheck.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 "TimeSubtractionCheck.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 {
+
+// Returns `true` if `Range` is inside a macro definition.
+static bool InsideMacroDefinition(const MatchFinder::MatchResult &Result,
+ SourceRange Range) {
+ return !clang::Lexer::makeFileCharRange(
+ clang::CharSourceRange::getCharRange(Range),
+ *Result.SourceManager, Result.Context->getLangOpts())
+ .isValid();
+}
+
+static bool isConstructorAssignment(const MatchFinder::MatchResult &Result,
+ const Expr *Node) {
+ return selectFirst<const Expr>(
+ "e", match(expr(hasParent(materializeTemporaryExpr(hasParent(
+ cxxConstructExpr(hasParent(exprWithCleanups(
+ hasParent(varDecl()))))))))
+ .bind("e"),
+ *Node, *Result.Context)) != nullptr;
+}
+
+static bool isArgument(const MatchFinder::MatchResult &Result,
+ const Expr *Node) {
+ return selectFirst<const Expr>(
+ "e",
+ match(expr(hasParent(
+ materializeTemporaryExpr(hasParent(cxxConstructExpr(
+ hasParent(callExpr()),
+ unless(hasParent(cxxOperatorCallExpr())))))))
+ .bind("e"),
+ *Node, *Result.Context)) != nullptr;
+}
+
+static bool isReturn(const MatchFinder::MatchResult &Result, const Expr *Node) {
+ return selectFirst<const Expr>(
+ "e", match(expr(hasParent(materializeTemporaryExpr(hasParent(
+ cxxConstructExpr(hasParent(exprWithCleanups(
+ hasParent(returnStmt()))))))))
+ .bind("e"),
+ *Node, *Result.Context)) != nullptr;
+}
+
+static bool parensRequired(const MatchFinder::MatchResult &Result,
+ const Expr *Node) {
+ // TODO: Figure out any more contexts in which we can omit the surrounding
+ // parentheses.
+ return !(isConstructorAssignment(Result, Node) || isArgument(Result, Node) ||
+ isReturn(Result, Node));
+}
+
+void TimeSubtractionCheck::emitDiagnostic(const Expr *Node,
+ llvm::StringRef Replacement) {
+ diag(Node->getBeginLoc(), "perform subtraction in the time domain")
+ << FixItHint::CreateReplacement(Node->getSourceRange(), Replacement);
+}
+
+void TimeSubtractionCheck::registerMatchers(MatchFinder *Finder) {
+ for (auto ScaleName :
+ {"Hours", "Minutes", "Seconds", "Millis", "Micros", "Nanos"}) {
+ std::string TimeInverse = (llvm::Twine("ToUnix") + ScaleName).str();
+ llvm::Optional<DurationScale> Scale = getScaleForTimeInverse(TimeInverse);
+ assert(Scale && "Unknow scale encountered");
+
+ auto TimeInverseMatcher = callExpr(callee(
+ functionDecl(hasName((llvm::Twine("::absl::") + TimeInverse).str()))
+ .bind("func_decl")));
+
+ // Match the cases where we know that the result is a 'Duration' and the
+ // first argument is a 'Time'. Just knowing the type of the first operand
+ // is not sufficient, since the second operand could be either a 'Time' or
+ // a 'Duration'. If we know the result is a 'Duration', we can then infer
+ // that the second operand must be a 'Time'.
+ auto CallMatcher =
+ callExpr(
+ callee(functionDecl(hasName(getDurationFactoryForScale(*Scale)))),
+ hasArgument(0, binaryOperator(hasOperatorName("-"),
+ hasLHS(TimeInverseMatcher))
+ .bind("binop")))
+ .bind("outer_call");
+ Finder->addMatcher(CallMatcher, this);
+
+ // Match cases where we know the second operand is a 'Time'. Since
+ // subtracting a 'Time' from a 'Duration' is not defined, in these cases,
+ // we always know the first operand is a 'Time' if the second is a 'Time'.
+ auto OperandMatcher =
+ binaryOperator(hasOperatorName("-"), hasRHS(TimeInverseMatcher))
+ .bind("binop");
+ Finder->addMatcher(OperandMatcher, this);
+ }
+}
+
+void TimeSubtractionCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *BinOp = Result.Nodes.getNodeAs<BinaryOperator>("binop");
+ std::string InverseName =
+ Result.Nodes.getNodeAs<FunctionDecl>("func_decl")->getNameAsString();
+ if (InsideMacroDefinition(Result, BinOp->getSourceRange()))
+ return;
+
+ llvm::Optional<DurationScale> Scale = getScaleForTimeInverse(InverseName);
+ if (!Scale)
+ return;
+
+ const auto *OuterCall = Result.Nodes.getNodeAs<CallExpr>("outer_call");
+ if (OuterCall) {
+ if (InsideMacroDefinition(Result, OuterCall->getSourceRange()))
+ return;
+
+ // We're working with the first case of matcher, and need to replace the
+ // entire 'Duration' factory call. (Which also means being careful about
+ // our order-of-operations and optionally putting in some parenthesis.
+ bool NeedParens = parensRequired(Result, OuterCall);
+
+ emitDiagnostic(
+ OuterCall,
+ (llvm::Twine(NeedParens ? "(" : "") +
+ rewriteExprFromNumberToTime(Result, *Scale, BinOp->getLHS()) + " - " +
+ rewriteExprFromNumberToTime(Result, *Scale, BinOp->getRHS()) +
+ (NeedParens ? ")" : ""))
+ .str());
+ } else {
+ // We're working with the second case of matcher, and either just need to
+ // change the arguments, or perhaps remove an outer function call. In the
+ // latter case (addressed first), we also need to worry about parenthesis.
+ const auto *MaybeCallArg = selectFirst<const CallExpr>(
+ "arg", match(expr(hasAncestor(
+ callExpr(callee(functionDecl(hasName(
+ getDurationFactoryForScale(*Scale)))))
+ .bind("arg"))),
+ *BinOp, *Result.Context));
+ if (MaybeCallArg && MaybeCallArg->getArg(0)->IgnoreImpCasts() == BinOp &&
+ !InsideMacroDefinition(Result, MaybeCallArg->getSourceRange())) {
+ // Handle the case where the matched expression is inside a call which
+ // converts it from the inverse to a Duration. In this case, we replace
+ // the outer with just the subtraction expresison, which gives the right
+ // type and scale, taking care again about parenthesis.
+ bool NeedParens = parensRequired(Result, MaybeCallArg);
+
+ emitDiagnostic(
+ MaybeCallArg,
+ (llvm::Twine(NeedParens ? "(" : "") +
+ rewriteExprFromNumberToTime(Result, *Scale, BinOp->getLHS()) +
+ " - " +
+ rewriteExprFromNumberToTime(Result, *Scale, BinOp->getRHS()) +
+ (NeedParens ? ")" : ""))
+ .str());
+ } else {
+ // In the last case, just convert the arguments and wrap the result in
+ // the correct inverse function.
+ emitDiagnostic(
+ BinOp,
+ (llvm::Twine(
+ getDurationInverseForScale(*Scale).second.str().substr(2)) +
+ "(" + rewriteExprFromNumberToTime(Result, *Scale, BinOp->getLHS()) +
+ " - " +
+ rewriteExprFromNumberToTime(Result, *Scale, BinOp->getRHS()) + ")")
+ .str());
+ }
+ }
+}
+
+} // namespace abseil
+} // namespace tidy
+} // namespace clang