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