Index: clang-move/CMakeLists.txt =================================================================== --- clang-move/CMakeLists.txt +++ clang-move/CMakeLists.txt @@ -3,7 +3,7 @@ ) add_clang_library(clangMove - ClangMove.cpp + Move.cpp HelperDeclRefGraph.cpp LINK_LIBS Index: clang-move/ClangMove.h =================================================================== --- clang-move/ClangMove.h +++ clang-move/ClangMove.h @@ -1,240 +0,0 @@ -//===-- ClangMove.h - Clang move -----------------------------------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_CLANGMOVE_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_CLANGMOVE_H - -#include "HelperDeclRefGraph.h" -#include "clang/ASTMatchers/ASTMatchFinder.h" -#include "clang/Frontend/FrontendAction.h" -#include "clang/Tooling/Core/Replacement.h" -#include "clang/Tooling/Tooling.h" -#include "llvm/ADT/SmallPtrSet.h" -#include "llvm/ADT/StringMap.h" -#include "llvm/ADT/StringRef.h" -#include -#include -#include -#include - -namespace clang { -namespace move { - -// A reporter which collects and reports declarations in old header. -class DeclarationReporter { -public: - DeclarationReporter() = default; - ~DeclarationReporter() = default; - - void reportDeclaration(llvm::StringRef DeclarationName, llvm::StringRef Type, - bool Templated) { - DeclarationList.emplace_back(DeclarationName, Type, Templated); - }; - - struct Declaration { - Declaration(llvm::StringRef QName, llvm::StringRef Kind, bool Templated) - : QualifiedName(QName), Kind(Kind), Templated(Templated) {} - - friend bool operator==(const Declaration &LHS, const Declaration &RHS) { - return std::tie(LHS.QualifiedName, LHS.Kind, LHS.Templated) == - std::tie(RHS.QualifiedName, RHS.Kind, RHS.Templated); - } - std::string QualifiedName; // E.g. A::B::Foo. - std::string Kind; // E.g. Function, Class - bool Templated = false; // Whether the declaration is templated. - }; - - const std::vector getDeclarationList() const { - return DeclarationList; - } - -private: - std::vector DeclarationList; -}; - -// Specify declarations being moved. It contains all information of the moved -// declarations. -struct MoveDefinitionSpec { - // The list of fully qualified names, e.g. Foo, a::Foo, b::Foo. - SmallVector Names; - // The file path of old header, can be relative path and absolute path. - std::string OldHeader; - // The file path of old cc, can be relative path and absolute path. - std::string OldCC; - // The file path of new header, can be relative path and absolute path. - std::string NewHeader; - // The file path of new cc, can be relative path and absolute path. - std::string NewCC; - // Whether old.h depends on new.h. If true, #include "new.h" will be added - // in old.h. - bool OldDependOnNew = false; - // Whether new.h depends on old.h. If true, #include "old.h" will be added - // in new.h. - bool NewDependOnOld = false; -}; - -// A Context which contains extra options which are used in ClangMoveTool. -struct ClangMoveContext { - MoveDefinitionSpec Spec; - // The Key is file path, value is the replacements being applied to the file. - std::map &FileToReplacements; - // The original working directory where the local clang-move binary runs. - // - // clang-move will change its current working directory to the build - // directory when analyzing the source file. We save the original working - // directory in order to get the absolute file path for the fields in Spec. - std::string OriginalRunningDirectory; - // The name of a predefined code style. - std::string FallbackStyle; - // Whether dump all declarations in old header. - bool DumpDeclarations; -}; - -// This tool is used to move class/function definitions from the given source -// files (old.h/cc) to new files (new.h/cc). -// The goal of this tool is to make the new/old files as compilable as possible. -// -// When moving a symbol,all used helper declarations (e.g. static -// functions/variables definitions in global/named namespace, -// functions/variables/classes definitions in anonymous namespace) used by the -// moved symbol in old.cc are moved to the new.cc. In addition, all -// using-declarations in old.cc are also moved to new.cc; forward class -// declarations in old.h are also moved to new.h. -// -// The remaining helper declarations which are unused by non-moved symbols in -// old.cc will be removed. -// -// Note: When all declarations in old header are being moved, all code in -// old.h/cc will be moved, which means old.h/cc are empty. This ignores symbols -// that are not supported (e.g. typedef and enum) so that we always move old -// files to new files when all symbols produced from dump_decls are moved. -class ClangMoveTool : public ast_matchers::MatchFinder::MatchCallback { -public: - ClangMoveTool(ClangMoveContext *const Context, - DeclarationReporter *const Reporter); - - void registerMatchers(ast_matchers::MatchFinder *Finder); - - void run(const ast_matchers::MatchFinder::MatchResult &Result) override; - - void onEndOfTranslationUnit() override; - - /// Add #includes from old.h/cc files. - /// - /// \param IncludeHeader The name of the file being included, as written in - /// the source code. - /// \param IsAngled Whether the file name was enclosed in angle brackets. - /// \param SearchPath The search path which was used to find the IncludeHeader - /// in the file system. It can be a relative path or an absolute path. - /// \param FileName The name of file where the IncludeHeader comes from. - /// \param IncludeFilenameRange The source range for the written file name in - /// #include (i.e. "old.h" for #include "old.h") in old.cc. - /// \param SM The SourceManager. - void addIncludes(llvm::StringRef IncludeHeader, bool IsAngled, - llvm::StringRef SearchPath, llvm::StringRef FileName, - clang::CharSourceRange IncludeFilenameRange, - const SourceManager &SM); - - std::vector &getMovedDecls() { return MovedDecls; } - - /// Add declarations being removed from old.h/cc. For each declarations, the - /// method also records the mapping relationship between the corresponding - /// FilePath and its FileID. - void addRemovedDecl(const NamedDecl *Decl); - - llvm::SmallPtrSet &getUnremovedDeclsInOldHeader() { - return UnremovedDeclsInOldHeader; - } - -private: - // Make the Path absolute using the OrignalRunningDirectory if the Path is not - // an absolute path. An empty Path will result in an empty string. - std::string makeAbsolutePath(StringRef Path); - - void removeDeclsInOldFiles(); - void moveDeclsToNewFiles(); - void moveAll(SourceManager& SM, StringRef OldFile, StringRef NewFile); - - // Stores all MatchCallbacks created by this tool. - std::vector> - MatchCallbacks; - // Store all potential declarations (decls being moved, forward decls) that - // might need to move to new.h/cc. It includes all helper declarations - // (include unused ones) by default. The unused ones will be filtered out in - // the last stage. Saving in an AST-visited order. - std::vector MovedDecls; - // The declarations that needs to be removed in old.cc/h. - std::vector RemovedDecls; - // The #includes in old_header.h. - std::vector HeaderIncludes; - // The #includes in old_cc.cc. - std::vector CCIncludes; - // Records all helper declarations (function/variable/class definitions in - // anonymous namespaces, static function/variable definitions in global/named - // namespaces) in old.cc. saving in an AST-visited order. - std::vector HelperDeclarations; - // The unmoved named declarations in old header. - llvm::SmallPtrSet UnremovedDeclsInOldHeader; - /// The source range for the written file name in #include (i.e. "old.h" for - /// #include "old.h") in old.cc, including the enclosing quotes or angle - /// brackets. - clang::CharSourceRange OldHeaderIncludeRangeInCC; - /// The source range for the written file name in #include (i.e. "old.h" for - /// #include "old.h") in old.h, including the enclosing quotes or angle - /// brackets. - clang::CharSourceRange OldHeaderIncludeRangeInHeader; - /// Mapping from FilePath to FileID, which can be used in post processes like - /// cleanup around replacements. - llvm::StringMap FilePathToFileID; - /// A context contains all running options. It is not owned. - ClangMoveContext *const Context; - /// A reporter to report all declarations from old header. It is not owned. - DeclarationReporter *const Reporter; - /// Builder for helper declarations reference graph. - HelperDeclRGBuilder RGBuilder; -}; - -class ClangMoveAction : public clang::ASTFrontendAction { -public: - ClangMoveAction(ClangMoveContext *const Context, - DeclarationReporter *const Reporter) - : MoveTool(Context, Reporter) { - MoveTool.registerMatchers(&MatchFinder); - } - - ~ClangMoveAction() override = default; - - std::unique_ptr - CreateASTConsumer(clang::CompilerInstance &Compiler, - llvm::StringRef InFile) override; - -private: - ast_matchers::MatchFinder MatchFinder; - ClangMoveTool MoveTool; -}; - -class ClangMoveActionFactory : public tooling::FrontendActionFactory { -public: - ClangMoveActionFactory(ClangMoveContext *const Context, - DeclarationReporter *const Reporter = nullptr) - : Context(Context), Reporter(Reporter) {} - - clang::FrontendAction *create() override { - return new ClangMoveAction(Context, Reporter); - } - -private: - // Not owned. - ClangMoveContext *const Context; - DeclarationReporter *const Reporter; -}; - -} // namespace move -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_CLANGMOVE_H Index: clang-move/ClangMove.cpp =================================================================== --- clang-move/ClangMove.cpp +++ clang-move/ClangMove.cpp @@ -1,937 +0,0 @@ -//===-- ClangMove.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 "ClangMove.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, InnerMatcher) { - const auto *Context = Node.getDeclContext(); - if (!Context) - return false; - while (const auto *NextContext = Context->getParent()) { - if (isa(NextContext) || - isa(NextContext)) - break; - Context = NextContext; - } - return InnerMatcher.matches(*Decl::castFromDeclContext(Context), Finder, - Builder); -} - -AST_MATCHER_P(CXXMethodDecl, ofOutermostEnclosingClass, - ast_matchers::internal::Matcher, InnerMatcher) { - const CXXRecordDecl *Parent = Node.getParent(); - if (!Parent) - return false; - while (const auto *NextParent = - dyn_cast(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("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("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("typedef")) - MoveDeclFromOldFileToNewFile(MoveTool, TD); - else if (const auto *TAD = - Result.Nodes.getNodeAs("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("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("class_method")) - MatchClassMethod(CMD, SM); - else if (const auto *VD = - Result.Nodes.getNodeAs("class_static_var_decl")) - MatchClassStaticVariable(VD, SM); - else if (const auto *CD = - Result.Nodes.getNodeAs("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 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 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 getNamespaces(const Decl *D) { - std::vector Namespaces; - for (const auto *Context = D->getDeclContext(); Context; - Context = Context->getParent()) { - if (llvm::isa(Context) || - llvm::isa(Context)) - break; - - if (const auto *ND = llvm::dyn_cast(Context)) - Namespaces.push_back(ND->getName().str()); - } - std::reverse(Namespaces.begin(), Namespaces.end()); - return Namespaces; -} - -tooling::Replacements -createInsertedReplacements(const std::vector &Includes, - const std::vector &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 CurrentNamespaces; - for (const auto *MovedDecl : Decls) { - // The namespaces of the declaration being moved. - std::vector 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 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 -getUsedDecls(const HelperDeclRefGraph *RG, - const std::vector &Decls) { - assert(RG); - llvm::DenseSet Nodes; - for (const auto *D : Decls) { - auto Result = RG->getReachableNodes( - HelperDeclRGBuilder::getOutmostClassOrFunDecl(D)); - Nodes.insert(Result.begin(), Result.end()); - } - llvm::DenseSet Results; - for (const auto *Node : Nodes) - Results.insert(Node->getDecl()); - return Results; -} - -} // namespace - -std::unique_ptr -ClangMoveAction::CreateASTConsumer(CompilerInstance &Compiler, - StringRef /*InFile*/) { - Compiler.getPreprocessor().addPPCallbacks(llvm::make_unique( - &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> 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(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(this)); - Finder->addMatcher(functionDecl(InOldFiles, *HasAnySymbolNames, TopLevelDecl) - .bind("function"), - MatchCallbacks.back().get()); - - MatchCallbacks.push_back(llvm::make_unique(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(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(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("decls_in_header")) { - UnremovedDeclsInOldHeader.insert(D); - } else if (const auto *FWD = - Result.Nodes.getNodeAs("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("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("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 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 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 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 NewHeaderDecls; - std::vector 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 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 Index: clang-move/HelperDeclRefGraph.cpp =================================================================== --- clang-move/HelperDeclRefGraph.cpp +++ clang-move/HelperDeclRefGraph.cpp @@ -7,7 +7,7 @@ //===----------------------------------------------------------------------===// #include "HelperDeclRefGraph.h" -#include "ClangMove.h" +#include "Move.h" #include "clang/AST/Decl.h" #include "llvm/Support/Debug.h" #include Index: clang-move/Move.h =================================================================== --- clang-move/Move.h +++ clang-move/Move.h @@ -0,0 +1,240 @@ +//===-- Move.h - Clang move ----------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_CLANGMOVE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_CLANGMOVE_H + +#include "HelperDeclRefGraph.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Tooling/Core/Replacement.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include +#include +#include +#include + +namespace clang { +namespace move { + +// A reporter which collects and reports declarations in old header. +class DeclarationReporter { +public: + DeclarationReporter() = default; + ~DeclarationReporter() = default; + + void reportDeclaration(llvm::StringRef DeclarationName, llvm::StringRef Type, + bool Templated) { + DeclarationList.emplace_back(DeclarationName, Type, Templated); + }; + + struct Declaration { + Declaration(llvm::StringRef QName, llvm::StringRef Kind, bool Templated) + : QualifiedName(QName), Kind(Kind), Templated(Templated) {} + + friend bool operator==(const Declaration &LHS, const Declaration &RHS) { + return std::tie(LHS.QualifiedName, LHS.Kind, LHS.Templated) == + std::tie(RHS.QualifiedName, RHS.Kind, RHS.Templated); + } + std::string QualifiedName; // E.g. A::B::Foo. + std::string Kind; // E.g. Function, Class + bool Templated = false; // Whether the declaration is templated. + }; + + const std::vector getDeclarationList() const { + return DeclarationList; + } + +private: + std::vector DeclarationList; +}; + +// Specify declarations being moved. It contains all information of the moved +// declarations. +struct MoveDefinitionSpec { + // The list of fully qualified names, e.g. Foo, a::Foo, b::Foo. + SmallVector Names; + // The file path of old header, can be relative path and absolute path. + std::string OldHeader; + // The file path of old cc, can be relative path and absolute path. + std::string OldCC; + // The file path of new header, can be relative path and absolute path. + std::string NewHeader; + // The file path of new cc, can be relative path and absolute path. + std::string NewCC; + // Whether old.h depends on new.h. If true, #include "new.h" will be added + // in old.h. + bool OldDependOnNew = false; + // Whether new.h depends on old.h. If true, #include "old.h" will be added + // in new.h. + bool NewDependOnOld = false; +}; + +// A Context which contains extra options which are used in ClangMoveTool. +struct ClangMoveContext { + MoveDefinitionSpec Spec; + // The Key is file path, value is the replacements being applied to the file. + std::map &FileToReplacements; + // The original working directory where the local clang-move binary runs. + // + // clang-move will change its current working directory to the build + // directory when analyzing the source file. We save the original working + // directory in order to get the absolute file path for the fields in Spec. + std::string OriginalRunningDirectory; + // The name of a predefined code style. + std::string FallbackStyle; + // Whether dump all declarations in old header. + bool DumpDeclarations; +}; + +// This tool is used to move class/function definitions from the given source +// files (old.h/cc) to new files (new.h/cc). +// The goal of this tool is to make the new/old files as compilable as possible. +// +// When moving a symbol,all used helper declarations (e.g. static +// functions/variables definitions in global/named namespace, +// functions/variables/classes definitions in anonymous namespace) used by the +// moved symbol in old.cc are moved to the new.cc. In addition, all +// using-declarations in old.cc are also moved to new.cc; forward class +// declarations in old.h are also moved to new.h. +// +// The remaining helper declarations which are unused by non-moved symbols in +// old.cc will be removed. +// +// Note: When all declarations in old header are being moved, all code in +// old.h/cc will be moved, which means old.h/cc are empty. This ignores symbols +// that are not supported (e.g. typedef and enum) so that we always move old +// files to new files when all symbols produced from dump_decls are moved. +class ClangMoveTool : public ast_matchers::MatchFinder::MatchCallback { +public: + ClangMoveTool(ClangMoveContext *const Context, + DeclarationReporter *const Reporter); + + void registerMatchers(ast_matchers::MatchFinder *Finder); + + void run(const ast_matchers::MatchFinder::MatchResult &Result) override; + + void onEndOfTranslationUnit() override; + + /// Add #includes from old.h/cc files. + /// + /// \param IncludeHeader The name of the file being included, as written in + /// the source code. + /// \param IsAngled Whether the file name was enclosed in angle brackets. + /// \param SearchPath The search path which was used to find the IncludeHeader + /// in the file system. It can be a relative path or an absolute path. + /// \param FileName The name of file where the IncludeHeader comes from. + /// \param IncludeFilenameRange The source range for the written file name in + /// #include (i.e. "old.h" for #include "old.h") in old.cc. + /// \param SM The SourceManager. + void addIncludes(llvm::StringRef IncludeHeader, bool IsAngled, + llvm::StringRef SearchPath, llvm::StringRef FileName, + clang::CharSourceRange IncludeFilenameRange, + const SourceManager &SM); + + std::vector &getMovedDecls() { return MovedDecls; } + + /// Add declarations being removed from old.h/cc. For each declarations, the + /// method also records the mapping relationship between the corresponding + /// FilePath and its FileID. + void addRemovedDecl(const NamedDecl *Decl); + + llvm::SmallPtrSet &getUnremovedDeclsInOldHeader() { + return UnremovedDeclsInOldHeader; + } + +private: + // Make the Path absolute using the OrignalRunningDirectory if the Path is not + // an absolute path. An empty Path will result in an empty string. + std::string makeAbsolutePath(StringRef Path); + + void removeDeclsInOldFiles(); + void moveDeclsToNewFiles(); + void moveAll(SourceManager& SM, StringRef OldFile, StringRef NewFile); + + // Stores all MatchCallbacks created by this tool. + std::vector> + MatchCallbacks; + // Store all potential declarations (decls being moved, forward decls) that + // might need to move to new.h/cc. It includes all helper declarations + // (include unused ones) by default. The unused ones will be filtered out in + // the last stage. Saving in an AST-visited order. + std::vector MovedDecls; + // The declarations that needs to be removed in old.cc/h. + std::vector RemovedDecls; + // The #includes in old_header.h. + std::vector HeaderIncludes; + // The #includes in old_cc.cc. + std::vector CCIncludes; + // Records all helper declarations (function/variable/class definitions in + // anonymous namespaces, static function/variable definitions in global/named + // namespaces) in old.cc. saving in an AST-visited order. + std::vector HelperDeclarations; + // The unmoved named declarations in old header. + llvm::SmallPtrSet UnremovedDeclsInOldHeader; + /// The source range for the written file name in #include (i.e. "old.h" for + /// #include "old.h") in old.cc, including the enclosing quotes or angle + /// brackets. + clang::CharSourceRange OldHeaderIncludeRangeInCC; + /// The source range for the written file name in #include (i.e. "old.h" for + /// #include "old.h") in old.h, including the enclosing quotes or angle + /// brackets. + clang::CharSourceRange OldHeaderIncludeRangeInHeader; + /// Mapping from FilePath to FileID, which can be used in post processes like + /// cleanup around replacements. + llvm::StringMap FilePathToFileID; + /// A context contains all running options. It is not owned. + ClangMoveContext *const Context; + /// A reporter to report all declarations from old header. It is not owned. + DeclarationReporter *const Reporter; + /// Builder for helper declarations reference graph. + HelperDeclRGBuilder RGBuilder; +}; + +class ClangMoveAction : public clang::ASTFrontendAction { +public: + ClangMoveAction(ClangMoveContext *const Context, + DeclarationReporter *const Reporter) + : MoveTool(Context, Reporter) { + MoveTool.registerMatchers(&MatchFinder); + } + + ~ClangMoveAction() override = default; + + std::unique_ptr + CreateASTConsumer(clang::CompilerInstance &Compiler, + llvm::StringRef InFile) override; + +private: + ast_matchers::MatchFinder MatchFinder; + ClangMoveTool MoveTool; +}; + +class ClangMoveActionFactory : public tooling::FrontendActionFactory { +public: + ClangMoveActionFactory(ClangMoveContext *const Context, + DeclarationReporter *const Reporter = nullptr) + : Context(Context), Reporter(Reporter) {} + + clang::FrontendAction *create() override { + return new ClangMoveAction(Context, Reporter); + } + +private: + // Not owned. + ClangMoveContext *const Context; + DeclarationReporter *const Reporter; +}; + +} // namespace move +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_CLANGMOVE_H Index: clang-move/Move.cpp =================================================================== --- clang-move/Move.cpp +++ clang-move/Move.cpp @@ -0,0 +1,937 @@ +//===-- 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, InnerMatcher) { + const auto *Context = Node.getDeclContext(); + if (!Context) + return false; + while (const auto *NextContext = Context->getParent()) { + if (isa(NextContext) || + isa(NextContext)) + break; + Context = NextContext; + } + return InnerMatcher.matches(*Decl::castFromDeclContext(Context), Finder, + Builder); +} + +AST_MATCHER_P(CXXMethodDecl, ofOutermostEnclosingClass, + ast_matchers::internal::Matcher, InnerMatcher) { + const CXXRecordDecl *Parent = Node.getParent(); + if (!Parent) + return false; + while (const auto *NextParent = + dyn_cast(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("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("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("typedef")) + MoveDeclFromOldFileToNewFile(MoveTool, TD); + else if (const auto *TAD = + Result.Nodes.getNodeAs("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("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("class_method")) + MatchClassMethod(CMD, SM); + else if (const auto *VD = + Result.Nodes.getNodeAs("class_static_var_decl")) + MatchClassStaticVariable(VD, SM); + else if (const auto *CD = + Result.Nodes.getNodeAs("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 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 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 getNamespaces(const Decl *D) { + std::vector Namespaces; + for (const auto *Context = D->getDeclContext(); Context; + Context = Context->getParent()) { + if (llvm::isa(Context) || + llvm::isa(Context)) + break; + + if (const auto *ND = llvm::dyn_cast(Context)) + Namespaces.push_back(ND->getName().str()); + } + std::reverse(Namespaces.begin(), Namespaces.end()); + return Namespaces; +} + +tooling::Replacements +createInsertedReplacements(const std::vector &Includes, + const std::vector &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 CurrentNamespaces; + for (const auto *MovedDecl : Decls) { + // The namespaces of the declaration being moved. + std::vector 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 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 +getUsedDecls(const HelperDeclRefGraph *RG, + const std::vector &Decls) { + assert(RG); + llvm::DenseSet Nodes; + for (const auto *D : Decls) { + auto Result = RG->getReachableNodes( + HelperDeclRGBuilder::getOutmostClassOrFunDecl(D)); + Nodes.insert(Result.begin(), Result.end()); + } + llvm::DenseSet Results; + for (const auto *Node : Nodes) + Results.insert(Node->getDecl()); + return Results; +} + +} // namespace + +std::unique_ptr +ClangMoveAction::CreateASTConsumer(CompilerInstance &Compiler, + StringRef /*InFile*/) { + Compiler.getPreprocessor().addPPCallbacks(llvm::make_unique( + &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> 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(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(this)); + Finder->addMatcher(functionDecl(InOldFiles, *HasAnySymbolNames, TopLevelDecl) + .bind("function"), + MatchCallbacks.back().get()); + + MatchCallbacks.push_back(llvm::make_unique(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(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(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("decls_in_header")) { + UnremovedDeclsInOldHeader.insert(D); + } else if (const auto *FWD = + Result.Nodes.getNodeAs("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("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("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 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 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 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 NewHeaderDecls; + std::vector 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 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 Index: clang-move/tool/CMakeLists.txt =================================================================== --- clang-move/tool/CMakeLists.txt +++ clang-move/tool/CMakeLists.txt @@ -1,7 +1,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) add_clang_executable(clang-move - ClangMoveMain.cpp + ClangMove.cpp ) target_link_libraries(clang-move Index: clang-move/tool/ClangMove.cpp =================================================================== --- clang-move/tool/ClangMove.cpp +++ clang-move/tool/ClangMove.cpp @@ -0,0 +1,212 @@ +//===-- ClangMove.cpp - move defintion to new file --------------*- 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 "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/ArgumentsAdjusters.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/YAMLTraits.h" +#include +#include + +using namespace clang; +using namespace llvm; + +namespace { + +std::error_code CreateNewFile(const llvm::Twine &path) { + int fd = 0; + if (std::error_code ec = llvm::sys::fs::openFileForWrite( + path, fd, llvm::sys::fs::CD_CreateAlways, llvm::sys::fs::F_Text)) + return ec; + + return llvm::sys::Process::SafelyCloseFileDescriptor(fd); +} + +cl::OptionCategory ClangMoveCategory("clang-move options"); + +cl::list Names("names", cl::CommaSeparated, + cl::desc("The list of the names of classes being " + "moved, e.g. \"Foo,a::Foo,b::Foo\"."), + cl::cat(ClangMoveCategory)); + +cl::opt + OldHeader("old_header", + cl::desc("The relative/absolute file path of old header."), + cl::cat(ClangMoveCategory)); + +cl::opt + OldCC("old_cc", cl::desc("The relative/absolute file path of old cc."), + cl::cat(ClangMoveCategory)); + +cl::opt + NewHeader("new_header", + cl::desc("The relative/absolute file path of new header."), + cl::cat(ClangMoveCategory)); + +cl::opt + NewCC("new_cc", cl::desc("The relative/absolute file path of new cc."), + cl::cat(ClangMoveCategory)); + +cl::opt + OldDependOnNew("old_depend_on_new", + cl::desc("Whether old header will depend on new header. If " + "true, clang-move will " + "add #include of new header to old header."), + cl::init(false), cl::cat(ClangMoveCategory)); + +cl::opt + NewDependOnOld("new_depend_on_old", + cl::desc("Whether new header will depend on old header. If " + "true, clang-move will " + "add #include of old header to new header."), + cl::init(false), cl::cat(ClangMoveCategory)); + +cl::opt + Style("style", + cl::desc("The style name used for reformatting. Default is \"llvm\""), + cl::init("llvm"), cl::cat(ClangMoveCategory)); + +cl::opt Dump("dump_result", + cl::desc("Dump results in JSON format to stdout."), + cl::cat(ClangMoveCategory)); + +cl::opt DumpDecls( + "dump_decls", + cl::desc("Dump all declarations in old header (JSON format) to stdout. If " + "the option is specified, other command options will be ignored. " + "An empty JSON will be returned if old header isn't specified."), + cl::cat(ClangMoveCategory)); + +} // namespace + +int main(int argc, const char **argv) { + llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); + tooling::CommonOptionsParser OptionsParser(argc, argv, ClangMoveCategory); + + if (OldDependOnNew && NewDependOnOld) { + llvm::errs() << "Provide either --old_depend_on_new or " + "--new_depend_on_old. clang-move doesn't support these two " + "options at same time (It will introduce include cycle).\n"; + return 1; + } + + tooling::RefactoringTool Tool(OptionsParser.getCompilations(), + OptionsParser.getSourcePathList()); + // Add "-fparse-all-comments" compile option to make clang parse all comments. + Tool.appendArgumentsAdjuster(tooling::getInsertArgumentAdjuster( + "-fparse-all-comments", tooling::ArgumentInsertPosition::BEGIN)); + move::MoveDefinitionSpec Spec; + Spec.Names = {Names.begin(), Names.end()}; + Spec.OldHeader = OldHeader; + Spec.NewHeader = NewHeader; + Spec.OldCC = OldCC; + Spec.NewCC = NewCC; + Spec.OldDependOnNew = OldDependOnNew; + Spec.NewDependOnOld = NewDependOnOld; + + llvm::SmallString<128> InitialDirectory; + if (std::error_code EC = llvm::sys::fs::current_path(InitialDirectory)) + llvm::report_fatal_error("Cannot detect current path: " + + Twine(EC.message())); + + move::ClangMoveContext Context{Spec, Tool.getReplacements(), + InitialDirectory.str(), Style, DumpDecls}; + move::DeclarationReporter Reporter; + move::ClangMoveActionFactory Factory(&Context, &Reporter); + + int CodeStatus = Tool.run(&Factory); + if (CodeStatus) + return CodeStatus; + + if (DumpDecls) { + llvm::outs() << "[\n"; + const auto &Declarations = Reporter.getDeclarationList(); + for (auto I = Declarations.begin(), E = Declarations.end(); I != E; ++I) { + llvm::outs() << " {\n"; + llvm::outs() << " \"DeclarationName\": \"" << I->QualifiedName + << "\",\n"; + llvm::outs() << " \"DeclarationType\": \"" << I->Kind << "\",\n"; + llvm::outs() << " \"Templated\": " << (I->Templated ? "true" : "false") + << "\n"; + llvm::outs() << " }"; + // Don't print trailing "," at the end of last element. + if (I != std::prev(E)) + llvm::outs() << ",\n"; + } + llvm::outs() << "\n]\n"; + return 0; + } + + if (!NewCC.empty()) { + std::error_code EC = CreateNewFile(NewCC); + if (EC) { + llvm::errs() << "Failed to create " << NewCC << ": " << EC.message() + << "\n"; + return EC.value(); + } + } + if (!NewHeader.empty()) { + std::error_code EC = CreateNewFile(NewHeader); + if (EC) { + llvm::errs() << "Failed to create " << NewHeader << ": " << EC.message() + << "\n"; + return EC.value(); + } + } + + IntrusiveRefCntPtr DiagOpts(new DiagnosticOptions()); + clang::TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr(new DiagnosticIDs()), &*DiagOpts, + &DiagnosticPrinter, false); + auto &FileMgr = Tool.getFiles(); + SourceManager SM(Diagnostics, FileMgr); + Rewriter Rewrite(SM, LangOptions()); + + if (!formatAndApplyAllReplacements(Tool.getReplacements(), Rewrite, Style)) { + llvm::errs() << "Failed applying all replacements.\n"; + return 1; + } + + if (Dump) { + std::set Files; + for (const auto &it : Tool.getReplacements()) + Files.insert(it.first); + auto WriteToJson = [&](llvm::raw_ostream &OS) { + OS << "[\n"; + for (auto I = Files.begin(), E = Files.end(); I != E; ++I) { + OS << " {\n"; + OS << " \"FilePath\": \"" << *I << "\",\n"; + const auto *Entry = FileMgr.getFile(*I); + auto ID = SM.translateFile(Entry); + std::string Content; + llvm::raw_string_ostream ContentStream(Content); + Rewrite.getEditBuffer(ID).write(ContentStream); + OS << " \"SourceText\": \"" + << llvm::yaml::escape(ContentStream.str()) << "\"\n"; + OS << " }"; + if (I != std::prev(E)) + OS << ",\n"; + } + OS << "\n]\n"; + }; + WriteToJson(llvm::outs()); + return 0; + } + + return Rewrite.overwriteChangedFiles(); +} Index: clang-move/tool/ClangMoveMain.cpp =================================================================== --- clang-move/tool/ClangMoveMain.cpp +++ clang-move/tool/ClangMoveMain.cpp @@ -1,212 +0,0 @@ -//===-- ClangMoveMain.cpp - move defintion to new file ----------*- 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 "ClangMove.h" -#include "clang/Frontend/TextDiagnosticPrinter.h" -#include "clang/Rewrite/Core/Rewriter.h" -#include "clang/Tooling/ArgumentsAdjusters.h" -#include "clang/Tooling/CommonOptionsParser.h" -#include "clang/Tooling/Refactoring.h" -#include "clang/Tooling/Tooling.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/Process.h" -#include "llvm/Support/Signals.h" -#include "llvm/Support/YAMLTraits.h" -#include -#include - -using namespace clang; -using namespace llvm; - -namespace { - -std::error_code CreateNewFile(const llvm::Twine &path) { - int fd = 0; - if (std::error_code ec = llvm::sys::fs::openFileForWrite( - path, fd, llvm::sys::fs::CD_CreateAlways, llvm::sys::fs::F_Text)) - return ec; - - return llvm::sys::Process::SafelyCloseFileDescriptor(fd); -} - -cl::OptionCategory ClangMoveCategory("clang-move options"); - -cl::list Names("names", cl::CommaSeparated, - cl::desc("The list of the names of classes being " - "moved, e.g. \"Foo,a::Foo,b::Foo\"."), - cl::cat(ClangMoveCategory)); - -cl::opt - OldHeader("old_header", - cl::desc("The relative/absolute file path of old header."), - cl::cat(ClangMoveCategory)); - -cl::opt - OldCC("old_cc", cl::desc("The relative/absolute file path of old cc."), - cl::cat(ClangMoveCategory)); - -cl::opt - NewHeader("new_header", - cl::desc("The relative/absolute file path of new header."), - cl::cat(ClangMoveCategory)); - -cl::opt - NewCC("new_cc", cl::desc("The relative/absolute file path of new cc."), - cl::cat(ClangMoveCategory)); - -cl::opt - OldDependOnNew("old_depend_on_new", - cl::desc("Whether old header will depend on new header. If " - "true, clang-move will " - "add #include of new header to old header."), - cl::init(false), cl::cat(ClangMoveCategory)); - -cl::opt - NewDependOnOld("new_depend_on_old", - cl::desc("Whether new header will depend on old header. If " - "true, clang-move will " - "add #include of old header to new header."), - cl::init(false), cl::cat(ClangMoveCategory)); - -cl::opt - Style("style", - cl::desc("The style name used for reformatting. Default is \"llvm\""), - cl::init("llvm"), cl::cat(ClangMoveCategory)); - -cl::opt Dump("dump_result", - cl::desc("Dump results in JSON format to stdout."), - cl::cat(ClangMoveCategory)); - -cl::opt DumpDecls( - "dump_decls", - cl::desc("Dump all declarations in old header (JSON format) to stdout. If " - "the option is specified, other command options will be ignored. " - "An empty JSON will be returned if old header isn't specified."), - cl::cat(ClangMoveCategory)); - -} // namespace - -int main(int argc, const char **argv) { - llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); - tooling::CommonOptionsParser OptionsParser(argc, argv, ClangMoveCategory); - - if (OldDependOnNew && NewDependOnOld) { - llvm::errs() << "Provide either --old_depend_on_new or " - "--new_depend_on_old. clang-move doesn't support these two " - "options at same time (It will introduce include cycle).\n"; - return 1; - } - - tooling::RefactoringTool Tool(OptionsParser.getCompilations(), - OptionsParser.getSourcePathList()); - // Add "-fparse-all-comments" compile option to make clang parse all comments. - Tool.appendArgumentsAdjuster(tooling::getInsertArgumentAdjuster( - "-fparse-all-comments", tooling::ArgumentInsertPosition::BEGIN)); - move::MoveDefinitionSpec Spec; - Spec.Names = {Names.begin(), Names.end()}; - Spec.OldHeader = OldHeader; - Spec.NewHeader = NewHeader; - Spec.OldCC = OldCC; - Spec.NewCC = NewCC; - Spec.OldDependOnNew = OldDependOnNew; - Spec.NewDependOnOld = NewDependOnOld; - - llvm::SmallString<128> InitialDirectory; - if (std::error_code EC = llvm::sys::fs::current_path(InitialDirectory)) - llvm::report_fatal_error("Cannot detect current path: " + - Twine(EC.message())); - - move::ClangMoveContext Context{Spec, Tool.getReplacements(), - InitialDirectory.str(), Style, DumpDecls}; - move::DeclarationReporter Reporter; - move::ClangMoveActionFactory Factory(&Context, &Reporter); - - int CodeStatus = Tool.run(&Factory); - if (CodeStatus) - return CodeStatus; - - if (DumpDecls) { - llvm::outs() << "[\n"; - const auto &Declarations = Reporter.getDeclarationList(); - for (auto I = Declarations.begin(), E = Declarations.end(); I != E; ++I) { - llvm::outs() << " {\n"; - llvm::outs() << " \"DeclarationName\": \"" << I->QualifiedName - << "\",\n"; - llvm::outs() << " \"DeclarationType\": \"" << I->Kind << "\",\n"; - llvm::outs() << " \"Templated\": " << (I->Templated ? "true" : "false") - << "\n"; - llvm::outs() << " }"; - // Don't print trailing "," at the end of last element. - if (I != std::prev(E)) - llvm::outs() << ",\n"; - } - llvm::outs() << "\n]\n"; - return 0; - } - - if (!NewCC.empty()) { - std::error_code EC = CreateNewFile(NewCC); - if (EC) { - llvm::errs() << "Failed to create " << NewCC << ": " << EC.message() - << "\n"; - return EC.value(); - } - } - if (!NewHeader.empty()) { - std::error_code EC = CreateNewFile(NewHeader); - if (EC) { - llvm::errs() << "Failed to create " << NewHeader << ": " << EC.message() - << "\n"; - return EC.value(); - } - } - - IntrusiveRefCntPtr DiagOpts(new DiagnosticOptions()); - clang::TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts); - DiagnosticsEngine Diagnostics( - IntrusiveRefCntPtr(new DiagnosticIDs()), &*DiagOpts, - &DiagnosticPrinter, false); - auto &FileMgr = Tool.getFiles(); - SourceManager SM(Diagnostics, FileMgr); - Rewriter Rewrite(SM, LangOptions()); - - if (!formatAndApplyAllReplacements(Tool.getReplacements(), Rewrite, Style)) { - llvm::errs() << "Failed applying all replacements.\n"; - return 1; - } - - if (Dump) { - std::set Files; - for (const auto &it : Tool.getReplacements()) - Files.insert(it.first); - auto WriteToJson = [&](llvm::raw_ostream &OS) { - OS << "[\n"; - for (auto I = Files.begin(), E = Files.end(); I != E; ++I) { - OS << " {\n"; - OS << " \"FilePath\": \"" << *I << "\",\n"; - const auto *Entry = FileMgr.getFile(*I); - auto ID = SM.translateFile(Entry); - std::string Content; - llvm::raw_string_ostream ContentStream(Content); - Rewrite.getEditBuffer(ID).write(ContentStream); - OS << " \"SourceText\": \"" - << llvm::yaml::escape(ContentStream.str()) << "\"\n"; - OS << " }"; - if (I != std::prev(E)) - OS << ",\n"; - } - OS << "\n]\n"; - }; - WriteToJson(llvm::outs()); - return 0; - } - - return Rewrite.overwriteChangedFiles(); -}