brintos

brintos / llvm-project-archived public Read only

0
0
Text · 36.8 KiB · 519d359 Raw
938 lines · cpp
1//===-- Move.cpp - Implement ClangMove functationalities --------*- C++ -*-===//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 "Move.h"10#include "HelperDeclRefGraph.h"11#include "clang/ASTMatchers/ASTMatchers.h"12#include "clang/Basic/SourceManager.h"13#include "clang/Format/Format.h"14#include "clang/Frontend/CompilerInstance.h"15#include "clang/Lex/Lexer.h"16#include "clang/Lex/Preprocessor.h"17#include "clang/Rewrite/Core/Rewriter.h"18#include "clang/Tooling/Core/Replacement.h"19#include "llvm/Support/Debug.h"20#include "llvm/Support/Path.h"21 22#define DEBUG_TYPE "clang-move"23 24using namespace clang::ast_matchers;25 26namespace clang {27namespace move {28namespace {29 30// FIXME: Move to ASTMatchers.31AST_MATCHER(VarDecl, isStaticDataMember) { return Node.isStaticDataMember(); }32 33AST_MATCHER(NamedDecl, notInMacro) { return !Node.getLocation().isMacroID(); }34 35AST_MATCHER_P(Decl, hasOutermostEnclosingClass,36              ast_matchers::internal::Matcher<Decl>, InnerMatcher) {37  const auto *Context = Node.getDeclContext();38  if (!Context)39    return false;40  while (const auto *NextContext = Context->getParent()) {41    if (isa<NamespaceDecl>(NextContext) ||42        isa<TranslationUnitDecl>(NextContext))43      break;44    Context = NextContext;45  }46  return InnerMatcher.matches(*Decl::castFromDeclContext(Context), Finder,47                              Builder);48}49 50AST_MATCHER_P(CXXMethodDecl, ofOutermostEnclosingClass,51              ast_matchers::internal::Matcher<CXXRecordDecl>, InnerMatcher) {52  const CXXRecordDecl *Parent = Node.getParent();53  if (!Parent)54    return false;55  while (const auto *NextParent =56             dyn_cast<CXXRecordDecl>(Parent->getParent())) {57    Parent = NextParent;58  }59 60  return InnerMatcher.matches(*Parent, Finder, Builder);61}62 63std::string CleanPath(StringRef PathRef) {64  llvm::SmallString<128> Path(PathRef);65  llvm::sys::path::remove_dots(Path, /*remove_dot_dot=*/true);66  // FIXME: figure out why this is necessary.67  llvm::sys::path::native(Path);68  return std::string(Path);69}70 71// Make the Path absolute using the CurrentDir if the Path is not an absolute72// path. An empty Path will result in an empty string.73std::string MakeAbsolutePath(StringRef CurrentDir, StringRef Path) {74  if (Path.empty())75    return "";76  llvm::SmallString<128> InitialDirectory(CurrentDir);77  llvm::SmallString<128> AbsolutePath(Path);78  llvm::sys::path::make_absolute(InitialDirectory, AbsolutePath);79  return CleanPath(std::move(AbsolutePath));80}81 82// Make the Path absolute using the current working directory of the given83// SourceManager if the Path is not an absolute path.84//85// The Path can be a path relative to the build directory, or retrieved from86// the SourceManager.87std::string MakeAbsolutePath(const SourceManager &SM, StringRef Path) {88  llvm::SmallString<128> AbsolutePath(Path);89  if (std::error_code EC =90          SM.getFileManager().getVirtualFileSystem().makeAbsolute(AbsolutePath))91    llvm::errs() << "Warning: could not make absolute file: '" << EC.message()92                 << '\n';93  // Handle symbolic link path cases.94  // We are trying to get the real file path of the symlink.95  auto Dir = SM.getFileManager().getOptionalDirectoryRef(96      llvm::sys::path::parent_path(AbsolutePath.str()));97  if (Dir) {98    StringRef DirName = SM.getFileManager().getCanonicalName(*Dir);99    // FIXME: getCanonicalName might fail to get real path on VFS.100    if (llvm::sys::path::is_absolute(DirName)) {101      SmallString<128> AbsoluteFilename;102      llvm::sys::path::append(AbsoluteFilename, DirName,103                              llvm::sys::path::filename(AbsolutePath.str()));104      return CleanPath(AbsoluteFilename);105    }106  }107  return CleanPath(AbsolutePath);108}109 110// Matches AST nodes that are expanded within the given AbsoluteFilePath.111AST_POLYMORPHIC_MATCHER_P(isExpansionInFile,112                          AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt, TypeLoc),113                          std::string, AbsoluteFilePath) {114  auto &SourceManager = Finder->getASTContext().getSourceManager();115  auto ExpansionLoc = SourceManager.getExpansionLoc(Node.getBeginLoc());116  if (ExpansionLoc.isInvalid())117    return false;118  auto FileEntry =119      SourceManager.getFileEntryRefForID(SourceManager.getFileID(ExpansionLoc));120  if (!FileEntry)121    return false;122  return MakeAbsolutePath(SourceManager, FileEntry->getName()) ==123         AbsoluteFilePath;124}125 126class FindAllIncludes : public PPCallbacks {127public:128  explicit FindAllIncludes(SourceManager *SM, ClangMoveTool *const MoveTool)129      : SM(*SM), MoveTool(MoveTool) {}130 131  void InclusionDirective(SourceLocation HashLoc, const Token & /*IncludeTok*/,132                          StringRef FileName, bool IsAngled,133                          CharSourceRange FilenameRange,134                          OptionalFileEntryRef /*File*/, StringRef SearchPath,135                          StringRef /*RelativePath*/,136                          const Module * /*SuggestedModule*/,137                          bool /*ModuleImported*/,138                          SrcMgr::CharacteristicKind /*FileType*/) override {139    if (auto FileEntry = SM.getFileEntryRefForID(SM.getFileID(HashLoc)))140      MoveTool->addIncludes(FileName, IsAngled, SearchPath,141                            FileEntry->getName(), FilenameRange, SM);142  }143 144private:145  const SourceManager &SM;146  ClangMoveTool *const MoveTool;147};148 149/// Add a declaration being moved to new.h/cc. Note that the declaration will150/// also be deleted in old.h/cc.151void MoveDeclFromOldFileToNewFile(ClangMoveTool *MoveTool, const NamedDecl *D) {152  MoveTool->getMovedDecls().push_back(D);153  MoveTool->addRemovedDecl(D);154  MoveTool->getUnremovedDeclsInOldHeader().erase(D);155}156 157class FunctionDeclarationMatch : public MatchFinder::MatchCallback {158public:159  explicit FunctionDeclarationMatch(ClangMoveTool *MoveTool)160      : MoveTool(MoveTool) {}161 162  void run(const MatchFinder::MatchResult &Result) override {163    const auto *FD = Result.Nodes.getNodeAs<FunctionDecl>("function");164    assert(FD);165    const NamedDecl *D = FD;166    if (const auto *FTD = FD->getDescribedFunctionTemplate())167      D = FTD;168    MoveDeclFromOldFileToNewFile(MoveTool, D);169  }170 171private:172  ClangMoveTool *MoveTool;173};174 175class VarDeclarationMatch : public MatchFinder::MatchCallback {176public:177  explicit VarDeclarationMatch(ClangMoveTool *MoveTool)178      : MoveTool(MoveTool) {}179 180  void run(const MatchFinder::MatchResult &Result) override {181    const auto *VD = Result.Nodes.getNodeAs<VarDecl>("var");182    assert(VD);183    MoveDeclFromOldFileToNewFile(MoveTool, VD);184  }185 186private:187  ClangMoveTool *MoveTool;188};189 190class TypeAliasMatch : public MatchFinder::MatchCallback {191public:192  explicit TypeAliasMatch(ClangMoveTool *MoveTool)193      : MoveTool(MoveTool) {}194 195  void run(const MatchFinder::MatchResult &Result) override {196    if (const auto *TD = Result.Nodes.getNodeAs<TypedefDecl>("typedef"))197      MoveDeclFromOldFileToNewFile(MoveTool, TD);198    else if (const auto *TAD =199                 Result.Nodes.getNodeAs<TypeAliasDecl>("type_alias")) {200      const NamedDecl * D = TAD;201      if (const auto * TD = TAD->getDescribedAliasTemplate())202        D = TD;203      MoveDeclFromOldFileToNewFile(MoveTool, D);204    }205  }206 207private:208  ClangMoveTool *MoveTool;209};210 211class EnumDeclarationMatch : public MatchFinder::MatchCallback {212public:213  explicit EnumDeclarationMatch(ClangMoveTool *MoveTool)214      : MoveTool(MoveTool) {}215 216  void run(const MatchFinder::MatchResult &Result) override {217    const auto *ED = Result.Nodes.getNodeAs<EnumDecl>("enum");218    assert(ED);219    MoveDeclFromOldFileToNewFile(MoveTool, ED);220  }221 222private:223  ClangMoveTool *MoveTool;224};225 226class ClassDeclarationMatch : public MatchFinder::MatchCallback {227public:228  explicit ClassDeclarationMatch(ClangMoveTool *MoveTool)229      : MoveTool(MoveTool) {}230  void run(const MatchFinder::MatchResult &Result) override {231    SourceManager *SM = &Result.Context->getSourceManager();232    if (const auto *CMD = Result.Nodes.getNodeAs<CXXMethodDecl>("class_method"))233      MatchClassMethod(CMD, SM);234    else if (const auto *VD =235                 Result.Nodes.getNodeAs<VarDecl>("class_static_var_decl"))236      MatchClassStaticVariable(VD, SM);237    else if (const auto *CD =238                 Result.Nodes.getNodeAs<CXXRecordDecl>("moved_class"))239      MatchClassDeclaration(CD, SM);240  }241 242private:243  void MatchClassMethod(const CXXMethodDecl *CMD, SourceManager *SM) {244    // Skip inline class methods. isInline() ast matcher doesn't ignore this245    // case.246    if (!CMD->isInlined()) {247      MoveTool->getMovedDecls().push_back(CMD);248      MoveTool->addRemovedDecl(CMD);249      // Get template class method from its method declaration as250      // UnremovedDecls stores template class method.251      if (const auto *FTD = CMD->getDescribedFunctionTemplate())252        MoveTool->getUnremovedDeclsInOldHeader().erase(FTD);253      else254        MoveTool->getUnremovedDeclsInOldHeader().erase(CMD);255    }256  }257 258  void MatchClassStaticVariable(const NamedDecl *VD, SourceManager *SM) {259    MoveDeclFromOldFileToNewFile(MoveTool, VD);260  }261 262  void MatchClassDeclaration(const CXXRecordDecl *CD, SourceManager *SM) {263    // Get class template from its class declaration as UnremovedDecls stores264    // class template.265    if (const auto *TC = CD->getDescribedClassTemplate())266      MoveTool->getMovedDecls().push_back(TC);267    else268      MoveTool->getMovedDecls().push_back(CD);269    MoveTool->addRemovedDecl(MoveTool->getMovedDecls().back());270    MoveTool->getUnremovedDeclsInOldHeader().erase(271        MoveTool->getMovedDecls().back());272  }273 274  ClangMoveTool *MoveTool;275};276 277// Expand to get the end location of the line where the EndLoc of the given278// Decl.279SourceLocation getLocForEndOfDecl(const Decl *D,280                                  const LangOptions &LangOpts = LangOptions()) {281  const auto &SM = D->getASTContext().getSourceManager();282  // If the expansion range is a character range, this is the location of283  // the first character past the end. Otherwise it's the location of the284  // first character in the final token in the range.285  auto EndExpansionLoc = SM.getExpansionRange(D->getEndLoc()).getEnd();286  std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(EndExpansionLoc);287  // Try to load the file buffer.288  bool InvalidTemp = false;289  llvm::StringRef File = SM.getBufferData(LocInfo.first, &InvalidTemp);290  if (InvalidTemp)291    return SourceLocation();292 293  const char *TokBegin = File.data() + LocInfo.second;294  // Lex from the start of the given location.295  Lexer Lex(SM.getLocForStartOfFile(LocInfo.first), LangOpts, File.begin(),296            TokBegin, File.end());297 298  llvm::SmallVector<char, 16> Line;299  // FIXME: this is a bit hacky to get ReadToEndOfLine work.300  Lex.setParsingPreprocessorDirective(true);301  Lex.ReadToEndOfLine(&Line);302  SourceLocation EndLoc = EndExpansionLoc.getLocWithOffset(Line.size());303  // If we already reach EOF, just return the EOF SourceLocation;304  // otherwise, move 1 offset ahead to include the trailing newline character305  // '\n'.306  return SM.getLocForEndOfFile(LocInfo.first) == EndLoc307             ? EndLoc308             : EndLoc.getLocWithOffset(1);309}310 311// Get full range of a Decl including the comments associated with it.312CharSourceRange getFullRange(const Decl *D,313                             const LangOptions &options = LangOptions()) {314  const auto &SM = D->getASTContext().getSourceManager();315  SourceRange Full(SM.getExpansionLoc(D->getBeginLoc()), getLocForEndOfDecl(D));316  // Expand to comments that are associated with the Decl.317  if (const auto *Comment = D->getASTContext().getRawCommentForDeclNoCache(D)) {318    if (SM.isBeforeInTranslationUnit(Full.getEnd(), Comment->getEndLoc()))319      Full.setEnd(Comment->getEndLoc());320    // FIXME: Don't delete a preceding comment, if there are no other entities321    // it could refer to.322    if (SM.isBeforeInTranslationUnit(Comment->getBeginLoc(), Full.getBegin()))323      Full.setBegin(Comment->getBeginLoc());324  }325 326  return CharSourceRange::getCharRange(Full);327}328 329std::string getDeclarationSourceText(const Decl *D) {330  const auto &SM = D->getASTContext().getSourceManager();331  llvm::StringRef SourceText =332      Lexer::getSourceText(getFullRange(D), SM, LangOptions());333  return SourceText.str();334}335 336bool isInHeaderFile(const Decl *D, llvm::StringRef OriginalRunningDirectory,337                    llvm::StringRef OldHeader) {338  const auto &SM = D->getASTContext().getSourceManager();339  if (OldHeader.empty())340    return false;341  auto ExpansionLoc = SM.getExpansionLoc(D->getBeginLoc());342  if (ExpansionLoc.isInvalid())343    return false;344 345  if (auto FE = SM.getFileEntryRefForID(SM.getFileID(ExpansionLoc))) {346    return MakeAbsolutePath(SM, FE->getName()) ==347           MakeAbsolutePath(OriginalRunningDirectory, OldHeader);348  }349 350  return false;351}352 353std::vector<std::string> getNamespaces(const Decl *D) {354  std::vector<std::string> Namespaces;355  for (const auto *Context = D->getDeclContext(); Context;356       Context = Context->getParent()) {357    if (llvm::isa<TranslationUnitDecl>(Context) ||358        llvm::isa<LinkageSpecDecl>(Context))359      break;360 361    if (const auto *ND = llvm::dyn_cast<NamespaceDecl>(Context))362      Namespaces.push_back(ND->getName().str());363  }364  std::reverse(Namespaces.begin(), Namespaces.end());365  return Namespaces;366}367 368tooling::Replacements369createInsertedReplacements(const std::vector<std::string> &Includes,370                           const std::vector<const NamedDecl *> &Decls,371                           llvm::StringRef FileName, bool IsHeader = false,372                           StringRef OldHeaderInclude = "") {373  std::string NewCode;374  std::string GuardName(FileName);375  if (IsHeader) {376    for (size_t i = 0; i < GuardName.size(); ++i) {377      if (!isAlphanumeric(GuardName[i]))378        GuardName[i] = '_';379    }380    GuardName = StringRef(GuardName).upper();381    NewCode += "#ifndef " + GuardName + "\n";382    NewCode += "#define " + GuardName + "\n\n";383  }384 385  NewCode += OldHeaderInclude;386  // Add #Includes.387  for (const auto &Include : Includes)388    NewCode += Include;389 390  if (!Includes.empty())391    NewCode += "\n";392 393  // Add moved class definition and its related declarations. All declarations394  // in same namespace are grouped together.395  //396  // Record namespaces where the current position is in.397  std::vector<std::string> CurrentNamespaces;398  for (const auto *MovedDecl : Decls) {399    // The namespaces of the declaration being moved.400    std::vector<std::string> DeclNamespaces = getNamespaces(MovedDecl);401    auto CurrentIt = CurrentNamespaces.begin();402    auto DeclIt = DeclNamespaces.begin();403    // Skip the common prefix.404    while (CurrentIt != CurrentNamespaces.end() &&405           DeclIt != DeclNamespaces.end()) {406      if (*CurrentIt != *DeclIt)407        break;408      ++CurrentIt;409      ++DeclIt;410    }411    // Calculate the new namespaces after adding MovedDecl in CurrentNamespace,412    // which is used for next iteration of this loop.413    std::vector<std::string> NextNamespaces(CurrentNamespaces.begin(),414                                            CurrentIt);415    NextNamespaces.insert(NextNamespaces.end(), DeclIt, DeclNamespaces.end());416 417 418    // End with CurrentNamespace.419    bool HasEndCurrentNamespace = false;420    auto RemainingSize = CurrentNamespaces.end() - CurrentIt;421    for (auto It = CurrentNamespaces.rbegin(); RemainingSize > 0;422         --RemainingSize, ++It) {423      assert(It < CurrentNamespaces.rend());424      NewCode += "} // namespace " + *It + "\n";425      HasEndCurrentNamespace = true;426    }427    // Add trailing '\n' after the nested namespace definition.428    if (HasEndCurrentNamespace)429      NewCode += "\n";430 431    // If the moved declaration is not in CurrentNamespace, add extra namespace432    // definitions.433    bool IsInNewNamespace = false;434    while (DeclIt != DeclNamespaces.end()) {435      NewCode += "namespace " + *DeclIt + " {\n";436      IsInNewNamespace = true;437      ++DeclIt;438    }439    // If the moved declaration is in same namespace CurrentNamespace, add440    // a preceeding `\n' before the moved declaration.441    // FIXME: Don't add empty lines between using declarations.442    if (!IsInNewNamespace)443      NewCode += "\n";444    NewCode += getDeclarationSourceText(MovedDecl);445    CurrentNamespaces = std::move(NextNamespaces);446  }447  std::reverse(CurrentNamespaces.begin(), CurrentNamespaces.end());448  for (const auto &NS : CurrentNamespaces)449    NewCode += "} // namespace " + NS + "\n";450 451  if (IsHeader)452    NewCode += "\n#endif // " + GuardName + "\n";453  return tooling::Replacements(tooling::Replacement(FileName, 0, 0, NewCode));454}455 456// Return a set of all decls which are used/referenced by the given Decls.457// Specifically, given a class member declaration, this method will return all458// decls which are used by the whole class.459llvm::DenseSet<const Decl *>460getUsedDecls(const HelperDeclRefGraph *RG,461             const std::vector<const NamedDecl *> &Decls) {462  assert(RG);463  llvm::DenseSet<const CallGraphNode *> Nodes;464  for (const auto *D : Decls) {465    auto Result = RG->getReachableNodes(466        HelperDeclRGBuilder::getOutmostClassOrFunDecl(D));467    Nodes.insert_range(Result);468  }469  llvm::DenseSet<const Decl *> Results;470  for (const auto *Node : Nodes)471    Results.insert(Node->getDecl());472  return Results;473}474 475} // namespace476 477std::unique_ptr<ASTConsumer>478ClangMoveAction::CreateASTConsumer(CompilerInstance &Compiler,479                                   StringRef /*InFile*/) {480  Compiler.getPreprocessor().addPPCallbacks(std::make_unique<FindAllIncludes>(481      &Compiler.getSourceManager(), &MoveTool));482  return MatchFinder.newASTConsumer();483}484 485ClangMoveTool::ClangMoveTool(ClangMoveContext *const Context,486                             DeclarationReporter *const Reporter)487    : Context(Context), Reporter(Reporter) {488  if (!Context->Spec.NewHeader.empty())489    CCIncludes.push_back("#include \"" + Context->Spec.NewHeader + "\"\n");490}491 492void ClangMoveTool::addRemovedDecl(const NamedDecl *Decl) {493  const auto &SM = Decl->getASTContext().getSourceManager();494  auto Loc = Decl->getLocation();495  StringRef FilePath = SM.getFilename(Loc);496  FilePathToFileID[FilePath] = SM.getFileID(Loc);497  RemovedDecls.push_back(Decl);498}499 500void ClangMoveTool::registerMatchers(ast_matchers::MatchFinder *Finder) {501  auto InOldHeader =502      isExpansionInFile(makeAbsolutePath(Context->Spec.OldHeader));503  auto InOldCC = isExpansionInFile(makeAbsolutePath(Context->Spec.OldCC));504  auto InOldFiles = anyOf(InOldHeader, InOldCC);505  auto classTemplateForwardDecls =506      classTemplateDecl(unless(has(cxxRecordDecl(isDefinition()))));507  auto ForwardClassDecls = namedDecl(508      anyOf(cxxRecordDecl(unless(anyOf(isImplicit(), isDefinition()))),509            classTemplateForwardDecls));510  auto TopLevelDecl =511      hasDeclContext(anyOf(namespaceDecl(), translationUnitDecl()));512 513  //============================================================================514  // Matchers for old header515  //============================================================================516  // Match all top-level named declarations (e.g. function, variable, enum) in517  // old header, exclude forward class declarations and namespace declarations.518  //519  // We consider declarations inside a class belongs to the class. So these520  // declarations will be ignored.521  auto AllDeclsInHeader = namedDecl(522      unless(ForwardClassDecls), unless(namespaceDecl()),523      unless(usingDirectiveDecl()), // using namespace decl.524      notInMacro(),525      InOldHeader,526      hasParent(decl(anyOf(namespaceDecl(), translationUnitDecl()))),527      hasDeclContext(decl(anyOf(namespaceDecl(), translationUnitDecl()))));528  Finder->addMatcher(AllDeclsInHeader.bind("decls_in_header"), this);529 530  // Don't register other matchers when dumping all declarations in header.531  if (Context->DumpDeclarations)532    return;533 534  // Match forward declarations in old header.535  Finder->addMatcher(namedDecl(ForwardClassDecls, InOldHeader).bind("fwd_decl"),536                     this);537 538  //============================================================================539  // Matchers for old cc540  //============================================================================541  auto IsOldCCTopLevelDecl = allOf(542      hasParent(decl(anyOf(namespaceDecl(), translationUnitDecl()))), InOldCC);543  // Matching using decls/type alias decls which are in named/anonymous/global544  // namespace, these decls are always copied to new.h/cc. Those in classes,545  // functions are covered in other matchers.546  Finder->addMatcher(namedDecl(anyOf(usingDecl(IsOldCCTopLevelDecl),547                                     usingDirectiveDecl(unless(isImplicit()),548                                                        IsOldCCTopLevelDecl),549                                     typeAliasDecl(IsOldCCTopLevelDecl)),550                               notInMacro())551                         .bind("using_decl"),552                     this);553 554  // Match static functions/variable definitions which are defined in named555  // namespaces.556  SmallVector<std::string, 4> QualNames;557  QualNames.reserve(Context->Spec.Names.size());558  for (StringRef SymbolName : Context->Spec.Names) {559    QualNames.push_back(("::" + SymbolName.trim().ltrim(':')).str());560  }561 562  if (QualNames.empty()) {563    llvm::errs() << "No symbols being moved.\n";564    return;565  }566 567  ast_matchers::internal::Matcher<NamedDecl> HasAnySymbolNames =568      hasAnyName(SmallVector<StringRef, 4>(QualNames.begin(), QualNames.end()));569 570  auto InMovedClass =571      hasOutermostEnclosingClass(cxxRecordDecl(HasAnySymbolNames));572 573  // Matchers for helper declarations in old.cc.574  auto InAnonymousNS = hasParent(namespaceDecl(isAnonymous()));575  auto NotInMovedClass= allOf(unless(InMovedClass), InOldCC);576  auto IsOldCCHelper =577      allOf(NotInMovedClass, anyOf(isStaticStorageClass(), InAnonymousNS));578  // Match helper classes separately with helper functions/variables since we579  // want to reuse these matchers in finding helpers usage below.580  //581  // There could be forward declarations usage for helpers, especially for582  // classes and functions. We need include these forward declarations.583  //584  // Forward declarations for variable helpers will be excluded as these585  // declarations (with "extern") are not supposed in cpp file.586   auto HelperFuncOrVar =587      namedDecl(notInMacro(), anyOf(functionDecl(IsOldCCHelper),588                                    varDecl(isDefinition(), IsOldCCHelper)));589  auto HelperClasses =590      cxxRecordDecl(notInMacro(), NotInMovedClass, InAnonymousNS);591  // Save all helper declarations in old.cc.592  Finder->addMatcher(593      namedDecl(anyOf(HelperFuncOrVar, HelperClasses)).bind("helper_decls"),594      this);595 596  // Construct an AST-based call graph of helper declarations in old.cc.597  // In the following matcheres, "dc" is a caller while "helper_decls" and598  // "used_class" is a callee, so a new edge starting from caller to callee will599  // be add in the graph.600  //601  // Find helper function/variable usages.602  Finder->addMatcher(603      declRefExpr(to(HelperFuncOrVar), hasAncestor(decl().bind("dc")))604          .bind("func_ref"),605      &RGBuilder);606  // Find helper class usages.607  Finder->addMatcher(608      typeLoc(loc(recordType(hasDeclaration(HelperClasses.bind("used_class")))),609              hasAncestor(decl().bind("dc"))),610      &RGBuilder);611 612  //============================================================================613  // Matchers for old files, including old.h/old.cc614  //============================================================================615  // Create a MatchCallback for class declarations.616  MatchCallbacks.push_back(std::make_unique<ClassDeclarationMatch>(this));617  // Match moved class declarations.618  auto MovedClass =619      cxxRecordDecl(InOldFiles, HasAnySymbolNames, isDefinition(), TopLevelDecl)620          .bind("moved_class");621  Finder->addMatcher(MovedClass, MatchCallbacks.back().get());622  // Match moved class methods (static methods included) which are defined623  // outside moved class declaration.624  Finder->addMatcher(cxxMethodDecl(InOldFiles,625                                   ofOutermostEnclosingClass(HasAnySymbolNames),626                                   isDefinition())627                         .bind("class_method"),628                     MatchCallbacks.back().get());629  // Match static member variable definition of the moved class.630  Finder->addMatcher(631      varDecl(InMovedClass, InOldFiles, isDefinition(), isStaticDataMember())632          .bind("class_static_var_decl"),633      MatchCallbacks.back().get());634 635  MatchCallbacks.push_back(std::make_unique<FunctionDeclarationMatch>(this));636  Finder->addMatcher(functionDecl(InOldFiles, HasAnySymbolNames, TopLevelDecl)637                         .bind("function"),638                     MatchCallbacks.back().get());639 640  MatchCallbacks.push_back(std::make_unique<VarDeclarationMatch>(this));641  Finder->addMatcher(642      varDecl(InOldFiles, HasAnySymbolNames, TopLevelDecl).bind("var"),643      MatchCallbacks.back().get());644 645  // Match enum definition in old.h. Enum helpers (which are defined in old.cc)646  // will not be moved for now no matter whether they are used or not.647  MatchCallbacks.push_back(std::make_unique<EnumDeclarationMatch>(this));648  Finder->addMatcher(649      enumDecl(InOldHeader, HasAnySymbolNames, isDefinition(), TopLevelDecl)650          .bind("enum"),651      MatchCallbacks.back().get());652 653  // Match type alias in old.h, this includes "typedef" and "using" type alias654  // declarations. Type alias helpers (which are defined in old.cc) will not be655  // moved for now no matter whether they are used or not.656  MatchCallbacks.push_back(std::make_unique<TypeAliasMatch>(this));657  Finder->addMatcher(namedDecl(anyOf(typedefDecl().bind("typedef"),658                                     typeAliasDecl().bind("type_alias")),659                               InOldHeader, HasAnySymbolNames, TopLevelDecl),660                     MatchCallbacks.back().get());661}662 663void ClangMoveTool::run(const ast_matchers::MatchFinder::MatchResult &Result) {664  if (const auto *D = Result.Nodes.getNodeAs<NamedDecl>("decls_in_header")) {665    UnremovedDeclsInOldHeader.insert(D);666  } else if (const auto *FWD =667                 Result.Nodes.getNodeAs<CXXRecordDecl>("fwd_decl")) {668    // Skip all forward declarations which appear after moved class declaration.669    if (RemovedDecls.empty()) {670      if (const auto *DCT = FWD->getDescribedClassTemplate())671        MovedDecls.push_back(DCT);672      else673        MovedDecls.push_back(FWD);674    }675  } else if (const auto *ND =676                 Result.Nodes.getNodeAs<NamedDecl>("helper_decls")) {677    MovedDecls.push_back(ND);678    HelperDeclarations.push_back(ND);679    LLVM_DEBUG(llvm::dbgs()680               << "Add helper : " << ND->getDeclName() << " (" << ND << ")\n");681  } else if (const auto *UD = Result.Nodes.getNodeAs<NamedDecl>("using_decl")) {682    MovedDecls.push_back(UD);683  }684}685 686std::string ClangMoveTool::makeAbsolutePath(StringRef Path) {687  return MakeAbsolutePath(Context->OriginalRunningDirectory, Path);688}689 690void ClangMoveTool::addIncludes(llvm::StringRef IncludeHeader, bool IsAngled,691                                llvm::StringRef SearchPath,692                                llvm::StringRef FileName,693                                CharSourceRange IncludeFilenameRange,694                                const SourceManager &SM) {695  SmallString<128> HeaderWithSearchPath;696  llvm::sys::path::append(HeaderWithSearchPath, SearchPath, IncludeHeader);697  std::string AbsoluteIncludeHeader =698      MakeAbsolutePath(SM, HeaderWithSearchPath);699  std::string IncludeLine =700      IsAngled ? ("#include <" + IncludeHeader + ">\n").str()701               : ("#include \"" + IncludeHeader + "\"\n").str();702 703  std::string AbsoluteOldHeader = makeAbsolutePath(Context->Spec.OldHeader);704  std::string AbsoluteCurrentFile = MakeAbsolutePath(SM, FileName);705  if (AbsoluteOldHeader == AbsoluteCurrentFile) {706    // Find old.h includes "old.h".707    if (AbsoluteOldHeader == AbsoluteIncludeHeader) {708      OldHeaderIncludeRangeInHeader = IncludeFilenameRange;709      return;710    }711    HeaderIncludes.push_back(IncludeLine);712  } else if (makeAbsolutePath(Context->Spec.OldCC) == AbsoluteCurrentFile) {713    // Find old.cc includes "old.h".714    if (AbsoluteOldHeader == AbsoluteIncludeHeader) {715      OldHeaderIncludeRangeInCC = IncludeFilenameRange;716      return;717    }718    CCIncludes.push_back(IncludeLine);719  }720}721 722void ClangMoveTool::removeDeclsInOldFiles() {723  if (RemovedDecls.empty()) return;724 725  // If old_header is not specified (only move declarations from old.cc), remain726  // all the helper function declarations in old.cc as UnremovedDeclsInOldHeader727  // is empty in this case, there is no way to verify unused/used helpers.728  if (!Context->Spec.OldHeader.empty()) {729    std::vector<const NamedDecl *> UnremovedDecls;730    for (const auto *D : UnremovedDeclsInOldHeader)731      UnremovedDecls.push_back(D);732 733    auto UsedDecls = getUsedDecls(RGBuilder.getGraph(), UnremovedDecls);734 735    // We remove the helper declarations which are not used in the old.cc after736    // moving the given declarations.737    for (const auto *D : HelperDeclarations) {738      LLVM_DEBUG(llvm::dbgs() << "Check helper is used: " << D->getDeclName()739                              << " (" << D << ")\n");740      if (!UsedDecls.count(HelperDeclRGBuilder::getOutmostClassOrFunDecl(741              D->getCanonicalDecl()))) {742        LLVM_DEBUG(llvm::dbgs() << "Helper removed in old.cc: "743                                << D->getDeclName() << " (" << D << ")\n");744        RemovedDecls.push_back(D);745      }746    }747  }748 749  for (const auto *RemovedDecl : RemovedDecls) {750    const auto &SM = RemovedDecl->getASTContext().getSourceManager();751    auto Range = getFullRange(RemovedDecl);752    tooling::Replacement RemoveReplacement(753        SM, CharSourceRange::getCharRange(Range.getBegin(), Range.getEnd()),754        "");755    std::string FilePath = RemoveReplacement.getFilePath().str();756    auto Err = Context->FileToReplacements[FilePath].add(RemoveReplacement);757    if (Err)758      llvm::errs() << llvm::toString(std::move(Err)) << "\n";759  }760  const auto &SM = RemovedDecls[0]->getASTContext().getSourceManager();761 762  // Post process of cleanup around all the replacements.763  for (auto &FileAndReplacements : Context->FileToReplacements) {764    StringRef FilePath = FileAndReplacements.first;765    // Add #include of new header to old header.766    if (Context->Spec.OldDependOnNew &&767        MakeAbsolutePath(SM, FilePath) ==768            makeAbsolutePath(Context->Spec.OldHeader)) {769      // FIXME: Minimize the include path like clang-include-fixer.770      std::string IncludeNewH =771          "#include \"" + Context->Spec.NewHeader + "\"\n";772      // This replacement for inserting header will be cleaned up at the end.773      auto Err = FileAndReplacements.second.add(774          tooling::Replacement(FilePath, UINT_MAX, 0, IncludeNewH));775      if (Err)776        llvm::errs() << llvm::toString(std::move(Err)) << "\n";777    }778 779    auto SI = FilePathToFileID.find(FilePath);780    // Ignore replacements for new.h/cc.781    if (SI == FilePathToFileID.end()) continue;782    llvm::StringRef Code = SM.getBufferData(SI->second);783    auto Style = format::getStyle(format::DefaultFormatStyle, FilePath,784                                  Context->FallbackStyle);785    if (!Style) {786      llvm::errs() << llvm::toString(Style.takeError()) << "\n";787      continue;788    }789    auto CleanReplacements = format::cleanupAroundReplacements(790        Code, Context->FileToReplacements[std::string(FilePath)], *Style);791 792    if (!CleanReplacements) {793      llvm::errs() << llvm::toString(CleanReplacements.takeError()) << "\n";794      continue;795    }796    Context->FileToReplacements[std::string(FilePath)] = *CleanReplacements;797  }798}799 800void ClangMoveTool::moveDeclsToNewFiles() {801  std::vector<const NamedDecl *> NewHeaderDecls;802  std::vector<const NamedDecl *> NewCCDecls;803  for (const auto *MovedDecl : MovedDecls) {804    if (isInHeaderFile(MovedDecl, Context->OriginalRunningDirectory,805                       Context->Spec.OldHeader))806      NewHeaderDecls.push_back(MovedDecl);807    else808      NewCCDecls.push_back(MovedDecl);809  }810 811  auto UsedDecls = getUsedDecls(RGBuilder.getGraph(), RemovedDecls);812  std::vector<const NamedDecl *> ActualNewCCDecls;813 814  // Filter out all unused helpers in NewCCDecls.815  // We only move the used helpers (including transitively used helpers) and the816  // given symbols being moved.817  for (const auto *D : NewCCDecls) {818    if (llvm::is_contained(HelperDeclarations, D) &&819        !UsedDecls.count(HelperDeclRGBuilder::getOutmostClassOrFunDecl(820            D->getCanonicalDecl())))821      continue;822 823    LLVM_DEBUG(llvm::dbgs() << "Helper used in new.cc: " << D->getDeclName()824                            << " " << D << "\n");825    ActualNewCCDecls.push_back(D);826  }827 828  if (!Context->Spec.NewHeader.empty()) {829    std::string OldHeaderInclude =830        Context->Spec.NewDependOnOld831            ? "#include \"" + Context->Spec.OldHeader + "\"\n"832            : "";833    Context->FileToReplacements[Context->Spec.NewHeader] =834        createInsertedReplacements(HeaderIncludes, NewHeaderDecls,835                                   Context->Spec.NewHeader, /*IsHeader=*/true,836                                   OldHeaderInclude);837  }838  if (!Context->Spec.NewCC.empty())839    Context->FileToReplacements[Context->Spec.NewCC] =840        createInsertedReplacements(CCIncludes, ActualNewCCDecls,841                                   Context->Spec.NewCC);842}843 844// Move all contents from OldFile to NewFile.845void ClangMoveTool::moveAll(SourceManager &SM, StringRef OldFile,846                            StringRef NewFile) {847  auto FE = SM.getFileManager().getOptionalFileRef(makeAbsolutePath(OldFile));848  if (!FE) {849    llvm::errs() << "Failed to get file: " << OldFile << "\n";850    return;851  }852  FileID ID = SM.getOrCreateFileID(*FE, SrcMgr::C_User);853  auto Begin = SM.getLocForStartOfFile(ID);854  auto End = SM.getLocForEndOfFile(ID);855  tooling::Replacement RemoveAll(SM, CharSourceRange::getCharRange(Begin, End),856                                 "");857  std::string FilePath = RemoveAll.getFilePath().str();858  Context->FileToReplacements[FilePath] = tooling::Replacements(RemoveAll);859 860  StringRef Code = SM.getBufferData(ID);861  if (!NewFile.empty()) {862    auto AllCode =863        tooling::Replacements(tooling::Replacement(NewFile, 0, 0, Code));864    auto ReplaceOldInclude = [&](CharSourceRange OldHeaderIncludeRange) {865      AllCode = AllCode.merge(tooling::Replacements(tooling::Replacement(866          SM, OldHeaderIncludeRange, '"' + Context->Spec.NewHeader + '"')));867    };868    // Fix the case where old.h/old.cc includes "old.h", we replace the869    // `#include "old.h"` with `#include "new.h"`.870    if (Context->Spec.NewCC == NewFile && OldHeaderIncludeRangeInCC.isValid())871      ReplaceOldInclude(OldHeaderIncludeRangeInCC);872    else if (Context->Spec.NewHeader == NewFile &&873             OldHeaderIncludeRangeInHeader.isValid())874      ReplaceOldInclude(OldHeaderIncludeRangeInHeader);875    Context->FileToReplacements[std::string(NewFile)] = std::move(AllCode);876  }877}878 879void ClangMoveTool::onEndOfTranslationUnit() {880  if (Context->DumpDeclarations) {881    assert(Reporter);882    for (const auto *Decl : UnremovedDeclsInOldHeader) {883      auto Kind = Decl->getKind();884      bool Templated = Decl->isTemplated();885      const std::string QualifiedName = Decl->getQualifiedNameAsString();886      if (Kind == Decl::Kind::Var)887        Reporter->reportDeclaration(QualifiedName, "Variable", Templated);888      else if (Kind == Decl::Kind::Function ||889               Kind == Decl::Kind::FunctionTemplate)890        Reporter->reportDeclaration(QualifiedName, "Function", Templated);891      else if (Kind == Decl::Kind::ClassTemplate ||892               Kind == Decl::Kind::CXXRecord)893        Reporter->reportDeclaration(QualifiedName, "Class", Templated);894      else if (Kind == Decl::Kind::Enum)895        Reporter->reportDeclaration(QualifiedName, "Enum", Templated);896      else if (Kind == Decl::Kind::Typedef || Kind == Decl::Kind::TypeAlias ||897               Kind == Decl::Kind::TypeAliasTemplate)898        Reporter->reportDeclaration(QualifiedName, "TypeAlias", Templated);899    }900    return;901  }902 903  if (RemovedDecls.empty())904    return;905  // Ignore symbols that are not supported when checking if there is unremoved906  // symbol in old header. This makes sure that we always move old files to new907  // files when all symbols produced from dump_decls are moved.908  auto IsSupportedKind = [](const NamedDecl *Decl) {909    switch (Decl->getKind()) {910    case Decl::Kind::Function:911    case Decl::Kind::FunctionTemplate:912    case Decl::Kind::ClassTemplate:913    case Decl::Kind::CXXRecord:914    case Decl::Kind::Enum:915    case Decl::Kind::Typedef:916    case Decl::Kind::TypeAlias:917    case Decl::Kind::TypeAliasTemplate:918    case Decl::Kind::Var:919      return true;920    default:921      return false;922    }923  };924  if (llvm::none_of(UnremovedDeclsInOldHeader, IsSupportedKind) &&925      !Context->Spec.OldHeader.empty()) {926    auto &SM = RemovedDecls[0]->getASTContext().getSourceManager();927    moveAll(SM, Context->Spec.OldHeader, Context->Spec.NewHeader);928    moveAll(SM, Context->Spec.OldCC, Context->Spec.NewCC);929    return;930  }931  LLVM_DEBUG(RGBuilder.getGraph()->dump());932  moveDeclsToNewFiles();933  removeDeclsInOldFiles();934}935 936} // namespace move937} // namespace clang938