1 //===--- TimeSubtractionCheck.cpp - clang-tidy ----------------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
16 using namespace clang::ast_matchers
;
22 // Returns `true` if `Range` is inside a macro definition.
23 static bool insideMacroDefinition(const MatchFinder::MatchResult
&Result
,
25 return !clang::Lexer::makeFileCharRange(
26 clang::CharSourceRange::getCharRange(Range
),
27 *Result
.SourceManager
, Result
.Context
->getLangOpts())
31 static bool isConstructorAssignment(const MatchFinder::MatchResult
&Result
,
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
>(
40 callExpr(hasParent(materializeTemporaryExpr(hasParent(
41 cxxConstructExpr(hasParent(exprWithCleanups(
42 hasParent(varDecl()))))))))
44 callExpr(hasParent(varDecl())).bind("e"))),
45 *Node
, *Result
.Context
)) != nullptr;
48 static bool isArgument(const MatchFinder::MatchResult
&Result
,
50 // For the same reason as in isConstructorAssignment two AST shapes need to be
52 return selectFirst
<const Expr
>(
56 expr(hasParent(materializeTemporaryExpr(
57 hasParent(cxxConstructExpr(
58 hasParent(callExpr()),
59 unless(hasParent(cxxOperatorCallExpr())))))))
61 expr(hasParent(callExpr()),
62 unless(hasParent(cxxOperatorCallExpr())))
64 *Node
, *Result
.Context
)) != nullptr;
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
70 return selectFirst
<const Expr
>(
73 expr(hasParent(materializeTemporaryExpr(hasParent(
74 cxxConstructExpr(hasParent(exprWithCleanups(
75 hasParent(returnStmt()))))))))
77 expr(hasParent(returnStmt())).bind("e"))),
78 *Node
, *Result
.Context
)) != nullptr;
81 static bool parensRequired(const MatchFinder::MatchResult
&Result
,
83 // TODO: Figure out any more contexts in which we can omit the surrounding
85 return !(isConstructorAssignment(Result
, Node
) || isArgument(Result
, Node
) ||
86 isReturn(Result
, Node
));
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
);
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");
102 auto TimeInverseMatcher
= callExpr(callee(
103 functionDecl(hasName((llvm::Twine("::absl::") + TimeInverse
).str()))
104 .bind("func_decl")));
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'.
113 callee(functionDecl(hasName(getDurationFactoryForScale(*Scale
)))),
114 hasArgument(0, binaryOperator(hasOperatorName("-"),
115 hasLHS(TimeInverseMatcher
))
118 Finder
->addMatcher(CallMatcher
, this);
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
))
126 Finder
->addMatcher(OperandMatcher
, this);
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()))
137 llvm::Optional
<DurationScale
> Scale
= getScaleForTimeInverse(InverseName
);
141 const auto *OuterCall
= Result
.Nodes
.getNodeAs
<CallExpr
>("outer_call");
143 if (insideMacroDefinition(Result
, OuterCall
->getSourceRange()))
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
);
153 (llvm::Twine(NeedParens
? "(" : "") +
154 rewriteExprFromNumberToTime(Result
, *Scale
, BinOp
->getLHS()) + " - " +
155 rewriteExprFromNumberToTime(Result
, *Scale
, BinOp
->getRHS()) +
156 (NeedParens
? ")" : ""))
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
)))))
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
);
178 (llvm::Twine(NeedParens
? "(" : "") +
179 rewriteExprFromNumberToTime(Result
, *Scale
, BinOp
->getLHS()) +
181 rewriteExprFromNumberToTime(Result
, *Scale
, BinOp
->getRHS()) +
182 (NeedParens
? ")" : ""))
185 // In the last case, just convert the arguments and wrap the result in
186 // the correct inverse function.
190 getDurationInverseForScale(*Scale
).second
.str().substr(2)) +
191 "(" + rewriteExprFromNumberToTime(Result
, *Scale
, BinOp
->getLHS()) +
193 rewriteExprFromNumberToTime(Result
, *Scale
, BinOp
->getRHS()) + ")")
199 } // namespace abseil