280 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 "MinMaxUseInitializerListCheck.h"10#include "../utils/ASTUtils.h"11#include "../utils/LexerUtils.h"12#include "clang/ASTMatchers/ASTMatchFinder.h"13#include "clang/Frontend/CompilerInstance.h"14#include "clang/Lex/Lexer.h"15 16using namespace clang;17 18namespace {19 20struct FindArgsResult {21 const Expr *First;22 const Expr *Last;23 const Expr *Compare;24 SmallVector<const clang::Expr *, 2> Args;25};26 27} // anonymous namespace28 29using namespace clang::ast_matchers;30 31namespace clang::tidy::modernize {32 33static FindArgsResult findArgs(const CallExpr *Call) {34 FindArgsResult Result;35 Result.First = nullptr;36 Result.Last = nullptr;37 Result.Compare = nullptr;38 39 // check if the function has initializer list argument40 if (Call->getNumArgs() < 3) {41 auto ArgIterator = Call->arguments().begin();42 43 const auto *InitListExpr =44 dyn_cast<CXXStdInitializerListExpr>(*ArgIterator);45 const auto *InitList =46 InitListExpr != nullptr47 ? dyn_cast<clang::InitListExpr>(48 InitListExpr->getSubExpr()->IgnoreImplicit())49 : nullptr;50 51 if (InitList) {52 Result.Args.append(InitList->inits().begin(), InitList->inits().end());53 Result.First = *ArgIterator;54 Result.Last = *ArgIterator;55 56 // check if there is a comparison argument57 std::advance(ArgIterator, 1);58 if (ArgIterator != Call->arguments().end())59 Result.Compare = *ArgIterator;60 61 return Result;62 }63 Result.Args = SmallVector<const Expr *>(Call->arguments());64 } else {65 // if it has 3 arguments then the last will be the comparison66 Result.Compare = *(std::next(Call->arguments().begin(), 2));67 Result.Args = SmallVector<const Expr *>(llvm::drop_end(Call->arguments()));68 }69 Result.First = Result.Args.front();70 Result.Last = Result.Args.back();71 72 return Result;73}74 75// Returns `true` as `first` only if a nested call to `std::min` or76// `std::max` was found. Checking if `FixItHint`s were generated is not enough,77// as the explicit casts that the check introduces may be generated without a78// nested `std::min` or `std::max` call.79static std::pair<bool, SmallVector<FixItHint>>80generateReplacements(const MatchFinder::MatchResult &Match,81 const CallExpr *TopCall, const FindArgsResult &Result,82 const bool IgnoreNonTrivialTypes,83 const std::uint64_t IgnoreTrivialTypesOfSizeAbove) {84 SmallVector<FixItHint> FixItHints;85 const SourceManager &SourceMngr = *Match.SourceManager;86 const LangOptions &LanguageOpts = Match.Context->getLangOpts();87 88 const QualType ResultType = TopCall->getDirectCallee()89 ->getReturnType()90 .getCanonicalType()91 .getNonReferenceType()92 .getUnqualifiedType();93 94 // check if the type is trivial95 const bool IsResultTypeTrivial = ResultType.isTrivialType(*Match.Context);96 97 if ((!IsResultTypeTrivial && IgnoreNonTrivialTypes))98 return {false, FixItHints};99 100 if (IsResultTypeTrivial &&101 static_cast<std::uint64_t>(102 Match.Context->getTypeSizeInChars(ResultType).getQuantity()) >103 IgnoreTrivialTypesOfSizeAbove)104 return {false, FixItHints};105 106 bool FoundNestedCall = false;107 108 for (const Expr *Arg : Result.Args) {109 const auto *InnerCall = dyn_cast<CallExpr>(Arg->IgnoreParenImpCasts());110 111 // If the argument is not a nested call112 if (!InnerCall) {113 // check if typecast is required114 const QualType ArgType = Arg->IgnoreParenImpCasts()115 ->getType()116 .getCanonicalType()117 .getUnqualifiedType();118 119 if (ArgType == ResultType)120 continue;121 122 const StringRef ArgText = Lexer::getSourceText(123 CharSourceRange::getTokenRange(Arg->getSourceRange()), SourceMngr,124 LanguageOpts);125 126 const auto Replacement = Twine("static_cast<")127 .concat(ResultType.getAsString(LanguageOpts))128 .concat(">(")129 .concat(ArgText)130 .concat(")")131 .str();132 133 FixItHints.push_back(134 FixItHint::CreateReplacement(Arg->getSourceRange(), Replacement));135 continue;136 }137 138 // if the nested call is not the same as the top call139 if (InnerCall->getDirectCallee()->getQualifiedNameAsString() !=140 TopCall->getDirectCallee()->getQualifiedNameAsString())141 continue;142 143 const FindArgsResult InnerResult = findArgs(InnerCall);144 145 // if the nested call doesn't have arguments skip it146 if (!InnerResult.First || !InnerResult.Last)147 continue;148 149 // if the nested call doesn't have the same compare function150 if ((Result.Compare || InnerResult.Compare) &&151 !utils::areStatementsIdentical(Result.Compare, InnerResult.Compare,152 *Match.Context))153 continue;154 155 // We have found a nested call156 FoundNestedCall = true;157 158 // remove the function call159 FixItHints.push_back(160 FixItHint::CreateRemoval(InnerCall->getCallee()->getSourceRange()));161 162 // remove the parentheses163 const auto LParen = utils::lexer::findNextTokenSkippingComments(164 InnerCall->getCallee()->getEndLoc(), SourceMngr, LanguageOpts);165 if (LParen.has_value() && LParen->is(tok::l_paren))166 FixItHints.push_back(167 FixItHint::CreateRemoval(SourceRange(LParen->getLocation())));168 FixItHints.push_back(169 FixItHint::CreateRemoval(SourceRange(InnerCall->getRParenLoc())));170 171 // if the inner call has an initializer list arg172 if (InnerResult.First == InnerResult.Last) {173 // remove the initializer list braces174 FixItHints.push_back(FixItHint::CreateRemoval(175 CharSourceRange::getTokenRange(InnerResult.First->getBeginLoc())));176 FixItHints.push_back(FixItHint::CreateRemoval(177 CharSourceRange::getTokenRange(InnerResult.First->getEndLoc())));178 }179 180 const auto [_, InnerReplacements] = generateReplacements(181 Match, InnerCall, InnerResult, IgnoreNonTrivialTypes,182 IgnoreTrivialTypesOfSizeAbove);183 184 FixItHints.append(InnerReplacements);185 186 if (InnerResult.Compare) {187 // find the comma after the value arguments188 const auto Comma = utils::lexer::findNextTokenSkippingComments(189 InnerResult.Last->getEndLoc(), SourceMngr, LanguageOpts);190 191 // remove the comma and the comparison192 if (Comma.has_value() && Comma->is(tok::comma))193 FixItHints.push_back(194 FixItHint::CreateRemoval(SourceRange(Comma->getLocation())));195 196 FixItHints.push_back(197 FixItHint::CreateRemoval(InnerResult.Compare->getSourceRange()));198 }199 }200 201 return {FoundNestedCall, FixItHints};202}203 204MinMaxUseInitializerListCheck::MinMaxUseInitializerListCheck(205 StringRef Name, ClangTidyContext *Context)206 : ClangTidyCheck(Name, Context),207 IgnoreNonTrivialTypes(Options.get("IgnoreNonTrivialTypes", true)),208 IgnoreTrivialTypesOfSizeAbove(209 Options.get("IgnoreTrivialTypesOfSizeAbove", 32L)),210 Inserter(Options.getLocalOrGlobal("IncludeStyle",211 utils::IncludeSorter::IS_LLVM),212 areDiagsSelfContained()) {}213 214void MinMaxUseInitializerListCheck::storeOptions(215 ClangTidyOptions::OptionMap &Opts) {216 Options.store(Opts, "IgnoreNonTrivialTypes", IgnoreNonTrivialTypes);217 Options.store(Opts, "IgnoreTrivialTypesOfSizeAbove",218 IgnoreTrivialTypesOfSizeAbove);219 Options.store(Opts, "IncludeStyle", Inserter.getStyle());220}221 222void MinMaxUseInitializerListCheck::registerMatchers(MatchFinder *Finder) {223 auto CreateMatcher = [](const StringRef FunctionName) {224 auto FuncDecl = functionDecl(hasName(FunctionName));225 auto Expression = callExpr(callee(FuncDecl));226 227 return callExpr(callee(FuncDecl),228 anyOf(hasArgument(0, Expression),229 hasArgument(1, Expression),230 hasArgument(0, cxxStdInitializerListExpr())),231 unless(hasParent(Expression)))232 .bind("topCall");233 };234 235 Finder->addMatcher(CreateMatcher("::std::max"), this);236 Finder->addMatcher(CreateMatcher("::std::min"), this);237}238 239void MinMaxUseInitializerListCheck::registerPPCallbacks(240 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {241 Inserter.registerPreprocessor(PP);242}243 244void MinMaxUseInitializerListCheck::check(245 const MatchFinder::MatchResult &Match) {246 const auto *TopCall = Match.Nodes.getNodeAs<CallExpr>("topCall");247 248 const FindArgsResult Result = findArgs(TopCall);249 const auto [FoundNestedCall, Replacements] =250 generateReplacements(Match, TopCall, Result, IgnoreNonTrivialTypes,251 IgnoreTrivialTypesOfSizeAbove);252 253 if (!FoundNestedCall)254 return;255 256 const DiagnosticBuilder Diagnostic =257 diag(TopCall->getBeginLoc(),258 "do not use nested 'std::%0' calls, use an initializer list instead")259 << TopCall->getDirectCallee()->getName()260 << Inserter.createIncludeInsertion(261 Match.SourceManager->getFileID(TopCall->getBeginLoc()),262 "<algorithm>");263 264 // if the top call doesn't have an initializer list argument265 if (Result.First != Result.Last) {266 // add { and } insertions267 Diagnostic << FixItHint::CreateInsertion(Result.First->getBeginLoc(), "{");268 269 Diagnostic << FixItHint::CreateInsertion(270 Lexer::getLocForEndOfToken(Result.Last->getEndLoc(), 0,271 *Match.SourceManager,272 Match.Context->getLangOpts()),273 "}");274 }275 276 Diagnostic << Replacements;277}278 279} // namespace clang::tidy::modernize280