11038db6fSNathan James //===--- UseRangesCheck.cpp - clang-tidy ----------------------------------===// 21038db6fSNathan James // 31038db6fSNathan James // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 41038db6fSNathan James // See https://llvm.org/LICENSE.txt for license information. 51038db6fSNathan James // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 61038db6fSNathan James // 71038db6fSNathan James //===----------------------------------------------------------------------===// 81038db6fSNathan James 91038db6fSNathan James #include "UseRangesCheck.h" 101038db6fSNathan James #include "Matchers.h" 111038db6fSNathan James #include "clang/AST/ASTContext.h" 121038db6fSNathan James #include "clang/AST/Decl.h" 131038db6fSNathan James #include "clang/AST/Expr.h" 141038db6fSNathan James #include "clang/ASTMatchers/ASTMatchFinder.h" 151038db6fSNathan James #include "clang/ASTMatchers/ASTMatchers.h" 161038db6fSNathan James #include "clang/ASTMatchers/ASTMatchersInternal.h" 171038db6fSNathan James #include "clang/Basic/Diagnostic.h" 181038db6fSNathan James #include "clang/Basic/LLVM.h" 191038db6fSNathan James #include "clang/Basic/SourceLocation.h" 201038db6fSNathan James #include "clang/Basic/SourceManager.h" 211038db6fSNathan James #include "clang/Lex/Lexer.h" 221038db6fSNathan James #include "llvm/ADT/ArrayRef.h" 231038db6fSNathan James #include "llvm/ADT/STLExtras.h" 241038db6fSNathan James #include "llvm/ADT/SmallBitVector.h" 251038db6fSNathan James #include "llvm/ADT/SmallString.h" 261038db6fSNathan James #include "llvm/ADT/SmallVector.h" 271038db6fSNathan James #include "llvm/ADT/StringRef.h" 281038db6fSNathan James #include "llvm/ADT/Twine.h" 291038db6fSNathan James #include "llvm/Support/raw_ostream.h" 301038db6fSNathan James #include <cassert> 311038db6fSNathan James #include <optional> 321038db6fSNathan James #include <string> 331038db6fSNathan James 341038db6fSNathan James using namespace clang::ast_matchers; 351038db6fSNathan James 361038db6fSNathan James static constexpr const char BoundCall[] = "CallExpr"; 371038db6fSNathan James static constexpr const char FuncDecl[] = "FuncDecl"; 381038db6fSNathan James static constexpr const char ArgName[] = "ArgName"; 391038db6fSNathan James 401038db6fSNathan James namespace clang::tidy::utils { 411038db6fSNathan James 421038db6fSNathan James static std::string getFullPrefix(ArrayRef<UseRangesCheck::Indexes> Signature) { 431038db6fSNathan James std::string Output; 441038db6fSNathan James llvm::raw_string_ostream OS(Output); 451038db6fSNathan James for (const UseRangesCheck::Indexes &Item : Signature) 461038db6fSNathan James OS << Item.BeginArg << ":" << Item.EndArg << ":" 471038db6fSNathan James << (Item.ReplaceArg == Item.First ? '0' : '1'); 481038db6fSNathan James return Output; 491038db6fSNathan James } 501038db6fSNathan James 511038db6fSNathan James namespace { 521038db6fSNathan James 531038db6fSNathan James AST_MATCHER(Expr, hasSideEffects) { 541038db6fSNathan James return Node.HasSideEffects(Finder->getASTContext()); 551038db6fSNathan James } 561038db6fSNathan James } // namespace 571038db6fSNathan James 581038db6fSNathan James static auto 591038db6fSNathan James makeExprMatcher(ast_matchers::internal::Matcher<Expr> ArgumentMatcher, 601038db6fSNathan James ArrayRef<StringRef> MethodNames, 611038db6fSNathan James ArrayRef<StringRef> FreeNames) { 621038db6fSNathan James return expr( 631038db6fSNathan James anyOf(cxxMemberCallExpr(argumentCountIs(0), 641038db6fSNathan James callee(cxxMethodDecl(hasAnyName(MethodNames))), 651038db6fSNathan James on(ArgumentMatcher)), 661038db6fSNathan James callExpr(argumentCountIs(1), hasArgument(0, ArgumentMatcher), 671038db6fSNathan James hasDeclaration(functionDecl(hasAnyName(FreeNames)))))); 681038db6fSNathan James } 691038db6fSNathan James 701038db6fSNathan James static ast_matchers::internal::Matcher<CallExpr> 711038db6fSNathan James makeMatcherPair(StringRef State, const UseRangesCheck::Indexes &Indexes, 721038db6fSNathan James ArrayRef<StringRef> BeginFreeNames, 731038db6fSNathan James ArrayRef<StringRef> EndFreeNames, 741038db6fSNathan James const std::optional<UseRangesCheck::ReverseIteratorDescriptor> 751038db6fSNathan James &ReverseDescriptor) { 761038db6fSNathan James std::string ArgBound = (ArgName + llvm::Twine(Indexes.BeginArg)).str(); 771038db6fSNathan James SmallString<64> ID = {BoundCall, State}; 781038db6fSNathan James ast_matchers::internal::Matcher<CallExpr> ArgumentMatcher = allOf( 791038db6fSNathan James hasArgument(Indexes.BeginArg, 801038db6fSNathan James makeExprMatcher(expr(unless(hasSideEffects())).bind(ArgBound), 811038db6fSNathan James {"begin", "cbegin"}, BeginFreeNames)), 821038db6fSNathan James hasArgument(Indexes.EndArg, 831038db6fSNathan James makeExprMatcher( 841038db6fSNathan James expr(matchers::isStatementIdenticalToBoundNode(ArgBound)), 851038db6fSNathan James {"end", "cend"}, EndFreeNames))); 861038db6fSNathan James if (ReverseDescriptor) { 871038db6fSNathan James ArgBound.push_back('R'); 881038db6fSNathan James SmallVector<StringRef> RBegin{ 891038db6fSNathan James llvm::make_first_range(ReverseDescriptor->FreeReverseNames)}; 901038db6fSNathan James SmallVector<StringRef> REnd{ 911038db6fSNathan James llvm::make_second_range(ReverseDescriptor->FreeReverseNames)}; 921038db6fSNathan James ArgumentMatcher = anyOf( 931038db6fSNathan James ArgumentMatcher, 941038db6fSNathan James allOf(hasArgument( 951038db6fSNathan James Indexes.BeginArg, 961038db6fSNathan James makeExprMatcher(expr(unless(hasSideEffects())).bind(ArgBound), 971038db6fSNathan James {"rbegin", "crbegin"}, RBegin)), 981038db6fSNathan James hasArgument( 991038db6fSNathan James Indexes.EndArg, 1001038db6fSNathan James makeExprMatcher( 1011038db6fSNathan James expr(matchers::isStatementIdenticalToBoundNode(ArgBound)), 1021038db6fSNathan James {"rend", "crend"}, REnd)))); 1031038db6fSNathan James } 1041038db6fSNathan James return callExpr(argumentCountAtLeast( 1051038db6fSNathan James std::max(Indexes.BeginArg, Indexes.EndArg) + 1), 1061038db6fSNathan James ArgumentMatcher) 1071038db6fSNathan James .bind(ID); 1081038db6fSNathan James } 1091038db6fSNathan James 1101038db6fSNathan James void UseRangesCheck::registerMatchers(MatchFinder *Finder) { 111*0762db65SNathan James auto Replaces = getReplacerMap(); 1121038db6fSNathan James ReverseDescriptor = getReverseDescriptor(); 1131038db6fSNathan James auto BeginEndNames = getFreeBeginEndMethods(); 1141038db6fSNathan James llvm::SmallVector<StringRef, 4> BeginNames{ 1151038db6fSNathan James llvm::make_first_range(BeginEndNames)}; 1161038db6fSNathan James llvm::SmallVector<StringRef, 4> EndNames{ 1171038db6fSNathan James llvm::make_second_range(BeginEndNames)}; 118*0762db65SNathan James Replacers.clear(); 119*0762db65SNathan James llvm::DenseSet<Replacer *> SeenRepl; 1201038db6fSNathan James for (auto I = Replaces.begin(), E = Replaces.end(); I != E; ++I) { 121*0762db65SNathan James auto Replacer = I->getValue(); 122*0762db65SNathan James if (!SeenRepl.insert(Replacer.get()).second) 1231038db6fSNathan James continue; 124*0762db65SNathan James Replacers.push_back(Replacer); 125*0762db65SNathan James assert(!Replacer->getReplacementSignatures().empty() && 126*0762db65SNathan James llvm::all_of(Replacer->getReplacementSignatures(), 127*0762db65SNathan James [](auto Index) { return !Index.empty(); })); 1281038db6fSNathan James std::vector<StringRef> Names(1, I->getKey()); 1291038db6fSNathan James for (auto J = std::next(I); J != E; ++J) 130*0762db65SNathan James if (J->getValue() == Replacer) 1311038db6fSNathan James Names.push_back(J->getKey()); 1321038db6fSNathan James 1331038db6fSNathan James std::vector<ast_matchers::internal::DynTypedMatcher> TotalMatchers; 1341038db6fSNathan James // As we match on the first matched signature, we need to sort the 1351038db6fSNathan James // signatures in order of length(longest to shortest). This way any 1361038db6fSNathan James // signature that is a subset of another signature will be matched after the 1371038db6fSNathan James // other. 138*0762db65SNathan James SmallVector<Signature> SigVec(Replacer->getReplacementSignatures()); 1391038db6fSNathan James llvm::sort(SigVec, [](auto &L, auto &R) { return R.size() < L.size(); }); 1401038db6fSNathan James for (const auto &Signature : SigVec) { 1411038db6fSNathan James std::vector<ast_matchers::internal::DynTypedMatcher> Matchers; 1421038db6fSNathan James for (const auto &ArgPair : Signature) 1431038db6fSNathan James Matchers.push_back(makeMatcherPair(getFullPrefix(Signature), ArgPair, 1441038db6fSNathan James BeginNames, EndNames, 1451038db6fSNathan James ReverseDescriptor)); 1461038db6fSNathan James TotalMatchers.push_back( 1471038db6fSNathan James ast_matchers::internal::DynTypedMatcher::constructVariadic( 1481038db6fSNathan James ast_matchers::internal::DynTypedMatcher::VO_AllOf, 1491038db6fSNathan James ASTNodeKind::getFromNodeKind<CallExpr>(), std::move(Matchers))); 1501038db6fSNathan James } 1511038db6fSNathan James Finder->addMatcher( 1521038db6fSNathan James callExpr( 153*0762db65SNathan James callee(functionDecl(hasAnyName(std::move(Names))) 154*0762db65SNathan James .bind((FuncDecl + Twine(Replacers.size() - 1).str()))), 1551038db6fSNathan James ast_matchers::internal::DynTypedMatcher::constructVariadic( 1561038db6fSNathan James ast_matchers::internal::DynTypedMatcher::VO_AnyOf, 1571038db6fSNathan James ASTNodeKind::getFromNodeKind<CallExpr>(), 1581038db6fSNathan James std::move(TotalMatchers)) 1591038db6fSNathan James .convertTo<CallExpr>()), 1601038db6fSNathan James this); 1611038db6fSNathan James } 1621038db6fSNathan James } 1631038db6fSNathan James 1641038db6fSNathan James static void removeFunctionArgs(DiagnosticBuilder &Diag, const CallExpr &Call, 1651038db6fSNathan James ArrayRef<unsigned> Indexes, 1661038db6fSNathan James const ASTContext &Ctx) { 1671038db6fSNathan James llvm::SmallVector<unsigned> Sorted(Indexes); 1681038db6fSNathan James llvm::sort(Sorted); 1691038db6fSNathan James // Keep track of commas removed 1701038db6fSNathan James llvm::SmallBitVector Commas(Call.getNumArgs()); 1711038db6fSNathan James // The first comma is actually the '(' which we can't remove 1721038db6fSNathan James Commas[0] = true; 1731038db6fSNathan James for (unsigned Index : Sorted) { 1741038db6fSNathan James const Expr *Arg = Call.getArg(Index); 1751038db6fSNathan James if (Commas[Index]) { 1761038db6fSNathan James if (Index >= Commas.size()) { 1771038db6fSNathan James Diag << FixItHint::CreateRemoval(Arg->getSourceRange()); 1781038db6fSNathan James } else { 1791038db6fSNathan James // Remove the next comma 1801038db6fSNathan James Commas[Index + 1] = true; 1811038db6fSNathan James Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange( 1821038db6fSNathan James {Arg->getBeginLoc(), 1831038db6fSNathan James Lexer::getLocForEndOfToken( 1841038db6fSNathan James Arg->getEndLoc(), 0, Ctx.getSourceManager(), Ctx.getLangOpts()) 1851038db6fSNathan James .getLocWithOffset(1)})); 1861038db6fSNathan James } 1871038db6fSNathan James } else { 1881038db6fSNathan James Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange( 1891038db6fSNathan James Arg->getBeginLoc().getLocWithOffset(-1), Arg->getEndLoc())); 1901038db6fSNathan James Commas[Index] = true; 1911038db6fSNathan James } 1921038db6fSNathan James } 1931038db6fSNathan James } 1941038db6fSNathan James 1951038db6fSNathan James void UseRangesCheck::check(const MatchFinder::MatchResult &Result) { 196*0762db65SNathan James Replacer *Replacer = nullptr; 197*0762db65SNathan James const FunctionDecl *Function = nullptr; 198*0762db65SNathan James for (auto [Node, Value] : Result.Nodes.getMap()) { 199*0762db65SNathan James StringRef NodeStr(Node); 200*0762db65SNathan James if (!NodeStr.consume_front(FuncDecl)) 201*0762db65SNathan James continue; 202*0762db65SNathan James Function = Value.get<FunctionDecl>(); 203*0762db65SNathan James size_t Index; 204*0762db65SNathan James if (NodeStr.getAsInteger(10, Index)) { 205*0762db65SNathan James llvm_unreachable("Unable to extract replacer index"); 206*0762db65SNathan James } 207*0762db65SNathan James assert(Index < Replacers.size()); 208*0762db65SNathan James Replacer = Replacers[Index].get(); 209*0762db65SNathan James break; 210*0762db65SNathan James } 211*0762db65SNathan James assert(Replacer && Function); 2121038db6fSNathan James SmallString<64> Buffer; 213*0762db65SNathan James for (const Signature &Sig : Replacer->getReplacementSignatures()) { 2141038db6fSNathan James Buffer.assign({BoundCall, getFullPrefix(Sig)}); 2151038db6fSNathan James const auto *Call = Result.Nodes.getNodeAs<CallExpr>(Buffer); 2161038db6fSNathan James if (!Call) 2171038db6fSNathan James continue; 2181038db6fSNathan James auto Diag = createDiag(*Call); 219*0762db65SNathan James if (auto ReplaceName = Replacer->getReplaceName(*Function)) 2201038db6fSNathan James Diag << FixItHint::CreateReplacement(Call->getCallee()->getSourceRange(), 2211038db6fSNathan James *ReplaceName); 222*0762db65SNathan James if (auto Include = Replacer->getHeaderInclusion(*Function)) 2231038db6fSNathan James Diag << Inserter.createIncludeInsertion( 2241038db6fSNathan James Result.SourceManager->getFileID(Call->getBeginLoc()), *Include); 2251038db6fSNathan James llvm::SmallVector<unsigned, 3> ToRemove; 2261038db6fSNathan James for (const auto &[First, Second, Replace] : Sig) { 2271038db6fSNathan James auto ArgNode = ArgName + std::to_string(First); 2281038db6fSNathan James if (const auto *ArgExpr = Result.Nodes.getNodeAs<Expr>(ArgNode)) { 2291038db6fSNathan James Diag << FixItHint::CreateReplacement( 2301038db6fSNathan James Call->getArg(Replace == Indexes::Second ? Second : First) 2311038db6fSNathan James ->getSourceRange(), 2321038db6fSNathan James Lexer::getSourceText( 2331038db6fSNathan James CharSourceRange::getTokenRange(ArgExpr->getSourceRange()), 2341038db6fSNathan James Result.Context->getSourceManager(), 2351038db6fSNathan James Result.Context->getLangOpts())); 2361038db6fSNathan James } else { 2371038db6fSNathan James assert(ReverseDescriptor && "Couldn't find forward argument"); 2381038db6fSNathan James ArgNode.push_back('R'); 2391038db6fSNathan James ArgExpr = Result.Nodes.getNodeAs<Expr>(ArgNode); 2401038db6fSNathan James assert(ArgExpr && "Couldn't find forward or reverse argument"); 2411038db6fSNathan James if (ReverseDescriptor->ReverseHeader) 2421038db6fSNathan James Diag << Inserter.createIncludeInsertion( 2431038db6fSNathan James Result.SourceManager->getFileID(Call->getBeginLoc()), 2441038db6fSNathan James *ReverseDescriptor->ReverseHeader); 24587ca6386SNathan James StringRef ArgText = Lexer::getSourceText( 24687ca6386SNathan James CharSourceRange::getTokenRange(ArgExpr->getSourceRange()), 24787ca6386SNathan James Result.Context->getSourceManager(), Result.Context->getLangOpts()); 24887ca6386SNathan James SmallString<128> ReplaceText; 24987ca6386SNathan James if (ReverseDescriptor->IsPipeSyntax) 25087ca6386SNathan James ReplaceText.assign( 25187ca6386SNathan James {ArgText, " | ", ReverseDescriptor->ReverseAdaptorName}); 25287ca6386SNathan James else 25387ca6386SNathan James ReplaceText.assign( 25487ca6386SNathan James {ReverseDescriptor->ReverseAdaptorName, "(", ArgText, ")"}); 2551038db6fSNathan James Diag << FixItHint::CreateReplacement( 2561038db6fSNathan James Call->getArg(Replace == Indexes::Second ? Second : First) 2571038db6fSNathan James ->getSourceRange(), 25887ca6386SNathan James ReplaceText); 2591038db6fSNathan James } 2601038db6fSNathan James ToRemove.push_back(Replace == Indexes::Second ? First : Second); 2611038db6fSNathan James } 2621038db6fSNathan James removeFunctionArgs(Diag, *Call, ToRemove, *Result.Context); 2631038db6fSNathan James return; 2641038db6fSNathan James } 2651038db6fSNathan James llvm_unreachable("No valid signature found"); 2661038db6fSNathan James } 2671038db6fSNathan James 2681038db6fSNathan James bool UseRangesCheck::isLanguageVersionSupported( 2691038db6fSNathan James const LangOptions &LangOpts) const { 2701038db6fSNathan James return LangOpts.CPlusPlus11; 2711038db6fSNathan James } 2721038db6fSNathan James 2731038db6fSNathan James UseRangesCheck::UseRangesCheck(StringRef Name, ClangTidyContext *Context) 2741038db6fSNathan James : ClangTidyCheck(Name, Context), 2751038db6fSNathan James Inserter(Options.getLocalOrGlobal("IncludeStyle", 2761038db6fSNathan James utils::IncludeSorter::IS_LLVM), 2771038db6fSNathan James areDiagsSelfContained()) {} 2781038db6fSNathan James 2791038db6fSNathan James void UseRangesCheck::registerPPCallbacks(const SourceManager &, 2801038db6fSNathan James Preprocessor *PP, Preprocessor *) { 2811038db6fSNathan James Inserter.registerPreprocessor(PP); 2821038db6fSNathan James } 2831038db6fSNathan James 2841038db6fSNathan James void UseRangesCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 2851038db6fSNathan James Options.store(Opts, "IncludeStyle", Inserter.getStyle()); 2861038db6fSNathan James } 2871038db6fSNathan James 2881038db6fSNathan James std::optional<std::string> 2891038db6fSNathan James UseRangesCheck::Replacer::getHeaderInclusion(const NamedDecl &) const { 2901038db6fSNathan James return std::nullopt; 2911038db6fSNathan James } 2921038db6fSNathan James 2931038db6fSNathan James DiagnosticBuilder UseRangesCheck::createDiag(const CallExpr &Call) { 2941038db6fSNathan James return diag(Call.getBeginLoc(), "use a ranges version of this algorithm"); 2951038db6fSNathan James } 2961038db6fSNathan James 2971038db6fSNathan James std::optional<UseRangesCheck::ReverseIteratorDescriptor> 2981038db6fSNathan James UseRangesCheck::getReverseDescriptor() const { 2991038db6fSNathan James return std::nullopt; 3001038db6fSNathan James } 3011038db6fSNathan James 3021038db6fSNathan James ArrayRef<std::pair<StringRef, StringRef>> 3031038db6fSNathan James UseRangesCheck::getFreeBeginEndMethods() const { 3041038db6fSNathan James return {}; 3051038db6fSNathan James } 3061038db6fSNathan James 3071038db6fSNathan James std::optional<TraversalKind> UseRangesCheck::getCheckTraversalKind() const { 3081038db6fSNathan James return TK_IgnoreUnlessSpelledInSource; 3091038db6fSNathan James } 3101038db6fSNathan James } // namespace clang::tidy::utils 311