Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[clang-tidy] Add the abseil-time-subtraction check
Differential Revision: https://reviews.llvm.org/D58137 llvm-svn: 355024
- Loading branch information
Hyrum Wright
committed
Feb 27, 2019
1 parent
ac552f7
commit c526e02
Showing
11 changed files
with
448 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
181 changes: 181 additions & 0 deletions
181
clang-tools-extra/clang-tidy/abseil/TimeSubtractionCheck.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
//===--- TimeSubtractionCheck.cpp - clang-tidy ----------------------------===// | ||
// | ||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
// See https://llvm.org/LICENSE.txt for license information. | ||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#include "TimeSubtractionCheck.h" | ||
#include "DurationRewriter.h" | ||
#include "clang/AST/ASTContext.h" | ||
#include "clang/ASTMatchers/ASTMatchFinder.h" | ||
#include "clang/Tooling/FixIt.h" | ||
|
||
using namespace clang::ast_matchers; | ||
|
||
namespace clang { | ||
namespace tidy { | ||
namespace abseil { | ||
|
||
// Returns `true` if `Range` is inside a macro definition. | ||
static bool InsideMacroDefinition(const MatchFinder::MatchResult &Result, | ||
SourceRange Range) { | ||
return !clang::Lexer::makeFileCharRange( | ||
clang::CharSourceRange::getCharRange(Range), | ||
*Result.SourceManager, Result.Context->getLangOpts()) | ||
.isValid(); | ||
} | ||
|
||
static bool isConstructorAssignment(const MatchFinder::MatchResult &Result, | ||
const Expr *Node) { | ||
return selectFirst<const Expr>( | ||
"e", match(expr(hasParent(materializeTemporaryExpr(hasParent( | ||
cxxConstructExpr(hasParent(exprWithCleanups( | ||
hasParent(varDecl())))))))) | ||
.bind("e"), | ||
*Node, *Result.Context)) != nullptr; | ||
} | ||
|
||
static bool isArgument(const MatchFinder::MatchResult &Result, | ||
const Expr *Node) { | ||
return selectFirst<const Expr>( | ||
"e", | ||
match(expr(hasParent( | ||
materializeTemporaryExpr(hasParent(cxxConstructExpr( | ||
hasParent(callExpr()), | ||
unless(hasParent(cxxOperatorCallExpr()))))))) | ||
.bind("e"), | ||
*Node, *Result.Context)) != nullptr; | ||
} | ||
|
||
static bool isReturn(const MatchFinder::MatchResult &Result, const Expr *Node) { | ||
return selectFirst<const Expr>( | ||
"e", match(expr(hasParent(materializeTemporaryExpr(hasParent( | ||
cxxConstructExpr(hasParent(exprWithCleanups( | ||
hasParent(returnStmt())))))))) | ||
.bind("e"), | ||
*Node, *Result.Context)) != nullptr; | ||
} | ||
|
||
static bool parensRequired(const MatchFinder::MatchResult &Result, | ||
const Expr *Node) { | ||
// TODO: Figure out any more contexts in which we can omit the surrounding | ||
// parentheses. | ||
return !(isConstructorAssignment(Result, Node) || isArgument(Result, Node) || | ||
isReturn(Result, Node)); | ||
} | ||
|
||
void TimeSubtractionCheck::emitDiagnostic(const Expr *Node, | ||
llvm::StringRef Replacement) { | ||
diag(Node->getBeginLoc(), "perform subtraction in the time domain") | ||
<< FixItHint::CreateReplacement(Node->getSourceRange(), Replacement); | ||
} | ||
|
||
void TimeSubtractionCheck::registerMatchers(MatchFinder *Finder) { | ||
for (auto ScaleName : | ||
{"Hours", "Minutes", "Seconds", "Millis", "Micros", "Nanos"}) { | ||
std::string TimeInverse = (llvm::Twine("ToUnix") + ScaleName).str(); | ||
llvm::Optional<DurationScale> Scale = getScaleForTimeInverse(TimeInverse); | ||
assert(Scale && "Unknow scale encountered"); | ||
|
||
auto TimeInverseMatcher = callExpr(callee( | ||
functionDecl(hasName((llvm::Twine("::absl::") + TimeInverse).str())) | ||
.bind("func_decl"))); | ||
|
||
// Match the cases where we know that the result is a 'Duration' and the | ||
// first argument is a 'Time'. Just knowing the type of the first operand | ||
// is not sufficient, since the second operand could be either a 'Time' or | ||
// a 'Duration'. If we know the result is a 'Duration', we can then infer | ||
// that the second operand must be a 'Time'. | ||
auto CallMatcher = | ||
callExpr( | ||
callee(functionDecl(hasName(getDurationFactoryForScale(*Scale)))), | ||
hasArgument(0, binaryOperator(hasOperatorName("-"), | ||
hasLHS(TimeInverseMatcher)) | ||
.bind("binop"))) | ||
.bind("outer_call"); | ||
Finder->addMatcher(CallMatcher, this); | ||
|
||
// Match cases where we know the second operand is a 'Time'. Since | ||
// subtracting a 'Time' from a 'Duration' is not defined, in these cases, | ||
// we always know the first operand is a 'Time' if the second is a 'Time'. | ||
auto OperandMatcher = | ||
binaryOperator(hasOperatorName("-"), hasRHS(TimeInverseMatcher)) | ||
.bind("binop"); | ||
Finder->addMatcher(OperandMatcher, this); | ||
} | ||
} | ||
|
||
void TimeSubtractionCheck::check(const MatchFinder::MatchResult &Result) { | ||
const auto *BinOp = Result.Nodes.getNodeAs<BinaryOperator>("binop"); | ||
std::string InverseName = | ||
Result.Nodes.getNodeAs<FunctionDecl>("func_decl")->getNameAsString(); | ||
if (InsideMacroDefinition(Result, BinOp->getSourceRange())) | ||
return; | ||
|
||
llvm::Optional<DurationScale> Scale = getScaleForTimeInverse(InverseName); | ||
if (!Scale) | ||
return; | ||
|
||
const auto *OuterCall = Result.Nodes.getNodeAs<CallExpr>("outer_call"); | ||
if (OuterCall) { | ||
if (InsideMacroDefinition(Result, OuterCall->getSourceRange())) | ||
return; | ||
|
||
// We're working with the first case of matcher, and need to replace the | ||
// entire 'Duration' factory call. (Which also means being careful about | ||
// our order-of-operations and optionally putting in some parenthesis. | ||
bool NeedParens = parensRequired(Result, OuterCall); | ||
|
||
emitDiagnostic( | ||
OuterCall, | ||
(llvm::Twine(NeedParens ? "(" : "") + | ||
rewriteExprFromNumberToTime(Result, *Scale, BinOp->getLHS()) + " - " + | ||
rewriteExprFromNumberToTime(Result, *Scale, BinOp->getRHS()) + | ||
(NeedParens ? ")" : "")) | ||
.str()); | ||
} else { | ||
// We're working with the second case of matcher, and either just need to | ||
// change the arguments, or perhaps remove an outer function call. In the | ||
// latter case (addressed first), we also need to worry about parenthesis. | ||
const auto *MaybeCallArg = selectFirst<const CallExpr>( | ||
"arg", match(expr(hasAncestor( | ||
callExpr(callee(functionDecl(hasName( | ||
getDurationFactoryForScale(*Scale))))) | ||
.bind("arg"))), | ||
*BinOp, *Result.Context)); | ||
if (MaybeCallArg && MaybeCallArg->getArg(0)->IgnoreImpCasts() == BinOp && | ||
!InsideMacroDefinition(Result, MaybeCallArg->getSourceRange())) { | ||
// Handle the case where the matched expression is inside a call which | ||
// converts it from the inverse to a Duration. In this case, we replace | ||
// the outer with just the subtraction expresison, which gives the right | ||
// type and scale, taking care again about parenthesis. | ||
bool NeedParens = parensRequired(Result, MaybeCallArg); | ||
|
||
emitDiagnostic( | ||
MaybeCallArg, | ||
(llvm::Twine(NeedParens ? "(" : "") + | ||
rewriteExprFromNumberToTime(Result, *Scale, BinOp->getLHS()) + | ||
" - " + | ||
rewriteExprFromNumberToTime(Result, *Scale, BinOp->getRHS()) + | ||
(NeedParens ? ")" : "")) | ||
.str()); | ||
} else { | ||
// In the last case, just convert the arguments and wrap the result in | ||
// the correct inverse function. | ||
emitDiagnostic( | ||
BinOp, | ||
(llvm::Twine( | ||
getDurationInverseForScale(*Scale).second.str().substr(2)) + | ||
"(" + rewriteExprFromNumberToTime(Result, *Scale, BinOp->getLHS()) + | ||
" - " + | ||
rewriteExprFromNumberToTime(Result, *Scale, BinOp->getRHS()) + ")") | ||
.str()); | ||
} | ||
} | ||
} | ||
|
||
} // namespace abseil | ||
} // namespace tidy | ||
} // namespace clang |
38 changes: 38 additions & 0 deletions
38
clang-tools-extra/clang-tidy/abseil/TimeSubtractionCheck.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
//===--- TimeSubtractionCheck.h - clang-tidy --------------------*- C++ -*-===// | ||
// | ||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
// See https://llvm.org/LICENSE.txt for license information. | ||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_TIMESUBTRACTIONCHECK_H | ||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_TIMESUBTRACTIONCHECK_H | ||
|
||
#include "../ClangTidy.h" | ||
|
||
namespace clang { | ||
namespace tidy { | ||
namespace abseil { | ||
|
||
/// Finds and fixes `absl::Time` subtraction expressions to do subtraction | ||
/// in the time domain instead of the numeric domain. | ||
/// | ||
/// For the user-facing documentation see: | ||
/// http://clang.llvm.org/extra/clang-tidy/checks/abseil-time-subtraction.html | ||
class TimeSubtractionCheck : public ClangTidyCheck { | ||
public: | ||
TimeSubtractionCheck(StringRef Name, ClangTidyContext *Context) | ||
: ClangTidyCheck(Name, Context) {} | ||
void registerMatchers(ast_matchers::MatchFinder *Finder) override; | ||
void check(const ast_matchers::MatchFinder::MatchResult &Result) override; | ||
|
||
private: | ||
void emitDiagnostic(const Expr* Node, llvm::StringRef Replacement); | ||
}; | ||
|
||
} // namespace abseil | ||
} // namespace tidy | ||
} // namespace clang | ||
|
||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_TIMESUBTRACTIONCHECK_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
37 changes: 37 additions & 0 deletions
37
clang-tools-extra/docs/clang-tidy/checks/abseil-time-subtraction.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
.. title:: clang-tidy - abseil-time-subtraction | ||
|
||
abseil-time-subtraction | ||
======================= | ||
|
||
Finds and fixes ``absl::Time`` subtraction expressions to do subtraction | ||
in the Time domain instead of the numeric domain. | ||
|
||
There are two cases of Time subtraction in which deduce additional type | ||
information: | ||
- When the result is an ``absl::Duration`` and the first argument is an | ||
``absl::Time``. | ||
- When the second argument is a ``absl::Time``. | ||
|
||
In the first case, we must know the result of the operation, since without that | ||
the second operand could be either an ``absl::Time`` or an ``absl::Duration``. | ||
In the second case, the first operand *must* be an ``absl::Time``, because | ||
subtracting an ``absl::Time`` from an ``absl::Duration`` is not defined. | ||
|
||
Examples: | ||
|
||
.. code-block:: c++ | ||
int x; | ||
absl::Time t; | ||
|
||
// Original - absl::Duration result and first operand is a absl::Time. | ||
absl::Duration d = absl::Seconds(absl::ToUnixSeconds(t) - x); | ||
|
||
// Suggestion - Perform subtraction in the Time domain instead. | ||
absl::Duration d = t - absl::FromUnixSeconds(x); | ||
|
||
|
||
// Original - Second operand is an absl::Time. | ||
int i = x - absl::ToUnixSeconds(t); | ||
|
||
// Suggestion - Perform subtraction in the Time domain instead. | ||
int i = absl::ToInt64Seconds(absl::FromUnixSeconds(x) - t); |
Oops, something went wrong.