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