diff options
Diffstat (limited to 'clang-move/Move.cpp')
-rw-r--r-- | clang-move/Move.cpp | 936 |
1 files changed, 936 insertions, 0 deletions
diff --git a/clang-move/Move.cpp b/clang-move/Move.cpp new file mode 100644 index 00000000..efb67c68 --- /dev/null +++ b/clang-move/Move.cpp @@ -0,0 +1,936 @@ +//===-- Move.cpp - Implement ClangMove functationalities --------*- C++ -*-===// +// +// 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 "Move.h" +#include "HelperDeclRefGraph.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Format/Format.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/Core/Replacement.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Path.h" + +#define DEBUG_TYPE "clang-move" + +using namespace clang::ast_matchers; + +namespace clang { +namespace move { +namespace { + +// FIXME: Move to ASTMatchers. +AST_MATCHER(VarDecl, isStaticDataMember) { return Node.isStaticDataMember(); } + +AST_MATCHER(NamedDecl, notInMacro) { return !Node.getLocation().isMacroID(); } + +AST_MATCHER_P(Decl, hasOutermostEnclosingClass, + ast_matchers::internal::Matcher<Decl>, InnerMatcher) { + const auto *Context = Node.getDeclContext(); + if (!Context) + return false; + while (const auto *NextContext = Context->getParent()) { + if (isa<NamespaceDecl>(NextContext) || + isa<TranslationUnitDecl>(NextContext)) + break; + Context = NextContext; + } + return InnerMatcher.matches(*Decl::castFromDeclContext(Context), Finder, + Builder); +} + +AST_MATCHER_P(CXXMethodDecl, ofOutermostEnclosingClass, + ast_matchers::internal::Matcher<CXXRecordDecl>, InnerMatcher) { + const CXXRecordDecl *Parent = Node.getParent(); + if (!Parent) + return false; + while (const auto *NextParent = + dyn_cast<CXXRecordDecl>(Parent->getParent())) { + Parent = NextParent; + } + + return InnerMatcher.matches(*Parent, Finder, Builder); +} + +std::string CleanPath(StringRef PathRef) { + llvm::SmallString<128> Path(PathRef); + llvm::sys::path::remove_dots(Path, /*remove_dot_dot=*/true); + // FIXME: figure out why this is necessary. + llvm::sys::path::native(Path); + return Path.str(); +} + +// Make the Path absolute using the CurrentDir if the Path is not an absolute +// path. An empty Path will result in an empty string. +std::string MakeAbsolutePath(StringRef CurrentDir, StringRef Path) { + if (Path.empty()) + return ""; + llvm::SmallString<128> InitialDirectory(CurrentDir); + llvm::SmallString<128> AbsolutePath(Path); + llvm::sys::fs::make_absolute(InitialDirectory, AbsolutePath); + return CleanPath(std::move(AbsolutePath)); +} + +// Make the Path absolute using the current working directory of the given +// SourceManager if the Path is not an absolute path. +// +// The Path can be a path relative to the build directory, or retrieved from +// the SourceManager. +std::string MakeAbsolutePath(const SourceManager &SM, StringRef Path) { + llvm::SmallString<128> AbsolutePath(Path); + if (std::error_code EC = + SM.getFileManager().getVirtualFileSystem().makeAbsolute(AbsolutePath)) + llvm::errs() << "Warning: could not make absolute file: '" << EC.message() + << '\n'; + // Handle symbolic link path cases. + // We are trying to get the real file path of the symlink. + const DirectoryEntry *Dir = SM.getFileManager().getDirectory( + llvm::sys::path::parent_path(AbsolutePath.str())); + if (Dir) { + StringRef DirName = SM.getFileManager().getCanonicalName(Dir); + // FIXME: getCanonicalName might fail to get real path on VFS. + if (llvm::sys::path::is_absolute(DirName)) { + SmallString<128> AbsoluteFilename; + llvm::sys::path::append(AbsoluteFilename, DirName, + llvm::sys::path::filename(AbsolutePath.str())); + return CleanPath(AbsoluteFilename); + } + } + return CleanPath(AbsolutePath); +} + +// Matches AST nodes that are expanded within the given AbsoluteFilePath. +AST_POLYMORPHIC_MATCHER_P(isExpansionInFile, + AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt, TypeLoc), + std::string, AbsoluteFilePath) { + auto &SourceManager = Finder->getASTContext().getSourceManager(); + auto ExpansionLoc = SourceManager.getExpansionLoc(Node.getBeginLoc()); + if (ExpansionLoc.isInvalid()) + return false; + auto FileEntry = + SourceManager.getFileEntryForID(SourceManager.getFileID(ExpansionLoc)); + if (!FileEntry) + return false; + return MakeAbsolutePath(SourceManager, FileEntry->getName()) == + AbsoluteFilePath; +} + +class FindAllIncludes : public PPCallbacks { +public: + explicit FindAllIncludes(SourceManager *SM, ClangMoveTool *const MoveTool) + : SM(*SM), MoveTool(MoveTool) {} + + void InclusionDirective(SourceLocation HashLoc, const Token & /*IncludeTok*/, + StringRef FileName, bool IsAngled, + CharSourceRange FilenameRange, + const FileEntry * /*File*/, StringRef SearchPath, + StringRef /*RelativePath*/, + const Module * /*Imported*/, + SrcMgr::CharacteristicKind /*FileType*/) override { + if (const auto *FileEntry = SM.getFileEntryForID(SM.getFileID(HashLoc))) + MoveTool->addIncludes(FileName, IsAngled, SearchPath, + FileEntry->getName(), FilenameRange, SM); + } + +private: + const SourceManager &SM; + ClangMoveTool *const MoveTool; +}; + +/// Add a declatration being moved to new.h/cc. Note that the declaration will +/// also be deleted in old.h/cc. +void MoveDeclFromOldFileToNewFile(ClangMoveTool *MoveTool, const NamedDecl *D) { + MoveTool->getMovedDecls().push_back(D); + MoveTool->addRemovedDecl(D); + MoveTool->getUnremovedDeclsInOldHeader().erase(D); +} + +class FunctionDeclarationMatch : public MatchFinder::MatchCallback { +public: + explicit FunctionDeclarationMatch(ClangMoveTool *MoveTool) + : MoveTool(MoveTool) {} + + void run(const MatchFinder::MatchResult &Result) override { + const auto *FD = Result.Nodes.getNodeAs<FunctionDecl>("function"); + assert(FD); + const NamedDecl *D = FD; + if (const auto *FTD = FD->getDescribedFunctionTemplate()) + D = FTD; + MoveDeclFromOldFileToNewFile(MoveTool, D); + } + +private: + ClangMoveTool *MoveTool; +}; + +class VarDeclarationMatch : public MatchFinder::MatchCallback { +public: + explicit VarDeclarationMatch(ClangMoveTool *MoveTool) + : MoveTool(MoveTool) {} + + void run(const MatchFinder::MatchResult &Result) override { + const auto *VD = Result.Nodes.getNodeAs<VarDecl>("var"); + assert(VD); + MoveDeclFromOldFileToNewFile(MoveTool, VD); + } + +private: + ClangMoveTool *MoveTool; +}; + +class TypeAliasMatch : public MatchFinder::MatchCallback { +public: + explicit TypeAliasMatch(ClangMoveTool *MoveTool) + : MoveTool(MoveTool) {} + + void run(const MatchFinder::MatchResult &Result) override { + if (const auto *TD = Result.Nodes.getNodeAs<TypedefDecl>("typedef")) + MoveDeclFromOldFileToNewFile(MoveTool, TD); + else if (const auto *TAD = + Result.Nodes.getNodeAs<TypeAliasDecl>("type_alias")) { + const NamedDecl * D = TAD; + if (const auto * TD = TAD->getDescribedAliasTemplate()) + D = TD; + MoveDeclFromOldFileToNewFile(MoveTool, D); + } + } + +private: + ClangMoveTool *MoveTool; +}; + +class EnumDeclarationMatch : public MatchFinder::MatchCallback { +public: + explicit EnumDeclarationMatch(ClangMoveTool *MoveTool) + : MoveTool(MoveTool) {} + + void run(const MatchFinder::MatchResult &Result) override { + const auto *ED = Result.Nodes.getNodeAs<EnumDecl>("enum"); + assert(ED); + MoveDeclFromOldFileToNewFile(MoveTool, ED); + } + +private: + ClangMoveTool *MoveTool; +}; + +class ClassDeclarationMatch : public MatchFinder::MatchCallback { +public: + explicit ClassDeclarationMatch(ClangMoveTool *MoveTool) + : MoveTool(MoveTool) {} + void run(const MatchFinder::MatchResult &Result) override { + SourceManager *SM = &Result.Context->getSourceManager(); + if (const auto *CMD = Result.Nodes.getNodeAs<CXXMethodDecl>("class_method")) + MatchClassMethod(CMD, SM); + else if (const auto *VD = + Result.Nodes.getNodeAs<VarDecl>("class_static_var_decl")) + MatchClassStaticVariable(VD, SM); + else if (const auto *CD = + Result.Nodes.getNodeAs<CXXRecordDecl>("moved_class")) + MatchClassDeclaration(CD, SM); + } + +private: + void MatchClassMethod(const CXXMethodDecl *CMD, SourceManager *SM) { + // Skip inline class methods. isInline() ast matcher doesn't ignore this + // case. + if (!CMD->isInlined()) { + MoveTool->getMovedDecls().push_back(CMD); + MoveTool->addRemovedDecl(CMD); + // Get template class method from its method declaration as + // UnremovedDecls stores template class method. + if (const auto *FTD = CMD->getDescribedFunctionTemplate()) + MoveTool->getUnremovedDeclsInOldHeader().erase(FTD); + else + MoveTool->getUnremovedDeclsInOldHeader().erase(CMD); + } + } + + void MatchClassStaticVariable(const NamedDecl *VD, SourceManager *SM) { + MoveDeclFromOldFileToNewFile(MoveTool, VD); + } + + void MatchClassDeclaration(const CXXRecordDecl *CD, SourceManager *SM) { + // Get class template from its class declaration as UnremovedDecls stores + // class template. + if (const auto *TC = CD->getDescribedClassTemplate()) + MoveTool->getMovedDecls().push_back(TC); + else + MoveTool->getMovedDecls().push_back(CD); + MoveTool->addRemovedDecl(MoveTool->getMovedDecls().back()); + MoveTool->getUnremovedDeclsInOldHeader().erase( + MoveTool->getMovedDecls().back()); + } + + ClangMoveTool *MoveTool; +}; + +// Expand to get the end location of the line where the EndLoc of the given +// Decl. +SourceLocation getLocForEndOfDecl(const Decl *D, + const LangOptions &LangOpts = LangOptions()) { + const auto &SM = D->getASTContext().getSourceManager(); + // If the expansion range is a character range, this is the location of + // the first character past the end. Otherwise it's the location of the + // first character in the final token in the range. + auto EndExpansionLoc = SM.getExpansionRange(D->getEndLoc()).getEnd(); + std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(EndExpansionLoc); + // Try to load the file buffer. + bool InvalidTemp = false; + llvm::StringRef File = SM.getBufferData(LocInfo.first, &InvalidTemp); + if (InvalidTemp) + return SourceLocation(); + + const char *TokBegin = File.data() + LocInfo.second; + // Lex from the start of the given location. + Lexer Lex(SM.getLocForStartOfFile(LocInfo.first), LangOpts, File.begin(), + TokBegin, File.end()); + + llvm::SmallVector<char, 16> Line; + // FIXME: this is a bit hacky to get ReadToEndOfLine work. + Lex.setParsingPreprocessorDirective(true); + Lex.ReadToEndOfLine(&Line); + SourceLocation EndLoc = EndExpansionLoc.getLocWithOffset(Line.size()); + // If we already reach EOF, just return the EOF SourceLocation; + // otherwise, move 1 offset ahead to include the trailing newline character + // '\n'. + return SM.getLocForEndOfFile(LocInfo.first) == EndLoc + ? EndLoc + : EndLoc.getLocWithOffset(1); +} + +// Get full range of a Decl including the comments associated with it. +CharSourceRange getFullRange(const Decl *D, + const LangOptions &options = LangOptions()) { + const auto &SM = D->getASTContext().getSourceManager(); + SourceRange Full(SM.getExpansionLoc(D->getBeginLoc()), getLocForEndOfDecl(D)); + // Expand to comments that are associated with the Decl. + if (const auto *Comment = D->getASTContext().getRawCommentForDeclNoCache(D)) { + if (SM.isBeforeInTranslationUnit(Full.getEnd(), Comment->getEndLoc())) + Full.setEnd(Comment->getEndLoc()); + // FIXME: Don't delete a preceding comment, if there are no other entities + // it could refer to. + if (SM.isBeforeInTranslationUnit(Comment->getBeginLoc(), Full.getBegin())) + Full.setBegin(Comment->getBeginLoc()); + } + + return CharSourceRange::getCharRange(Full); +} + +std::string getDeclarationSourceText(const Decl *D) { + const auto &SM = D->getASTContext().getSourceManager(); + llvm::StringRef SourceText = + Lexer::getSourceText(getFullRange(D), SM, LangOptions()); + return SourceText.str(); +} + +bool isInHeaderFile(const Decl *D, llvm::StringRef OriginalRunningDirectory, + llvm::StringRef OldHeader) { + const auto &SM = D->getASTContext().getSourceManager(); + if (OldHeader.empty()) + return false; + auto ExpansionLoc = SM.getExpansionLoc(D->getBeginLoc()); + if (ExpansionLoc.isInvalid()) + return false; + + if (const auto *FE = SM.getFileEntryForID(SM.getFileID(ExpansionLoc))) { + return MakeAbsolutePath(SM, FE->getName()) == + MakeAbsolutePath(OriginalRunningDirectory, OldHeader); + } + + return false; +} + +std::vector<std::string> getNamespaces(const Decl *D) { + std::vector<std::string> Namespaces; + for (const auto *Context = D->getDeclContext(); Context; + Context = Context->getParent()) { + if (llvm::isa<TranslationUnitDecl>(Context) || + llvm::isa<LinkageSpecDecl>(Context)) + break; + + if (const auto *ND = llvm::dyn_cast<NamespaceDecl>(Context)) + Namespaces.push_back(ND->getName().str()); + } + std::reverse(Namespaces.begin(), Namespaces.end()); + return Namespaces; +} + +tooling::Replacements +createInsertedReplacements(const std::vector<std::string> &Includes, + const std::vector<const NamedDecl *> &Decls, + llvm::StringRef FileName, bool IsHeader = false, + StringRef OldHeaderInclude = "") { + std::string NewCode; + std::string GuardName(FileName); + if (IsHeader) { + for (size_t i = 0; i < GuardName.size(); ++i) { + if (!isAlphanumeric(GuardName[i])) + GuardName[i] = '_'; + } + GuardName = StringRef(GuardName).upper(); + NewCode += "#ifndef " + GuardName + "\n"; + NewCode += "#define " + GuardName + "\n\n"; + } + + NewCode += OldHeaderInclude; + // Add #Includes. + for (const auto &Include : Includes) + NewCode += Include; + + if (!Includes.empty()) + NewCode += "\n"; + + // Add moved class definition and its related declarations. All declarations + // in same namespace are grouped together. + // + // Record namespaces where the current position is in. + std::vector<std::string> CurrentNamespaces; + for (const auto *MovedDecl : Decls) { + // The namespaces of the declaration being moved. + std::vector<std::string> DeclNamespaces = getNamespaces(MovedDecl); + auto CurrentIt = CurrentNamespaces.begin(); + auto DeclIt = DeclNamespaces.begin(); + // Skip the common prefix. + while (CurrentIt != CurrentNamespaces.end() && + DeclIt != DeclNamespaces.end()) { + if (*CurrentIt != *DeclIt) + break; + ++CurrentIt; + ++DeclIt; + } + // Calculate the new namespaces after adding MovedDecl in CurrentNamespace, + // which is used for next iteration of this loop. + std::vector<std::string> NextNamespaces(CurrentNamespaces.begin(), + CurrentIt); + NextNamespaces.insert(NextNamespaces.end(), DeclIt, DeclNamespaces.end()); + + + // End with CurrentNamespace. + bool HasEndCurrentNamespace = false; + auto RemainingSize = CurrentNamespaces.end() - CurrentIt; + for (auto It = CurrentNamespaces.rbegin(); RemainingSize > 0; + --RemainingSize, ++It) { + assert(It < CurrentNamespaces.rend()); + NewCode += "} // namespace " + *It + "\n"; + HasEndCurrentNamespace = true; + } + // Add trailing '\n' after the nested namespace definition. + if (HasEndCurrentNamespace) + NewCode += "\n"; + + // If the moved declaration is not in CurrentNamespace, add extra namespace + // definitions. + bool IsInNewNamespace = false; + while (DeclIt != DeclNamespaces.end()) { + NewCode += "namespace " + *DeclIt + " {\n"; + IsInNewNamespace = true; + ++DeclIt; + } + // If the moved declaration is in same namespace CurrentNamespace, add + // a preceeding `\n' before the moved declaration. + // FIXME: Don't add empty lines between using declarations. + if (!IsInNewNamespace) + NewCode += "\n"; + NewCode += getDeclarationSourceText(MovedDecl); + CurrentNamespaces = std::move(NextNamespaces); + } + std::reverse(CurrentNamespaces.begin(), CurrentNamespaces.end()); + for (const auto &NS : CurrentNamespaces) + NewCode += "} // namespace " + NS + "\n"; + + if (IsHeader) + NewCode += "\n#endif // " + GuardName + "\n"; + return tooling::Replacements(tooling::Replacement(FileName, 0, 0, NewCode)); +} + +// Return a set of all decls which are used/referenced by the given Decls. +// Specically, given a class member declaration, this method will return all +// decls which are used by the whole class. +llvm::DenseSet<const Decl *> +getUsedDecls(const HelperDeclRefGraph *RG, + const std::vector<const NamedDecl *> &Decls) { + assert(RG); + llvm::DenseSet<const CallGraphNode *> Nodes; + for (const auto *D : Decls) { + auto Result = RG->getReachableNodes( + HelperDeclRGBuilder::getOutmostClassOrFunDecl(D)); + Nodes.insert(Result.begin(), Result.end()); + } + llvm::DenseSet<const Decl *> Results; + for (const auto *Node : Nodes) + Results.insert(Node->getDecl()); + return Results; +} + +} // namespace + +std::unique_ptr<ASTConsumer> +ClangMoveAction::CreateASTConsumer(CompilerInstance &Compiler, + StringRef /*InFile*/) { + Compiler.getPreprocessor().addPPCallbacks(llvm::make_unique<FindAllIncludes>( + &Compiler.getSourceManager(), &MoveTool)); + return MatchFinder.newASTConsumer(); +} + +ClangMoveTool::ClangMoveTool(ClangMoveContext *const Context, + DeclarationReporter *const Reporter) + : Context(Context), Reporter(Reporter) { + if (!Context->Spec.NewHeader.empty()) + CCIncludes.push_back("#include \"" + Context->Spec.NewHeader + "\"\n"); +} + +void ClangMoveTool::addRemovedDecl(const NamedDecl *Decl) { + const auto &SM = Decl->getASTContext().getSourceManager(); + auto Loc = Decl->getLocation(); + StringRef FilePath = SM.getFilename(Loc); + FilePathToFileID[FilePath] = SM.getFileID(Loc); + RemovedDecls.push_back(Decl); +} + +void ClangMoveTool::registerMatchers(ast_matchers::MatchFinder *Finder) { + auto InOldHeader = + isExpansionInFile(makeAbsolutePath(Context->Spec.OldHeader)); + auto InOldCC = isExpansionInFile(makeAbsolutePath(Context->Spec.OldCC)); + auto InOldFiles = anyOf(InOldHeader, InOldCC); + auto classTemplateForwardDecls = + classTemplateDecl(unless(has(cxxRecordDecl(isDefinition())))); + auto ForwardClassDecls = namedDecl( + anyOf(cxxRecordDecl(unless(anyOf(isImplicit(), isDefinition()))), + classTemplateForwardDecls)); + auto TopLevelDecl = + hasDeclContext(anyOf(namespaceDecl(), translationUnitDecl())); + + //============================================================================ + // Matchers for old header + //============================================================================ + // Match all top-level named declarations (e.g. function, variable, enum) in + // old header, exclude forward class declarations and namespace declarations. + // + // We consider declarations inside a class belongs to the class. So these + // declarations will be ignored. + auto AllDeclsInHeader = namedDecl( + unless(ForwardClassDecls), unless(namespaceDecl()), + unless(usingDirectiveDecl()), // using namespace decl. + notInMacro(), + InOldHeader, + hasParent(decl(anyOf(namespaceDecl(), translationUnitDecl()))), + hasDeclContext(decl(anyOf(namespaceDecl(), translationUnitDecl())))); + Finder->addMatcher(AllDeclsInHeader.bind("decls_in_header"), this); + + // Don't register other matchers when dumping all declarations in header. + if (Context->DumpDeclarations) + return; + + // Match forward declarations in old header. + Finder->addMatcher(namedDecl(ForwardClassDecls, InOldHeader).bind("fwd_decl"), + this); + + //============================================================================ + // Matchers for old cc + //============================================================================ + auto IsOldCCTopLevelDecl = allOf( + hasParent(decl(anyOf(namespaceDecl(), translationUnitDecl()))), InOldCC); + // Matching using decls/type alias decls which are in named/anonymous/global + // namespace, these decls are always copied to new.h/cc. Those in classes, + // functions are covered in other matchers. + Finder->addMatcher(namedDecl(anyOf(usingDecl(IsOldCCTopLevelDecl), + usingDirectiveDecl(unless(isImplicit()), + IsOldCCTopLevelDecl), + typeAliasDecl(IsOldCCTopLevelDecl)), + notInMacro()) + .bind("using_decl"), + this); + + // Match static functions/variable definitions which are defined in named + // namespaces. + Optional<ast_matchers::internal::Matcher<NamedDecl>> HasAnySymbolNames; + for (StringRef SymbolName : Context->Spec.Names) { + llvm::StringRef GlobalSymbolName = SymbolName.trim().ltrim(':'); + const auto HasName = hasName(("::" + GlobalSymbolName).str()); + HasAnySymbolNames = + HasAnySymbolNames ? anyOf(*HasAnySymbolNames, HasName) : HasName; + } + + if (!HasAnySymbolNames) { + llvm::errs() << "No symbols being moved.\n"; + return; + } + auto InMovedClass = + hasOutermostEnclosingClass(cxxRecordDecl(*HasAnySymbolNames)); + + // Matchers for helper declarations in old.cc. + auto InAnonymousNS = hasParent(namespaceDecl(isAnonymous())); + auto NotInMovedClass= allOf(unless(InMovedClass), InOldCC); + auto IsOldCCHelper = + allOf(NotInMovedClass, anyOf(isStaticStorageClass(), InAnonymousNS)); + // Match helper classes separately with helper functions/variables since we + // want to reuse these matchers in finding helpers usage below. + // + // There could be forward declarations usage for helpers, especially for + // classes and functions. We need include these forward declarations. + // + // Forward declarations for variable helpers will be excluded as these + // declarations (with "extern") are not supposed in cpp file. + auto HelperFuncOrVar = + namedDecl(notInMacro(), anyOf(functionDecl(IsOldCCHelper), + varDecl(isDefinition(), IsOldCCHelper))); + auto HelperClasses = + cxxRecordDecl(notInMacro(), NotInMovedClass, InAnonymousNS); + // Save all helper declarations in old.cc. + Finder->addMatcher( + namedDecl(anyOf(HelperFuncOrVar, HelperClasses)).bind("helper_decls"), + this); + + // Construct an AST-based call graph of helper declarations in old.cc. + // In the following matcheres, "dc" is a caller while "helper_decls" and + // "used_class" is a callee, so a new edge starting from caller to callee will + // be add in the graph. + // + // Find helper function/variable usages. + Finder->addMatcher( + declRefExpr(to(HelperFuncOrVar), hasAncestor(decl().bind("dc"))) + .bind("func_ref"), + &RGBuilder); + // Find helper class usages. + Finder->addMatcher( + typeLoc(loc(recordType(hasDeclaration(HelperClasses.bind("used_class")))), + hasAncestor(decl().bind("dc"))), + &RGBuilder); + + //============================================================================ + // Matchers for old files, including old.h/old.cc + //============================================================================ + // Create a MatchCallback for class declarations. + MatchCallbacks.push_back(llvm::make_unique<ClassDeclarationMatch>(this)); + // Match moved class declarations. + auto MovedClass = cxxRecordDecl(InOldFiles, *HasAnySymbolNames, + isDefinition(), TopLevelDecl) + .bind("moved_class"); + Finder->addMatcher(MovedClass, MatchCallbacks.back().get()); + // Match moved class methods (static methods included) which are defined + // outside moved class declaration. + Finder->addMatcher( + cxxMethodDecl(InOldFiles, ofOutermostEnclosingClass(*HasAnySymbolNames), + isDefinition()) + .bind("class_method"), + MatchCallbacks.back().get()); + // Match static member variable definition of the moved class. + Finder->addMatcher( + varDecl(InMovedClass, InOldFiles, isDefinition(), isStaticDataMember()) + .bind("class_static_var_decl"), + MatchCallbacks.back().get()); + + MatchCallbacks.push_back(llvm::make_unique<FunctionDeclarationMatch>(this)); + Finder->addMatcher(functionDecl(InOldFiles, *HasAnySymbolNames, TopLevelDecl) + .bind("function"), + MatchCallbacks.back().get()); + + MatchCallbacks.push_back(llvm::make_unique<VarDeclarationMatch>(this)); + Finder->addMatcher( + varDecl(InOldFiles, *HasAnySymbolNames, TopLevelDecl).bind("var"), + MatchCallbacks.back().get()); + + // Match enum definition in old.h. Enum helpers (which are defined in old.cc) + // will not be moved for now no matter whether they are used or not. + MatchCallbacks.push_back(llvm::make_unique<EnumDeclarationMatch>(this)); + Finder->addMatcher( + enumDecl(InOldHeader, *HasAnySymbolNames, isDefinition(), TopLevelDecl) + .bind("enum"), + MatchCallbacks.back().get()); + + // Match type alias in old.h, this includes "typedef" and "using" type alias + // declarations. Type alias helpers (which are defined in old.cc) will not be + // moved for now no matter whether they are used or not. + MatchCallbacks.push_back(llvm::make_unique<TypeAliasMatch>(this)); + Finder->addMatcher(namedDecl(anyOf(typedefDecl().bind("typedef"), + typeAliasDecl().bind("type_alias")), + InOldHeader, *HasAnySymbolNames, TopLevelDecl), + MatchCallbacks.back().get()); +} + +void ClangMoveTool::run(const ast_matchers::MatchFinder::MatchResult &Result) { + if (const auto *D = Result.Nodes.getNodeAs<NamedDecl>("decls_in_header")) { + UnremovedDeclsInOldHeader.insert(D); + } else if (const auto *FWD = + Result.Nodes.getNodeAs<CXXRecordDecl>("fwd_decl")) { + // Skip all forward declarations which appear after moved class declaration. + if (RemovedDecls.empty()) { + if (const auto *DCT = FWD->getDescribedClassTemplate()) + MovedDecls.push_back(DCT); + else + MovedDecls.push_back(FWD); + } + } else if (const auto *ND = + Result.Nodes.getNodeAs<NamedDecl>("helper_decls")) { + MovedDecls.push_back(ND); + HelperDeclarations.push_back(ND); + LLVM_DEBUG(llvm::dbgs() << "Add helper : " << ND->getNameAsString() << " (" + << ND << ")\n"); + } else if (const auto *UD = Result.Nodes.getNodeAs<NamedDecl>("using_decl")) { + MovedDecls.push_back(UD); + } +} + +std::string ClangMoveTool::makeAbsolutePath(StringRef Path) { + return MakeAbsolutePath(Context->OriginalRunningDirectory, Path); +} + +void ClangMoveTool::addIncludes(llvm::StringRef IncludeHeader, bool IsAngled, + llvm::StringRef SearchPath, + llvm::StringRef FileName, + CharSourceRange IncludeFilenameRange, + const SourceManager &SM) { + SmallVector<char, 128> HeaderWithSearchPath; + llvm::sys::path::append(HeaderWithSearchPath, SearchPath, IncludeHeader); + std::string AbsoluteIncludeHeader = + MakeAbsolutePath(SM, llvm::StringRef(HeaderWithSearchPath.data(), + HeaderWithSearchPath.size())); + std::string IncludeLine = + IsAngled ? ("#include <" + IncludeHeader + ">\n").str() + : ("#include \"" + IncludeHeader + "\"\n").str(); + + std::string AbsoluteOldHeader = makeAbsolutePath(Context->Spec.OldHeader); + std::string AbsoluteCurrentFile = MakeAbsolutePath(SM, FileName); + if (AbsoluteOldHeader == AbsoluteCurrentFile) { + // Find old.h includes "old.h". + if (AbsoluteOldHeader == AbsoluteIncludeHeader) { + OldHeaderIncludeRangeInHeader = IncludeFilenameRange; + return; + } + HeaderIncludes.push_back(IncludeLine); + } else if (makeAbsolutePath(Context->Spec.OldCC) == AbsoluteCurrentFile) { + // Find old.cc includes "old.h". + if (AbsoluteOldHeader == AbsoluteIncludeHeader) { + OldHeaderIncludeRangeInCC = IncludeFilenameRange; + return; + } + CCIncludes.push_back(IncludeLine); + } +} + +void ClangMoveTool::removeDeclsInOldFiles() { + if (RemovedDecls.empty()) return; + + // If old_header is not specified (only move declarations from old.cc), remain + // all the helper function declarations in old.cc as UnremovedDeclsInOldHeader + // is empty in this case, there is no way to verify unused/used helpers. + if (!Context->Spec.OldHeader.empty()) { + std::vector<const NamedDecl *> UnremovedDecls; + for (const auto *D : UnremovedDeclsInOldHeader) + UnremovedDecls.push_back(D); + + auto UsedDecls = getUsedDecls(RGBuilder.getGraph(), UnremovedDecls); + + // We remove the helper declarations which are not used in the old.cc after + // moving the given declarations. + for (const auto *D : HelperDeclarations) { + LLVM_DEBUG(llvm::dbgs() << "Check helper is used: " + << D->getNameAsString() << " (" << D << ")\n"); + if (!UsedDecls.count(HelperDeclRGBuilder::getOutmostClassOrFunDecl( + D->getCanonicalDecl()))) { + LLVM_DEBUG(llvm::dbgs() << "Helper removed in old.cc: " + << D->getNameAsString() << " (" << D << ")\n"); + RemovedDecls.push_back(D); + } + } + } + + for (const auto *RemovedDecl : RemovedDecls) { + const auto &SM = RemovedDecl->getASTContext().getSourceManager(); + auto Range = getFullRange(RemovedDecl); + tooling::Replacement RemoveReplacement( + SM, CharSourceRange::getCharRange(Range.getBegin(), Range.getEnd()), + ""); + std::string FilePath = RemoveReplacement.getFilePath().str(); + auto Err = Context->FileToReplacements[FilePath].add(RemoveReplacement); + if (Err) + llvm::errs() << llvm::toString(std::move(Err)) << "\n"; + } + const auto &SM = RemovedDecls[0]->getASTContext().getSourceManager(); + + // Post process of cleanup around all the replacements. + for (auto &FileAndReplacements : Context->FileToReplacements) { + StringRef FilePath = FileAndReplacements.first; + // Add #include of new header to old header. + if (Context->Spec.OldDependOnNew && + MakeAbsolutePath(SM, FilePath) == + makeAbsolutePath(Context->Spec.OldHeader)) { + // FIXME: Minimize the include path like clang-include-fixer. + std::string IncludeNewH = + "#include \"" + Context->Spec.NewHeader + "\"\n"; + // This replacment for inserting header will be cleaned up at the end. + auto Err = FileAndReplacements.second.add( + tooling::Replacement(FilePath, UINT_MAX, 0, IncludeNewH)); + if (Err) + llvm::errs() << llvm::toString(std::move(Err)) << "\n"; + } + + auto SI = FilePathToFileID.find(FilePath); + // Ignore replacements for new.h/cc. + if (SI == FilePathToFileID.end()) continue; + llvm::StringRef Code = SM.getBufferData(SI->second); + auto Style = format::getStyle(format::DefaultFormatStyle, FilePath, + Context->FallbackStyle); + if (!Style) { + llvm::errs() << llvm::toString(Style.takeError()) << "\n"; + continue; + } + auto CleanReplacements = format::cleanupAroundReplacements( + Code, Context->FileToReplacements[FilePath], *Style); + + if (!CleanReplacements) { + llvm::errs() << llvm::toString(CleanReplacements.takeError()) << "\n"; + continue; + } + Context->FileToReplacements[FilePath] = *CleanReplacements; + } +} + +void ClangMoveTool::moveDeclsToNewFiles() { + std::vector<const NamedDecl *> NewHeaderDecls; + std::vector<const NamedDecl *> NewCCDecls; + for (const auto *MovedDecl : MovedDecls) { + if (isInHeaderFile(MovedDecl, Context->OriginalRunningDirectory, + Context->Spec.OldHeader)) + NewHeaderDecls.push_back(MovedDecl); + else + NewCCDecls.push_back(MovedDecl); + } + + auto UsedDecls = getUsedDecls(RGBuilder.getGraph(), RemovedDecls); + std::vector<const NamedDecl *> ActualNewCCDecls; + + // Filter out all unused helpers in NewCCDecls. + // We only move the used helpers (including transively used helpers) and the + // given symbols being moved. + for (const auto *D : NewCCDecls) { + if (llvm::is_contained(HelperDeclarations, D) && + !UsedDecls.count(HelperDeclRGBuilder::getOutmostClassOrFunDecl( + D->getCanonicalDecl()))) + continue; + + LLVM_DEBUG(llvm::dbgs() << "Helper used in new.cc: " << D->getNameAsString() + << " " << D << "\n"); + ActualNewCCDecls.push_back(D); + } + + if (!Context->Spec.NewHeader.empty()) { + std::string OldHeaderInclude = + Context->Spec.NewDependOnOld + ? "#include \"" + Context->Spec.OldHeader + "\"\n" + : ""; + Context->FileToReplacements[Context->Spec.NewHeader] = + createInsertedReplacements(HeaderIncludes, NewHeaderDecls, + Context->Spec.NewHeader, /*IsHeader=*/true, + OldHeaderInclude); + } + if (!Context->Spec.NewCC.empty()) + Context->FileToReplacements[Context->Spec.NewCC] = + createInsertedReplacements(CCIncludes, ActualNewCCDecls, + Context->Spec.NewCC); +} + +// Move all contents from OldFile to NewFile. +void ClangMoveTool::moveAll(SourceManager &SM, StringRef OldFile, + StringRef NewFile) { + const FileEntry *FE = SM.getFileManager().getFile(makeAbsolutePath(OldFile)); + if (!FE) { + llvm::errs() << "Failed to get file: " << OldFile << "\n"; + return; + } + FileID ID = SM.getOrCreateFileID(FE, SrcMgr::C_User); + auto Begin = SM.getLocForStartOfFile(ID); + auto End = SM.getLocForEndOfFile(ID); + tooling::Replacement RemoveAll(SM, CharSourceRange::getCharRange(Begin, End), + ""); + std::string FilePath = RemoveAll.getFilePath().str(); + Context->FileToReplacements[FilePath] = tooling::Replacements(RemoveAll); + + StringRef Code = SM.getBufferData(ID); + if (!NewFile.empty()) { + auto AllCode = + tooling::Replacements(tooling::Replacement(NewFile, 0, 0, Code)); + auto ReplaceOldInclude = [&](CharSourceRange OldHeaderIncludeRange) { + AllCode = AllCode.merge(tooling::Replacements(tooling::Replacement( + SM, OldHeaderIncludeRange, '"' + Context->Spec.NewHeader + '"'))); + }; + // Fix the case where old.h/old.cc includes "old.h", we replace the + // `#include "old.h"` with `#include "new.h"`. + if (Context->Spec.NewCC == NewFile && OldHeaderIncludeRangeInCC.isValid()) + ReplaceOldInclude(OldHeaderIncludeRangeInCC); + else if (Context->Spec.NewHeader == NewFile && + OldHeaderIncludeRangeInHeader.isValid()) + ReplaceOldInclude(OldHeaderIncludeRangeInHeader); + Context->FileToReplacements[NewFile] = std::move(AllCode); + } +} + +void ClangMoveTool::onEndOfTranslationUnit() { + if (Context->DumpDeclarations) { + assert(Reporter); + for (const auto *Decl : UnremovedDeclsInOldHeader) { + auto Kind = Decl->getKind(); + bool Templated = Decl->isTemplated(); + const std::string QualifiedName = Decl->getQualifiedNameAsString(); + if (Kind == Decl::Kind::Var) + Reporter->reportDeclaration(QualifiedName, "Variable", Templated); + else if (Kind == Decl::Kind::Function || + Kind == Decl::Kind::FunctionTemplate) + Reporter->reportDeclaration(QualifiedName, "Function", Templated); + else if (Kind == Decl::Kind::ClassTemplate || + Kind == Decl::Kind::CXXRecord) + Reporter->reportDeclaration(QualifiedName, "Class", Templated); + else if (Kind == Decl::Kind::Enum) + Reporter->reportDeclaration(QualifiedName, "Enum", Templated); + else if (Kind == Decl::Kind::Typedef || Kind == Decl::Kind::TypeAlias || + Kind == Decl::Kind::TypeAliasTemplate) + Reporter->reportDeclaration(QualifiedName, "TypeAlias", Templated); + } + return; + } + + if (RemovedDecls.empty()) + return; + // Ignore symbols that are not supported when checking if there is unremoved + // symbol in old header. This makes sure that we always move old files to new + // files when all symbols produced from dump_decls are moved. + auto IsSupportedKind = [](const NamedDecl *Decl) { + switch (Decl->getKind()) { + case Decl::Kind::Function: + case Decl::Kind::FunctionTemplate: + case Decl::Kind::ClassTemplate: + case Decl::Kind::CXXRecord: + case Decl::Kind::Enum: + case Decl::Kind::Typedef: + case Decl::Kind::TypeAlias: + case Decl::Kind::TypeAliasTemplate: + case Decl::Kind::Var: + return true; + default: + return false; + } + }; + if (std::none_of(UnremovedDeclsInOldHeader.begin(), + UnremovedDeclsInOldHeader.end(), IsSupportedKind) && + !Context->Spec.OldHeader.empty()) { + auto &SM = RemovedDecls[0]->getASTContext().getSourceManager(); + moveAll(SM, Context->Spec.OldHeader, Context->Spec.NewHeader); + moveAll(SM, Context->Spec.OldCC, Context->Spec.NewCC); + return; + } + LLVM_DEBUG(RGBuilder.getGraph()->dump()); + moveDeclsToNewFiles(); + removeDeclsInOldFiles(); +} + +} // namespace move +} // namespace clang |