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