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

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang-tools-extra/clang-tidy/abseil/TimeSubtractionCheck.cpp