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
15using namespace clang::ast_matchers;
16
17namespace clang::tidy::abseil {
18
19void UpgradeDurationConversionsCheck::registerMatchers(MatchFinder *Finder) {
20 // For the arithmetic calls, we match only the uses of the templated operators
21 // where the template parameter is not a built-in type. This means the
22 // instantiation makes use of an available user defined conversion to
23 // `int64_t`.
24 //
25 // The implementation of these templates will be updated to fail SFINAE for
26 // non-integral types. We match them to suggest an explicit cast.
27
28 // Match expressions like `a *= b` and `a /= b` where `a` has type
29 // `absl::Duration` and `b` is not of a built-in type.
30 Finder->addMatcher(
31 NodeMatch: cxxOperatorCallExpr(
32 argumentCountIs(N: 2),
33 hasArgument(
34 N: 0, InnerMatcher: expr(hasType(InnerMatcher: cxxRecordDecl(hasName(Name: "::absl::Duration"))))),
35 hasArgument(N: 1, InnerMatcher: expr().bind(ID: "arg")),
36 callee(InnerMatcher: functionDecl(
37 hasParent(functionTemplateDecl()),
38 unless(hasTemplateArgument(N: 0, InnerMatcher: refersToType(InnerMatcher: builtinType()))),
39 hasAnyName("operator*=", "operator/="))))
40 .bind(ID: "OuterExpr"),
41 Action: this);
42
43 // Match expressions like `a.operator*=(b)` and `a.operator/=(b)` where `a`
44 // has type `absl::Duration` and `b` is not of a built-in type.
45 Finder->addMatcher(
46 NodeMatch: cxxMemberCallExpr(
47 callee(InnerMatcher: cxxMethodDecl(
48 ofClass(InnerMatcher: cxxRecordDecl(hasName(Name: "::absl::Duration"))),
49 hasParent(functionTemplateDecl()),
50 unless(hasTemplateArgument(N: 0, InnerMatcher: refersToType(InnerMatcher: builtinType()))),
51 hasAnyName("operator*=", "operator/="))),
52 argumentCountIs(N: 1), hasArgument(N: 0, InnerMatcher: expr().bind(ID: "arg")))
53 .bind(ID: "OuterExpr"),
54 Action: this);
55
56 // Match expressions like `a * b`, `a / b`, `operator*(a, b)`, and
57 // `operator/(a, b)` where `a` has type `absl::Duration` and `b` is not of a
58 // built-in type.
59 Finder->addMatcher(
60 NodeMatch: callExpr(callee(InnerMatcher: functionDecl(
61 hasParent(functionTemplateDecl()),
62 unless(hasTemplateArgument(N: 0, InnerMatcher: refersToType(InnerMatcher: builtinType()))),
63 hasAnyName("::absl::operator*", "::absl::operator/"))),
64 argumentCountIs(N: 2),
65 hasArgument(N: 0, InnerMatcher: expr(hasType(
66 InnerMatcher: cxxRecordDecl(hasName(Name: "::absl::Duration"))))),
67 hasArgument(N: 1, InnerMatcher: expr().bind(ID: "arg")))
68 .bind(ID: "OuterExpr"),
69 Action: this);
70
71 // Match expressions like `a * b` and `operator*(a, b)` where `a` is not of a
72 // built-in type and `b` has type `absl::Duration`.
73 Finder->addMatcher(
74 NodeMatch: callExpr(callee(InnerMatcher: functionDecl(
75 hasParent(functionTemplateDecl()),
76 unless(hasTemplateArgument(N: 0, InnerMatcher: refersToType(InnerMatcher: builtinType()))),
77 hasName(Name: "::absl::operator*"))),
78 argumentCountIs(N: 2), hasArgument(N: 0, InnerMatcher: expr().bind(ID: "arg")),
79 hasArgument(N: 1, InnerMatcher: expr(hasType(
80 InnerMatcher: cxxRecordDecl(hasName(Name: "::absl::Duration"))))))
81 .bind(ID: "OuterExpr"),
82 Action: this);
83
84 // For the factory functions, we match only the non-templated overloads that
85 // take an `int64_t` parameter. Within these calls, we care about implicit
86 // casts through a user defined conversion to `int64_t`.
87 //
88 // The factory functions will be updated to be templated and SFINAE on whether
89 // the template parameter is an integral type. This complements the already
90 // existing templated overloads that only accept floating point types.
91
92 // Match calls like:
93 // `absl::Nanoseconds(x)`
94 // `absl::Microseconds(x)`
95 // `absl::Milliseconds(x)`
96 // `absl::Seconds(x)`
97 // `absl::Minutes(x)`
98 // `absl::Hours(x)`
99 // where `x` is not of a built-in type.
100 Finder->addMatcher(
101 NodeMatch: traverse(TK: TK_AsIs, InnerMatcher: implicitCastExpr(
102 anyOf(hasCastKind(Kind: CK_UserDefinedConversion),
103 has(implicitCastExpr(
104 hasCastKind(Kind: CK_UserDefinedConversion)))),
105 hasParent(callExpr(
106 callee(InnerMatcher: functionDecl(
107 DurationFactoryFunction(),
108 unless(hasParent(functionTemplateDecl())))),
109 hasArgument(N: 0, InnerMatcher: expr().bind(ID: "arg")))))
110 .bind(ID: "OuterExpr")),
111 Action: this);
112}
113
114void UpgradeDurationConversionsCheck::check(
115 const MatchFinder::MatchResult &Result) {
116 const llvm::StringRef Message =
117 "implicit conversion to 'int64_t' is deprecated in this context; use an "
118 "explicit cast instead";
119
120 TraversalKindScope RAII(*Result.Context, TK_AsIs);
121
122 const auto *ArgExpr = Result.Nodes.getNodeAs<Expr>(ID: "arg");
123 SourceLocation Loc = ArgExpr->getBeginLoc();
124
125 const auto *OuterExpr = Result.Nodes.getNodeAs<Expr>(ID: "OuterExpr");
126
127 if (!match(Matcher: isInTemplateInstantiation(), Node: *OuterExpr, Context&: *Result.Context)
128 .empty()) {
129 if (MatchedTemplateLocations.count(V: Loc) == 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, Description: 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(Matcher: IsInsideTemplate, Node: *ArgExpr, Context&: *Result.Context).empty())
145 MatchedTemplateLocations.insert(V: Loc);
146
147 DiagnosticBuilder Diag = diag(Loc, Description: Message);
148 CharSourceRange SourceRange = Lexer::makeFileCharRange(
149 Range: CharSourceRange::getTokenRange(ArgExpr->getSourceRange()),
150 SM: *Result.SourceManager, LangOpts: 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(InsertionLoc: SourceRange.getBegin(),
157 Code: "static_cast<int64_t>(")
158 << FixItHint::CreateInsertion(InsertionLoc: SourceRange.getEnd(), Code: ")");
159}
160
161} // namespace clang::tidy::abseil
162

source code of clang-tools-extra/clang-tidy/abseil/UpgradeDurationConversionsCheck.cpp