clang-tools  14.0.0git
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(TK_AsIs, implicitCastExpr(
104  anyOf(hasCastKind(CK_UserDefinedConversion),
105  has(implicitCastExpr(
106  hasCastKind(CK_UserDefinedConversion)))),
107  hasParent(callExpr(
108  callee(functionDecl(
109  DurationFactoryFunction(),
110  unless(hasParent(functionTemplateDecl())))),
111  hasArgument(0, expr().bind("arg")))))
112  .bind("OuterExpr")),
113  this);
114 }
115 
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  TraversalKindScope RAII(*Result.Context, TK_AsIs);
123 
124  const auto *ArgExpr = Result.Nodes.getNodeAs<Expr>("arg");
125  SourceLocation Loc = ArgExpr->getBeginLoc();
126 
127  const auto *OuterExpr = Result.Nodes.getNodeAs<Expr>("OuterExpr");
128 
129  if (!match(isInTemplateInstantiation(), *OuterExpr, *Result.Context)
130  .empty()) {
131  if (MatchedTemplateLocations.count(Loc) == 0) {
132  // For each location matched in a template instantiation, we check if the
133  // location can also be found in `MatchedTemplateLocations`. If it is not
134  // found, that means the expression did not create a match without the
135  // instantiation and depends on template parameters. A manual fix is
136  // probably required so we provide only a warning.
137  diag(Loc, Message);
138  }
139  return;
140  }
141 
142  // We gather source locations from template matches not in template
143  // instantiations for future matches.
144  internal::Matcher<Stmt> IsInsideTemplate =
145  hasAncestor(decl(anyOf(classTemplateDecl(), functionTemplateDecl())));
146  if (!match(IsInsideTemplate, *ArgExpr, *Result.Context).empty())
147  MatchedTemplateLocations.insert(Loc);
148 
149  DiagnosticBuilder Diag = diag(Loc, Message);
150  CharSourceRange SourceRange = Lexer::makeFileCharRange(
151  CharSourceRange::getTokenRange(ArgExpr->getSourceRange()),
152  *Result.SourceManager, Result.Context->getLangOpts());
153  if (SourceRange.isInvalid())
154  // An invalid source range likely means we are inside a macro body. A manual
155  // fix is likely needed so we do not create a fix-it hint.
156  return;
157 
158  Diag << FixItHint::CreateInsertion(SourceRange.getBegin(),
159  "static_cast<int64_t>(")
160  << FixItHint::CreateInsertion(SourceRange.getEnd(), ")");
161 }
162 
163 } // namespace abseil
164 } // namespace tidy
165 } // namespace clang
Loc
SourceLocation Loc
Definition: KernelNameRestrictionCheck.cpp:45
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:98
DurationRewriter.h
clang::clangd::check
bool check(llvm::StringRef File, llvm::function_ref< bool(const Position &)> ShouldCheckLine, const ThreadsafeFS &TFS, const ClangdLSPServer::Options &Opts, bool EnableCodeCompletion)
Definition: Check.cpp:258
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
UpgradeDurationConversionsCheck.h