• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
9 #include "UpgradeDurationConversionsCheck.h"
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 
registerMatchers(MatchFinder * Finder)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 
check(const MatchFinder::MatchResult & Result)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
167