clang-tools 22.0.0git
TimeSubtractionCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
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 clang::CharSourceRange::getCharRange(Range),
25 *Result.SourceManager, 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 "e",
37 match(expr(anyOf(
38 callExpr(hasParent(materializeTemporaryExpr(hasParent(
39 cxxConstructExpr(hasParent(exprWithCleanups(
40 hasParent(varDecl()))))))))
41 .bind("e"),
42 callExpr(hasParent(varDecl())).bind("e"))),
43 *Node, *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 "e",
52 match(
53 expr(anyOf(
54 expr(hasParent(materializeTemporaryExpr(
55 hasParent(cxxConstructExpr(
56 hasParent(callExpr()),
57 unless(hasParent(cxxOperatorCallExpr())))))))
58 .bind("e"),
59 expr(hasParent(callExpr()),
60 unless(hasParent(cxxOperatorCallExpr())))
61 .bind("e"))),
62 *Node, *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 "e",
70 match(expr(anyOf(
71 expr(hasParent(materializeTemporaryExpr(hasParent(
72 cxxConstructExpr(hasParent(exprWithCleanups(
73 hasParent(returnStmt()))))))))
74 .bind("e"),
75 expr(hasParent(returnStmt())).bind("e"))),
76 *Node, *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(TimeInverse);
98 assert(Scale && "Unknown scale encountered");
99
100 auto TimeInverseMatcher = callExpr(callee(
101 functionDecl(hasName((llvm::Twine("::absl::") + TimeInverse).str()))
102 .bind("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(functionDecl(hasName(getDurationFactoryForScale(*Scale)))),
112 hasArgument(0, binaryOperator(hasOperatorName("-"),
113 hasLHS(TimeInverseMatcher))
114 .bind("binop")))
115 .bind("outer_call");
116 Finder->addMatcher(CallMatcher, 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("-"), hasRHS(TimeInverseMatcher))
123 .bind("binop");
124 Finder->addMatcher(OperandMatcher, this);
125 }
126}
127
128void TimeSubtractionCheck::check(const MatchFinder::MatchResult &Result) {
129 const auto *BinOp = Result.Nodes.getNodeAs<BinaryOperator>("binop");
130 std::string InverseName =
131 Result.Nodes.getNodeAs<FunctionDecl>("func_decl")->getNameAsString();
132 if (insideMacroDefinition(Result, BinOp->getSourceRange()))
133 return;
134
135 std::optional<DurationScale> Scale = getScaleForTimeInverse(InverseName);
136 if (!Scale)
137 return;
138
139 const auto *OuterCall = Result.Nodes.getNodeAs<CallExpr>("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, BinOp->getLHS()) + " - " +
153 rewriteExprFromNumberToTime(Result, *Scale, 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 "arg", match(expr(hasAncestor(
162 callExpr(callee(functionDecl(hasName(
164 .bind("arg"))),
165 *BinOp, *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 MaybeCallArg,
176 (llvm::Twine(NeedParens ? "(" : "") +
177 rewriteExprFromNumberToTime(Result, *Scale, BinOp->getLHS()) +
178 " - " +
179 rewriteExprFromNumberToTime(Result, *Scale, 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).second.str().substr(2)) +
189 "(" + rewriteExprFromNumberToTime(Result, *Scale, BinOp->getLHS()) +
190 " - " +
191 rewriteExprFromNumberToTime(Result, *Scale, BinOp->getRHS()) + ")")
192 .str());
193 }
194 }
195}
196
197} // namespace clang::tidy::abseil
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
const std::pair< llvm::StringRef, llvm::StringRef > & getDurationInverseForScale(DurationScale Scale)
Given a Scale return the fully qualified inverse functions for it.
std::optional< DurationScale > getScaleForTimeInverse(llvm::StringRef Name)
Given the name of an inverse Time function (e.g., ToUnixSeconds), return its DurationScale,...
static bool isConstructorAssignment(const MatchFinder::MatchResult &Result, const Expr *Node)
static bool parensRequired(const MatchFinder::MatchResult &Result, const Expr *Node)
static bool isArgument(const MatchFinder::MatchResult &Result, const Expr *Node)
llvm::StringRef getDurationFactoryForScale(DurationScale Scale)
Returns the factory function name for a given Scale.
static bool isReturn(const MatchFinder::MatchResult &Result, const Expr *Node)
static bool insideMacroDefinition(const MatchFinder::MatchResult &Result, SourceRange Range)
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,...