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