xref: /llvm-project/clang-tools-extra/clang-tidy/utils/UseRangesCheck.cpp (revision 0762db6533eda3453158c7b9b0631542c47093a8)
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