162 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//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 operators21 // where the template parameter is not a built-in type. This means the22 // instantiation makes use of an available user defined conversion to23 // `int64_t`.24 //25 // The implementation of these templates will be updated to fail SFINAE for26 // non-integral types. We match them to suggest an explicit cast.27 28 // Match expressions like `a *= b` and `a /= b` where `a` has type29 // `absl::Duration` and `b` is not of a built-in type.30 Finder->addMatcher(31 cxxOperatorCallExpr(32 argumentCountIs(2),33 hasArgument(34 0, expr(hasType(cxxRecordDecl(hasName("::absl::Duration"))))),35 hasArgument(1, expr().bind("arg")),36 callee(functionDecl(37 hasParent(functionTemplateDecl()),38 unless(hasTemplateArgument(0, refersToType(builtinType()))),39 hasAnyName("operator*=", "operator/="))))40 .bind("OuterExpr"),41 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 cxxMemberCallExpr(47 callee(cxxMethodDecl(48 ofClass(cxxRecordDecl(hasName("::absl::Duration"))),49 hasParent(functionTemplateDecl()),50 unless(hasTemplateArgument(0, refersToType(builtinType()))),51 hasAnyName("operator*=", "operator/="))),52 argumentCountIs(1), hasArgument(0, expr().bind("arg")))53 .bind("OuterExpr"),54 this);55 56 // Match expressions like `a * b`, `a / b`, `operator*(a, b)`, and57 // `operator/(a, b)` where `a` has type `absl::Duration` and `b` is not of a58 // built-in type.59 Finder->addMatcher(60 callExpr(callee(functionDecl(61 hasParent(functionTemplateDecl()),62 unless(hasTemplateArgument(0, refersToType(builtinType()))),63 hasAnyName("::absl::operator*", "::absl::operator/"))),64 argumentCountIs(2),65 hasArgument(0, expr(hasType(66 cxxRecordDecl(hasName("::absl::Duration"))))),67 hasArgument(1, expr().bind("arg")))68 .bind("OuterExpr"),69 this);70 71 // Match expressions like `a * b` and `operator*(a, b)` where `a` is not of a72 // built-in type and `b` has type `absl::Duration`.73 Finder->addMatcher(74 callExpr(callee(functionDecl(75 hasParent(functionTemplateDecl()),76 unless(hasTemplateArgument(0, refersToType(builtinType()))),77 hasName("::absl::operator*"))),78 argumentCountIs(2), hasArgument(0, expr().bind("arg")),79 hasArgument(1, expr(hasType(80 cxxRecordDecl(hasName("::absl::Duration"))))))81 .bind("OuterExpr"),82 this);83 84 // For the factory functions, we match only the non-templated overloads that85 // take an `int64_t` parameter. Within these calls, we care about implicit86 // casts through a user defined conversion to `int64_t`.87 //88 // The factory functions will be updated to be templated and SFINAE on whether89 // the template parameter is an integral type. This complements the already90 // 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 traverse(TK_AsIs, implicitCastExpr(102 anyOf(hasCastKind(CK_UserDefinedConversion),103 has(implicitCastExpr(104 hasCastKind(CK_UserDefinedConversion)))),105 hasParent(callExpr(106 callee(functionDecl(107 durationFactoryFunction(),108 unless(hasParent(functionTemplateDecl())))),109 hasArgument(0, expr().bind("arg")))))110 .bind("OuterExpr")),111 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 const TraversalKindScope RAII(*Result.Context, TK_AsIs);121 122 const auto *ArgExpr = Result.Nodes.getNodeAs<Expr>("arg");123 const 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.contains(Loc)) {130 // For each location matched in a template instantiation, we check if the131 // location can also be found in `MatchedTemplateLocations`. If it is not132 // found, that means the expression did not create a match without the133 // instantiation and depends on template parameters. A manual fix is134 // 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 template141 // instantiations for future matches.142 const internal::Matcher<Stmt> IsInsideTemplate =143 hasAncestor(decl(anyOf(classTemplateDecl(), functionTemplateDecl())));144 if (!match(IsInsideTemplate, *ArgExpr, *Result.Context).empty())145 MatchedTemplateLocations.insert(Loc);146 147 const DiagnosticBuilder Diag = diag(Loc, Message);148 const 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 manual153 // 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 clang::tidy::abseil162