• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
InsideMacroDefinition(const MatchFinder::MatchResult & Result,SourceRange Range)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 
isConstructorAssignment(const MatchFinder::MatchResult & Result,const Expr * Node)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 
isArgument(const MatchFinder::MatchResult & Result,const Expr * Node)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 
isReturn(const MatchFinder::MatchResult & Result,const Expr * Node)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 
parensRequired(const MatchFinder::MatchResult & Result,const Expr * Node)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 
emitDiagnostic(const Expr * Node,llvm::StringRef Replacement)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 
registerMatchers(MatchFinder * Finder)95 void TimeSubtractionCheck::registerMatchers(MatchFinder *Finder) {
96   for (auto 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 
check(const MatchFinder::MatchResult & Result)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 
151     emitDiagnostic(
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 
176       emitDiagnostic(
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.
187       emitDiagnostic(
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
202