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