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