1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
|
//===--- UpgradeDurationConversionsCheck.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 "UpgradeDurationConversionsCheck.h"
#include "DurationRewriter.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/Lexer.h"
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace abseil {
void UpgradeDurationConversionsCheck::registerMatchers(MatchFinder *Finder) {
// For the arithmetic calls, we match only the uses of the templated operators
// where the template parameter is not a built-in type. This means the
// instantiation makes use of an available user defined conversion to
// `int64_t`.
//
// The implementation of these templates will be updated to fail SFINAE for
// non-integral types. We match them to suggest an explicit cast.
// Match expressions like `a *= b` and `a /= b` where `a` has type
// `absl::Duration` and `b` is not of a built-in type.
Finder->addMatcher(
cxxOperatorCallExpr(
argumentCountIs(2),
hasArgument(
0, expr(hasType(cxxRecordDecl(hasName("::absl::Duration"))))),
hasArgument(1, expr().bind("arg")),
callee(functionDecl(
hasParent(functionTemplateDecl()),
unless(hasTemplateArgument(0, refersToType(builtinType()))),
hasAnyName("operator*=", "operator/="))))
.bind("OuterExpr"),
this);
// Match expressions like `a.operator*=(b)` and `a.operator/=(b)` where `a`
// has type `absl::Duration` and `b` is not of a built-in type.
Finder->addMatcher(
cxxMemberCallExpr(
callee(cxxMethodDecl(
ofClass(cxxRecordDecl(hasName("::absl::Duration"))),
hasParent(functionTemplateDecl()),
unless(hasTemplateArgument(0, refersToType(builtinType()))),
hasAnyName("operator*=", "operator/="))),
argumentCountIs(1), hasArgument(0, expr().bind("arg")))
.bind("OuterExpr"),
this);
// Match expressions like `a * b`, `a / b`, `operator*(a, b)`, and
// `operator/(a, b)` where `a` has type `absl::Duration` and `b` is not of a
// built-in type.
Finder->addMatcher(
callExpr(callee(functionDecl(
hasParent(functionTemplateDecl()),
unless(hasTemplateArgument(0, refersToType(builtinType()))),
hasAnyName("::absl::operator*", "::absl::operator/"))),
argumentCountIs(2),
hasArgument(0, expr(hasType(
cxxRecordDecl(hasName("::absl::Duration"))))),
hasArgument(1, expr().bind("arg")))
.bind("OuterExpr"),
this);
// Match expressions like `a * b` and `operator*(a, b)` where `a` is not of a
// built-in type and `b` has type `absl::Duration`.
Finder->addMatcher(
callExpr(callee(functionDecl(
hasParent(functionTemplateDecl()),
unless(hasTemplateArgument(0, refersToType(builtinType()))),
hasName("::absl::operator*"))),
argumentCountIs(2), hasArgument(0, expr().bind("arg")),
hasArgument(1, expr(hasType(
cxxRecordDecl(hasName("::absl::Duration"))))))
.bind("OuterExpr"),
this);
// For the factory functions, we match only the non-templated overloads that
// take an `int64_t` parameter. Within these calls, we care about implicit
// casts through a user defined conversion to `int64_t`.
//
// The factory functions will be updated to be templated and SFINAE on whether
// the template parameter is an integral type. This complements the already
// existing templated overloads that only accept floating point types.
// Match calls like:
// `absl::Nanoseconds(x)`
// `absl::Microseconds(x)`
// `absl::Milliseconds(x)`
// `absl::Seconds(x)`
// `absl::Minutes(x)`
// `absl::Hours(x)`
// where `x` is not of a built-in type.
Finder->addMatcher(
traverse(TK_AsIs, implicitCastExpr(
anyOf(hasCastKind(CK_UserDefinedConversion),
has(implicitCastExpr(
hasCastKind(CK_UserDefinedConversion)))),
hasParent(callExpr(
callee(functionDecl(
DurationFactoryFunction(),
unless(hasParent(functionTemplateDecl())))),
hasArgument(0, expr().bind("arg")))))
.bind("OuterExpr")),
this);
}
void UpgradeDurationConversionsCheck::check(
const MatchFinder::MatchResult &Result) {
const llvm::StringRef Message =
"implicit conversion to 'int64_t' is deprecated in this context; use an "
"explicit cast instead";
TraversalKindScope RAII(*Result.Context, TK_AsIs);
const auto *ArgExpr = Result.Nodes.getNodeAs<Expr>("arg");
SourceLocation Loc = ArgExpr->getBeginLoc();
const auto *OuterExpr = Result.Nodes.getNodeAs<Expr>("OuterExpr");
if (!match(isInTemplateInstantiation(), *OuterExpr, *Result.Context)
.empty()) {
if (MatchedTemplateLocations.count(Loc) == 0) {
// For each location matched in a template instantiation, we check if the
// location can also be found in `MatchedTemplateLocations`. If it is not
// found, that means the expression did not create a match without the
// instantiation and depends on template parameters. A manual fix is
// probably required so we provide only a warning.
diag(Loc, Message);
}
return;
}
// We gather source locations from template matches not in template
// instantiations for future matches.
internal::Matcher<Stmt> IsInsideTemplate =
hasAncestor(decl(anyOf(classTemplateDecl(), functionTemplateDecl())));
if (!match(IsInsideTemplate, *ArgExpr, *Result.Context).empty())
MatchedTemplateLocations.insert(Loc);
DiagnosticBuilder Diag = diag(Loc, Message);
CharSourceRange SourceRange = Lexer::makeFileCharRange(
CharSourceRange::getTokenRange(ArgExpr->getSourceRange()),
*Result.SourceManager, Result.Context->getLangOpts());
if (SourceRange.isInvalid())
// An invalid source range likely means we are inside a macro body. A manual
// fix is likely needed so we do not create a fix-it hint.
return;
Diag << FixItHint::CreateInsertion(SourceRange.getBegin(),
"static_cast<int64_t>(")
<< FixItHint::CreateInsertion(SourceRange.getEnd(), ")");
}
} // namespace abseil
} // namespace tidy
} // namespace clang
|