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