================ @@ -0,0 +1,342 @@ +//===--- MinMaxUseInitializerListCheck.cpp - clang-tidy -------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "MinMaxUseInitializerListCheck.h" +#include "../utils/ASTUtils.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::modernize { + +struct FindArgsResult { + const Expr *First; + const Expr *Last; + const Expr *Compare; + std::vector<const Expr *> Args; +}; + +static const FindArgsResult findArgs(const CallExpr *Call); +static std::vector<std::pair<int, int>> +getCommentRanges(const std::string &source); +static bool +isPositionInComment(int position, + const std::vector<std::pair<int, int>> &commentRanges); +static void +removeCharacterFromSource(std::string &FunctionCallSource, + const std::vector<std::pair<int, int>> &CommentRanges, + char Character, const CallExpr *InnerCall, + std::vector<FixItHint> &Result, bool ReverseSearch); +static SourceLocation +getLocForEndOfToken(const Expr *expr, const MatchFinder::MatchResult &Match); +static const std::vector<FixItHint> +generateReplacement(const MatchFinder::MatchResult &Match, + const CallExpr *TopCall, const FindArgsResult &Result); + +MinMaxUseInitializerListCheck::MinMaxUseInitializerListCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + Inserter(Options.getLocalOrGlobal("IncludeStyle", + utils::IncludeSorter::IS_LLVM), + areDiagsSelfContained()) {} + +void MinMaxUseInitializerListCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IncludeStyle", Inserter.getStyle()); +} + +void MinMaxUseInitializerListCheck::registerMatchers(MatchFinder *Finder) { + auto CreateMatcher = [](const std::string &FunctionName) { + auto FuncDecl = functionDecl(hasName(FunctionName)); + auto Expression = callExpr(callee(FuncDecl)); + + return callExpr(callee(FuncDecl), + anyOf(hasArgument(0, Expression), + hasArgument(1, Expression), + hasArgument(0, cxxStdInitializerListExpr())), + unless(hasParent(Expression))) + .bind("topCall"); + }; + + Finder->addMatcher(CreateMatcher("::std::max"), this); + Finder->addMatcher(CreateMatcher("::std::min"), this); +} + +void MinMaxUseInitializerListCheck::registerPPCallbacks( + const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { + Inserter.registerPreprocessor(PP); +} + +void MinMaxUseInitializerListCheck::check( + const MatchFinder::MatchResult &Match) { + + const auto *TopCall = Match.Nodes.getNodeAs<CallExpr>("topCall"); + + // if topcall in macro ignore + if (TopCall->getBeginLoc().isMacroID()) { + return; + } + + FindArgsResult Result = findArgs(TopCall); + const std::vector<FixItHint> Replacement = + generateReplacement(Match, TopCall, Result); + + // if only changes are inserting '{' and '}' then ignore + if (Replacement.size() <= 2) { + return; + } + + const DiagnosticBuilder Diagnostic = + diag(TopCall->getBeginLoc(), + "do not use nested 'std::%0' calls, use initializer lists instead") + << TopCall->getDirectCallee()->getName() + << Inserter.createIncludeInsertion( + Match.SourceManager->getFileID(TopCall->getBeginLoc()), + "<algorithm>"); + + for (const auto &FixIt : Replacement) { + Diagnostic << FixIt; + } +} + +static const FindArgsResult findArgs(const CallExpr *Call) { + FindArgsResult Result; + Result.First = nullptr; + Result.Last = nullptr; + Result.Compare = nullptr; + + if (Call->getNumArgs() == 3) { + auto ArgIterator = Call->arguments().begin(); + std::advance(ArgIterator, 2); + Result.Compare = *ArgIterator; + } else { + auto ArgIterator = Call->arguments().begin(); + + if (const auto *InitListExpr = + dyn_cast<CXXStdInitializerListExpr>(*ArgIterator)) { + if (const auto *TempExpr = + dyn_cast<MaterializeTemporaryExpr>(InitListExpr->getSubExpr())) { + if (const auto *InitList = + dyn_cast<clang::InitListExpr>(TempExpr->getSubExpr())) { + for (const Expr *Init : InitList->inits()) { + Result.Args.push_back(Init); + } + Result.First = *ArgIterator; + Result.Last = *ArgIterator; + + std::advance(ArgIterator, 1); + if (ArgIterator != Call->arguments().end()) { + Result.Compare = *ArgIterator; + } + return Result; + } + } + } + } + + for (const Expr *Arg : Call->arguments()) { + if (!Result.First) + Result.First = Arg; + + if (Arg == Result.Compare) + continue; + + Result.Args.push_back(Arg); + Result.Last = Arg; + } + + return Result; +} + +static std::vector<std::pair<int, int>> +getCommentRanges(const std::string &source) { + std::vector<std::pair<int, int>> commentRanges; + bool inComment = false; + bool singleLineComment = false; + int start = -1; + for (unsigned long i = 0; i < source.size(); i++) { + switch (source[i]) { + case '/': + if (source[i + 1] == '/') + singleLineComment = true; + else if (source[i + 1] == '*') + inComment = true; + break; + case '*': + if (source[i + 1] == '/') + inComment = false; + break; + case '\n': + singleLineComment = false; + break; + } + if (inComment || singleLineComment) { + if (start == -1) + start = i; + } else if (start != -1) { + commentRanges.push_back({start, i}); + start = -1; + } + } + return commentRanges; +} + +static bool +isPositionInComment(int position, + const std::vector<std::pair<int, int>> &commentRanges) { + return std::any_of(commentRanges.begin(), commentRanges.end(), + [position](const auto &range) { + return position >= range.first && + position <= range.second; + }); +} + +static void +removeCharacterFromSource(std::string &FunctionCallSource, + const std::vector<std::pair<int, int>> &CommentRanges, + char Character, const CallExpr *InnerCall, + std::vector<FixItHint> &Result, bool ReverseSearch) { + size_t CurrentSearch = ReverseSearch ? FunctionCallSource.size() : 0; + while ((CurrentSearch = + ReverseSearch + ? FunctionCallSource.rfind(Character, CurrentSearch - 1) + : FunctionCallSource.find(Character, CurrentSearch + 1)) != + std::string::npos) { + if (!isPositionInComment(CurrentSearch, CommentRanges)) { + Result.push_back(FixItHint::CreateRemoval(CharSourceRange::getCharRange( + InnerCall->getSourceRange().getBegin().getLocWithOffset( + CurrentSearch), + InnerCall->getSourceRange().getBegin().getLocWithOffset( + CurrentSearch + 1)))); + break; + } + } +} + +SourceLocation getLocForEndOfToken(const Expr *expr, + const MatchFinder::MatchResult &Match) { + return Lexer::getLocForEndOfToken(expr->getEndLoc(), 0, *Match.SourceManager, + Match.Context->getLangOpts()); +} + +static const std::vector<FixItHint> +generateReplacement(const MatchFinder::MatchResult &Match, + const CallExpr *TopCall, const FindArgsResult &Result) { + std::vector<FixItHint> FixItHints; + + const QualType ResultType = TopCall->getDirectCallee() + ->getReturnType() + .getNonReferenceType() + .getUnqualifiedType() + .getCanonicalType(); + const bool IsInitializerList = Result.First == Result.Last; + + if (!IsInitializerList) + FixItHints.push_back( + FixItHint::CreateInsertion(Result.First->getBeginLoc(), "{")); + + for (const Expr *Arg : Result.Args) { + std::string ArgText = + Lexer::getSourceText( + CharSourceRange::getTokenRange(Arg->getSourceRange()), + *Match.SourceManager, Match.Context->getLangOpts()) + .str(); + + if (const auto InnerCall = dyn_cast<CallExpr>(Arg->IgnoreParenImpCasts())) { + const auto InnerResult = findArgs(InnerCall); + const auto InnerReplacement = + generateReplacement(Match, InnerCall, InnerResult); + if (InnerCall->getDirectCallee()->getQualifiedNameAsString() == + TopCall->getDirectCallee()->getQualifiedNameAsString() && + ((!Result.Compare && !InnerResult.Compare) || + utils::areStatementsIdentical(Result.Compare, InnerResult.Compare, + *Match.Context))) { + std::vector<std::pair<int, int>> CommentRanges = + getCommentRanges(ArgText); + + FixItHints.push_back( + FixItHint::CreateRemoval(CharSourceRange::getTokenRange( + InnerCall->getCallee()->getSourceRange()))); + + removeCharacterFromSource(ArgText, CommentRanges, '(', InnerCall, + FixItHints, false); + + if (InnerResult.First == InnerResult.Last) { + removeCharacterFromSource(ArgText, CommentRanges, '{', InnerCall, + FixItHints, false); + removeCharacterFromSource(ArgText, CommentRanges, '}', InnerCall, + FixItHints, true); + FixItHints.insert(FixItHints.end(), InnerReplacement.begin(), + InnerReplacement.end()); + } else { + FixItHints.insert(FixItHints.end(), InnerReplacement.begin() + 1, + InnerReplacement.end() - 1); + } + + if (InnerResult.Compare) { + bool CommentFound = false; + + int InnerCallStartOffset = InnerCall->getBeginLoc().getRawEncoding(); + int CompareStart = + getLocForEndOfToken(InnerResult.Last, Match).getRawEncoding() - + InnerCallStartOffset; + int LastEnd = + getLocForEndOfToken(InnerResult.Compare, Match).getRawEncoding() - + InnerCallStartOffset; + + for (const auto &Range : CommentRanges) { + + if (Range.first >= CompareStart && Range.second <= LastEnd) + CommentFound = true; + } + + if (CommentFound) { + removeCharacterFromSource(ArgText, CommentRanges, ',', InnerCall, + FixItHints, true); + + FixItHints.push_back( + FixItHint::CreateRemoval(CharSourceRange::getCharRange( + InnerResult.Compare->getBeginLoc(), + getLocForEndOfToken(InnerResult.Compare, Match)))); + } else { + FixItHints.push_back( + FixItHint::CreateRemoval(CharSourceRange::getCharRange( + getLocForEndOfToken(InnerResult.Last, Match), + getLocForEndOfToken(InnerResult.Compare, Match)))); + } + } + + removeCharacterFromSource(ArgText, CommentRanges, ')', InnerCall, + FixItHints, true); + + continue; + } + } + + const QualType ArgType = Arg->IgnoreParenImpCasts() + ->getType() + .getUnqualifiedType() + .getCanonicalType(); + + if (ArgType != ResultType) { + FixItHints.push_back(FixItHint::CreateReplacement( + Arg->getSourceRange(), + "static_cast<" + ResultType.getAsString() + ">(" + ArgText + ")")); + } + } + + if (!IsInitializerList) + FixItHints.push_back(FixItHint::CreateInsertion( + getLocForEndOfToken(Result.Last, Match), "}")); ---------------- 5chmidti wrote:
Please change this to ```c++ if (!IsInitializerList) { if (Result.Compare) FixItHints.push_back(FixItHint::CreateInsertion( Lexer::getLocForEndOfToken(Result.Last->getEndLoc(), 0, *Match.SourceManager, Match.Context->getLangOpts()), "}")); else FixItHints.push_back( FixItHint::CreateInsertion(TopCall->getEndLoc(), "}")); } ``` so that we don't have to use the lexer when it is not needed. https://github.com/llvm/llvm-project/pull/85572 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits