TimeSubtractionCheck.cpp 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. //===--- TimeSubtractionCheck.cpp - clang-tidy ----------------------------===//
  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. #include "TimeSubtractionCheck.h"
  9. #include "DurationRewriter.h"
  10. #include "clang/AST/ASTContext.h"
  11. #include "clang/ASTMatchers/ASTMatchFinder.h"
  12. #include "clang/Lex/Lexer.h"
  13. #include "clang/Tooling/FixIt.h"
  14. #include <optional>
  15. using namespace clang::ast_matchers;
  16. namespace clang::tidy::abseil {
  17. // Returns `true` if `Range` is inside a macro definition.
  18. static bool insideMacroDefinition(const MatchFinder::MatchResult &Result,
  19. SourceRange Range) {
  20. return !clang::Lexer::makeFileCharRange(
  21. clang::CharSourceRange::getCharRange(Range),
  22. *Result.SourceManager, Result.Context->getLangOpts())
  23. .isValid();
  24. }
  25. static bool isConstructorAssignment(const MatchFinder::MatchResult &Result,
  26. const Expr *Node) {
  27. // For C++14 and earlier there are elidable constructors that must be matched
  28. // in hasParent. The elidable constructors do not exist in C++17 and later and
  29. // therefore an additional check that does not match against the elidable
  30. // constructors are needed for this case.
  31. return selectFirst<const Expr>(
  32. "e",
  33. match(expr(anyOf(
  34. callExpr(hasParent(materializeTemporaryExpr(hasParent(
  35. cxxConstructExpr(hasParent(exprWithCleanups(
  36. hasParent(varDecl()))))))))
  37. .bind("e"),
  38. callExpr(hasParent(varDecl())).bind("e"))),
  39. *Node, *Result.Context)) != nullptr;
  40. }
  41. static bool isArgument(const MatchFinder::MatchResult &Result,
  42. const Expr *Node) {
  43. // For the same reason as in isConstructorAssignment two AST shapes need to be
  44. // matched here.
  45. return selectFirst<const Expr>(
  46. "e",
  47. match(
  48. expr(anyOf(
  49. expr(hasParent(materializeTemporaryExpr(
  50. hasParent(cxxConstructExpr(
  51. hasParent(callExpr()),
  52. unless(hasParent(cxxOperatorCallExpr())))))))
  53. .bind("e"),
  54. expr(hasParent(callExpr()),
  55. unless(hasParent(cxxOperatorCallExpr())))
  56. .bind("e"))),
  57. *Node, *Result.Context)) != nullptr;
  58. }
  59. static bool isReturn(const MatchFinder::MatchResult &Result, const Expr *Node) {
  60. // For the same reason as in isConstructorAssignment two AST shapes need to be
  61. // matched here.
  62. return selectFirst<const Expr>(
  63. "e",
  64. match(expr(anyOf(
  65. expr(hasParent(materializeTemporaryExpr(hasParent(
  66. cxxConstructExpr(hasParent(exprWithCleanups(
  67. hasParent(returnStmt()))))))))
  68. .bind("e"),
  69. expr(hasParent(returnStmt())).bind("e"))),
  70. *Node, *Result.Context)) != nullptr;
  71. }
  72. static bool parensRequired(const MatchFinder::MatchResult &Result,
  73. const Expr *Node) {
  74. // TODO: Figure out any more contexts in which we can omit the surrounding
  75. // parentheses.
  76. return !(isConstructorAssignment(Result, Node) || isArgument(Result, Node) ||
  77. isReturn(Result, Node));
  78. }
  79. void TimeSubtractionCheck::emitDiagnostic(const Expr *Node,
  80. llvm::StringRef Replacement) {
  81. diag(Node->getBeginLoc(), "perform subtraction in the time domain")
  82. << FixItHint::CreateReplacement(Node->getSourceRange(), Replacement);
  83. }
  84. void TimeSubtractionCheck::registerMatchers(MatchFinder *Finder) {
  85. for (const char *ScaleName :
  86. {"Hours", "Minutes", "Seconds", "Millis", "Micros", "Nanos"}) {
  87. std::string TimeInverse = (llvm::Twine("ToUnix") + ScaleName).str();
  88. std::optional<DurationScale> Scale = getScaleForTimeInverse(TimeInverse);
  89. assert(Scale && "Unknown scale encountered");
  90. auto TimeInverseMatcher = callExpr(callee(
  91. functionDecl(hasName((llvm::Twine("::absl::") + TimeInverse).str()))
  92. .bind("func_decl")));
  93. // Match the cases where we know that the result is a 'Duration' and the
  94. // first argument is a 'Time'. Just knowing the type of the first operand
  95. // is not sufficient, since the second operand could be either a 'Time' or
  96. // a 'Duration'. If we know the result is a 'Duration', we can then infer
  97. // that the second operand must be a 'Time'.
  98. auto CallMatcher =
  99. callExpr(
  100. callee(functionDecl(hasName(getDurationFactoryForScale(*Scale)))),
  101. hasArgument(0, binaryOperator(hasOperatorName("-"),
  102. hasLHS(TimeInverseMatcher))
  103. .bind("binop")))
  104. .bind("outer_call");
  105. Finder->addMatcher(CallMatcher, this);
  106. // Match cases where we know the second operand is a 'Time'. Since
  107. // subtracting a 'Time' from a 'Duration' is not defined, in these cases,
  108. // we always know the first operand is a 'Time' if the second is a 'Time'.
  109. auto OperandMatcher =
  110. binaryOperator(hasOperatorName("-"), hasRHS(TimeInverseMatcher))
  111. .bind("binop");
  112. Finder->addMatcher(OperandMatcher, this);
  113. }
  114. }
  115. void TimeSubtractionCheck::check(const MatchFinder::MatchResult &Result) {
  116. const auto *BinOp = Result.Nodes.getNodeAs<BinaryOperator>("binop");
  117. std::string InverseName =
  118. Result.Nodes.getNodeAs<FunctionDecl>("func_decl")->getNameAsString();
  119. if (insideMacroDefinition(Result, BinOp->getSourceRange()))
  120. return;
  121. std::optional<DurationScale> Scale = getScaleForTimeInverse(InverseName);
  122. if (!Scale)
  123. return;
  124. const auto *OuterCall = Result.Nodes.getNodeAs<CallExpr>("outer_call");
  125. if (OuterCall) {
  126. if (insideMacroDefinition(Result, OuterCall->getSourceRange()))
  127. return;
  128. // We're working with the first case of matcher, and need to replace the
  129. // entire 'Duration' factory call. (Which also means being careful about
  130. // our order-of-operations and optionally putting in some parenthesis.
  131. bool NeedParens = parensRequired(Result, OuterCall);
  132. emitDiagnostic(
  133. OuterCall,
  134. (llvm::Twine(NeedParens ? "(" : "") +
  135. rewriteExprFromNumberToTime(Result, *Scale, BinOp->getLHS()) + " - " +
  136. rewriteExprFromNumberToTime(Result, *Scale, BinOp->getRHS()) +
  137. (NeedParens ? ")" : ""))
  138. .str());
  139. } else {
  140. // We're working with the second case of matcher, and either just need to
  141. // change the arguments, or perhaps remove an outer function call. In the
  142. // latter case (addressed first), we also need to worry about parenthesis.
  143. const auto *MaybeCallArg = selectFirst<const CallExpr>(
  144. "arg", match(expr(hasAncestor(
  145. callExpr(callee(functionDecl(hasName(
  146. getDurationFactoryForScale(*Scale)))))
  147. .bind("arg"))),
  148. *BinOp, *Result.Context));
  149. if (MaybeCallArg && MaybeCallArg->getArg(0)->IgnoreImpCasts() == BinOp &&
  150. !insideMacroDefinition(Result, MaybeCallArg->getSourceRange())) {
  151. // Handle the case where the matched expression is inside a call which
  152. // converts it from the inverse to a Duration. In this case, we replace
  153. // the outer with just the subtraction expression, which gives the right
  154. // type and scale, taking care again about parenthesis.
  155. bool NeedParens = parensRequired(Result, MaybeCallArg);
  156. emitDiagnostic(
  157. MaybeCallArg,
  158. (llvm::Twine(NeedParens ? "(" : "") +
  159. rewriteExprFromNumberToTime(Result, *Scale, BinOp->getLHS()) +
  160. " - " +
  161. rewriteExprFromNumberToTime(Result, *Scale, BinOp->getRHS()) +
  162. (NeedParens ? ")" : ""))
  163. .str());
  164. } else {
  165. // In the last case, just convert the arguments and wrap the result in
  166. // the correct inverse function.
  167. emitDiagnostic(
  168. BinOp,
  169. (llvm::Twine(
  170. getDurationInverseForScale(*Scale).second.str().substr(2)) +
  171. "(" + rewriteExprFromNumberToTime(Result, *Scale, BinOp->getLHS()) +
  172. " - " +
  173. rewriteExprFromNumberToTime(Result, *Scale, BinOp->getRHS()) + ")")
  174. .str());
  175. }
  176. }
  177. }
  178. } // namespace clang::tidy::abseil