blob: 69ec412f12d10a7c35651124a730e2e34e81c779 [file] [log] [blame]
Haojian Wu357ef992016-09-21 13:18:19 +00001//===-- ClangMove.cpp - Implement ClangMove functationalities ---*- C++ -*-===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
10#include "ClangMove.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"
Haojian Wud2a6d7b2016-10-04 09:05:31 +000019#include "llvm/Support/Path.h"
Haojian Wu357ef992016-09-21 13:18:19 +000020
21using namespace clang::ast_matchers;
22
23namespace clang {
24namespace move {
25namespace {
26
Haojian Wud2a6d7b2016-10-04 09:05:31 +000027// Make the Path absolute using the CurrentDir if the Path is not an absolute
28// path. An empty Path will result in an empty string.
29std::string MakeAbsolutePath(StringRef CurrentDir, StringRef Path) {
30 if (Path.empty())
31 return "";
32 llvm::SmallString<128> InitialDirectory(CurrentDir);
33 llvm::SmallString<128> AbsolutePath(Path);
34 if (std::error_code EC =
35 llvm::sys::fs::make_absolute(InitialDirectory, AbsolutePath))
36 llvm::errs() << "Warning: could not make absolute file: '" << EC.message()
37 << '\n';
38 llvm::sys::path::remove_dots(AbsolutePath, /*remove_dot_dot=*/true);
39 return AbsolutePath.str();
40}
41
42// Make the Path absolute using the current working directory of the given
43// SourceManager if the Path is not an absolute path.
44//
45// The Path can be a path relative to the build directory, or retrieved from
46// the SourceManager.
47std::string MakeAbsolutePath(const SourceManager& SM, StringRef Path) {
48 llvm::SmallString<128> AbsolutePath(Path);
49 if (std::error_code EC =
50 SM.getFileManager().getVirtualFileSystem()->makeAbsolute(AbsolutePath))
51 llvm::errs() << "Warning: could not make absolute file: '" << EC.message()
52 << '\n';
53 llvm::sys::path::remove_dots(AbsolutePath, /*remove_dot_dot=*/true);
54 return AbsolutePath.str();
55}
56
57// Matches AST nodes that are expanded within the given AbsoluteFilePath.
58AST_POLYMORPHIC_MATCHER_P(isExpansionInFile,
59 AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt, TypeLoc),
60 std::string, AbsoluteFilePath) {
61 auto &SourceManager = Finder->getASTContext().getSourceManager();
62 auto ExpansionLoc = SourceManager.getExpansionLoc(Node.getLocStart());
63 if (ExpansionLoc.isInvalid())
64 return false;
65 auto FileEntry =
66 SourceManager.getFileEntryForID(SourceManager.getFileID(ExpansionLoc));
67 if (!FileEntry)
68 return false;
69 return MakeAbsolutePath(SourceManager, FileEntry->getName()) ==
70 AbsoluteFilePath;
71}
72
Haojian Wu357ef992016-09-21 13:18:19 +000073class FindAllIncludes : public clang::PPCallbacks {
74public:
75 explicit FindAllIncludes(SourceManager *SM, ClangMoveTool *const MoveTool)
76 : SM(*SM), MoveTool(MoveTool) {}
77
78 void InclusionDirective(clang::SourceLocation HashLoc,
79 const clang::Token & /*IncludeTok*/,
80 StringRef FileName, bool IsAngled,
81 clang::CharSourceRange /*FilenameRange*/,
82 const clang::FileEntry * /*File*/,
Haojian Wud2a6d7b2016-10-04 09:05:31 +000083 StringRef SearchPath, StringRef /*RelativePath*/,
Haojian Wu357ef992016-09-21 13:18:19 +000084 const clang::Module * /*Imported*/) override {
Haojian Wudaf4cb82016-09-23 13:28:38 +000085 if (const auto *FileEntry = SM.getFileEntryForID(SM.getFileID(HashLoc)))
Haojian Wud2a6d7b2016-10-04 09:05:31 +000086 MoveTool->addIncludes(FileName, IsAngled, SearchPath,
87 FileEntry->getName(), SM);
Haojian Wu357ef992016-09-21 13:18:19 +000088 }
89
90private:
91 const SourceManager &SM;
92 ClangMoveTool *const MoveTool;
93};
94
95clang::tooling::Replacement
96getReplacementInChangedCode(const clang::tooling::Replacements &Replacements,
97 const clang::tooling::Replacement &Replacement) {
98 unsigned Start = Replacements.getShiftedCodePosition(Replacement.getOffset());
99 unsigned End = Replacements.getShiftedCodePosition(Replacement.getOffset() +
100 Replacement.getLength());
101 return clang::tooling::Replacement(Replacement.getFilePath(), Start,
102 End - Start,
103 Replacement.getReplacementText());
104}
105
106void addOrMergeReplacement(const clang::tooling::Replacement &Replacement,
107 clang::tooling::Replacements *Replacements) {
108 auto Err = Replacements->add(Replacement);
109 if (Err) {
110 llvm::consumeError(std::move(Err));
111 auto Replace = getReplacementInChangedCode(*Replacements, Replacement);
112 *Replacements = Replacements->merge(clang::tooling::Replacements(Replace));
113 }
114}
115
Haojian Wud2a6d7b2016-10-04 09:05:31 +0000116bool isInHeaderFile(const clang::SourceManager &SM, const clang::Decl *D,
117 llvm::StringRef OriginalRunningDirectory,
118 llvm::StringRef OldHeader) {
119 if (OldHeader.empty())
Haojian Wu357ef992016-09-21 13:18:19 +0000120 return false;
121 auto ExpansionLoc = SM.getExpansionLoc(D->getLocStart());
122 if (ExpansionLoc.isInvalid())
123 return false;
124
Haojian Wud2a6d7b2016-10-04 09:05:31 +0000125 if (const auto *FE = SM.getFileEntryForID(SM.getFileID(ExpansionLoc))) {
126 return MakeAbsolutePath(SM, FE->getName()) ==
127 MakeAbsolutePath(OriginalRunningDirectory, OldHeader);
128 }
Haojian Wu357ef992016-09-21 13:18:19 +0000129
130 return false;
131}
132
133std::vector<std::string> GetNamespaces(const clang::Decl *D) {
134 std::vector<std::string> Namespaces;
135 for (const auto *Context = D->getDeclContext(); Context;
136 Context = Context->getParent()) {
137 if (llvm::isa<clang::TranslationUnitDecl>(Context) ||
138 llvm::isa<clang::LinkageSpecDecl>(Context))
139 break;
140
141 if (const auto *ND = llvm::dyn_cast<clang::NamespaceDecl>(Context))
142 Namespaces.push_back(ND->getName().str());
143 }
144 std::reverse(Namespaces.begin(), Namespaces.end());
145 return Namespaces;
146}
147
148SourceLocation getLocForEndOfDecl(const clang::Decl *D,
149 const clang::SourceManager *SM) {
150 auto End = D->getLocEnd();
151 clang::SourceLocation AfterSemi = clang::Lexer::findLocationAfterToken(
152 End, clang::tok::semi, *SM, clang::LangOptions(),
153 /*SkipTrailingWhitespaceAndNewLine=*/true);
154 if (AfterSemi.isValid())
155 End = AfterSemi.getLocWithOffset(-1);
156 return End;
157}
158
159std::string getDeclarationSourceText(const clang::Decl *D,
160 const clang::SourceManager *SM) {
161 auto EndLoc = getLocForEndOfDecl(D, SM);
162 llvm::StringRef SourceText = clang::Lexer::getSourceText(
163 clang::CharSourceRange::getTokenRange(D->getLocStart(), EndLoc), *SM,
164 clang::LangOptions());
165 return SourceText.str() + "\n";
166}
167
168clang::tooling::Replacements
169createInsertedReplacements(const std::vector<std::string> &Includes,
170 const std::vector<ClangMoveTool::MovedDecl> &Decls,
171 llvm::StringRef FileName) {
172 clang::tooling::Replacements InsertedReplacements;
173
174 // Add #Includes.
175 std::string AllIncludesString;
Haojian Wudaf4cb82016-09-23 13:28:38 +0000176 // FIXME: Add header guard.
Haojian Wu357ef992016-09-21 13:18:19 +0000177 for (const auto &Include : Includes)
178 AllIncludesString += Include;
179 clang::tooling::Replacement InsertInclude(FileName, 0, 0, AllIncludesString);
180 addOrMergeReplacement(InsertInclude, &InsertedReplacements);
181
182 // Add moved class definition and its related declarations. All declarations
183 // in same namespace are grouped together.
184 std::vector<std::string> CurrentNamespaces;
185 for (const auto &MovedDecl : Decls) {
186 std::vector<std::string> DeclNamespaces = GetNamespaces(MovedDecl.Decl);
187 auto CurrentIt = CurrentNamespaces.begin();
188 auto DeclIt = DeclNamespaces.begin();
189 while (CurrentIt != CurrentNamespaces.end() &&
190 DeclIt != DeclNamespaces.end()) {
191 if (*CurrentIt != *DeclIt)
192 break;
193 ++CurrentIt;
194 ++DeclIt;
195 }
196 std::vector<std::string> NextNamespaces(CurrentNamespaces.begin(),
197 CurrentIt);
198 NextNamespaces.insert(NextNamespaces.end(), DeclIt, DeclNamespaces.end());
199 auto RemainingSize = CurrentNamespaces.end() - CurrentIt;
200 for (auto It = CurrentNamespaces.rbegin(); RemainingSize > 0;
201 --RemainingSize, ++It) {
202 assert(It < CurrentNamespaces.rend());
203 auto code = "} // namespace " + *It + "\n";
204 clang::tooling::Replacement InsertedReplacement(FileName, 0, 0, code);
205 addOrMergeReplacement(InsertedReplacement, &InsertedReplacements);
206 }
207 while (DeclIt != DeclNamespaces.end()) {
208 clang::tooling::Replacement InsertedReplacement(
209 FileName, 0, 0, "namespace " + *DeclIt + " {\n");
210 addOrMergeReplacement(InsertedReplacement, &InsertedReplacements);
211 ++DeclIt;
212 }
213
214 // FIXME: consider moving comments of the moved declaration.
215 clang::tooling::Replacement InsertedReplacement(
216 FileName, 0, 0, getDeclarationSourceText(MovedDecl.Decl, MovedDecl.SM));
217 addOrMergeReplacement(InsertedReplacement, &InsertedReplacements);
218
219 CurrentNamespaces = std::move(NextNamespaces);
220 }
221 std::reverse(CurrentNamespaces.begin(), CurrentNamespaces.end());
222 for (const auto &NS : CurrentNamespaces) {
223 clang::tooling::Replacement InsertedReplacement(
224 FileName, 0, 0, "} // namespace " + NS + "\n");
225 addOrMergeReplacement(InsertedReplacement, &InsertedReplacements);
226 }
227 return InsertedReplacements;
228}
229
230} // namespace
231
232std::unique_ptr<clang::ASTConsumer>
233ClangMoveAction::CreateASTConsumer(clang::CompilerInstance &Compiler,
234 StringRef /*InFile*/) {
235 Compiler.getPreprocessor().addPPCallbacks(llvm::make_unique<FindAllIncludes>(
236 &Compiler.getSourceManager(), &MoveTool));
237 return MatchFinder.newASTConsumer();
238}
239
Haojian Wu357ef992016-09-21 13:18:19 +0000240ClangMoveTool::ClangMoveTool(
241 const MoveDefinitionSpec &MoveSpec,
Haojian Wud2a6d7b2016-10-04 09:05:31 +0000242 std::map<std::string, tooling::Replacements> &FileToReplacements,
243 llvm::StringRef OriginalRunningDirectory)
244 : Spec(MoveSpec), FileToReplacements(FileToReplacements),
245 OriginalRunningDirectory(OriginalRunningDirectory) {
Haojian Wu357ef992016-09-21 13:18:19 +0000246 Spec.Name = llvm::StringRef(Spec.Name).ltrim(':');
Haojian Wudaf4cb82016-09-23 13:28:38 +0000247 if (!Spec.NewHeader.empty())
248 CCIncludes.push_back("#include \"" + Spec.NewHeader + "\"\n");
Haojian Wu357ef992016-09-21 13:18:19 +0000249}
250
251void ClangMoveTool::registerMatchers(ast_matchers::MatchFinder *Finder) {
252 std::string FullyQualifiedName = "::" + Spec.Name;
Haojian Wud2a6d7b2016-10-04 09:05:31 +0000253 auto InOldHeader = isExpansionInFile(
254 MakeAbsolutePath(OriginalRunningDirectory, Spec.OldHeader));
255 auto InOldCC = isExpansionInFile(
256 MakeAbsolutePath(OriginalRunningDirectory, Spec.OldCC));
Haojian Wu357ef992016-09-21 13:18:19 +0000257 auto InOldFiles = anyOf(InOldHeader, InOldCC);
258 auto InMovedClass =
259 hasDeclContext(cxxRecordDecl(hasName(FullyQualifiedName)));
260
261 // Match moved class declarations.
262 auto MovedClass = cxxRecordDecl(
263 InOldFiles, hasName(FullyQualifiedName), isDefinition(),
264 hasDeclContext(anyOf(namespaceDecl(), translationUnitDecl())));
265 Finder->addMatcher(MovedClass.bind("moved_class"), this);
266
267 // Match moved class methods (static methods included) which are defined
268 // outside moved class declaration.
269 Finder->addMatcher(cxxMethodDecl(InOldFiles,
270 ofClass(hasName(FullyQualifiedName)),
271 isDefinition())
272 .bind("class_method"),
273 this);
274
275 // Match static member variable definition of the moved class.
276 Finder->addMatcher(varDecl(InMovedClass, InOldCC, isDefinition())
277 .bind("class_static_var_decl"),
278 this);
279
280 auto inAnonymousNamespace = hasParent(namespaceDecl(isAnonymous()));
281 // Match functions/variables definitions which are defined in anonymous
282 // namespace in old cc.
283 Finder->addMatcher(
284 namedDecl(anyOf(functionDecl(isDefinition()), varDecl(isDefinition())),
285 inAnonymousNamespace)
286 .bind("decls_in_anonymous_ns"),
287 this);
288
289 // Match static functions/variabale definitions in old cc.
290 Finder->addMatcher(
291 namedDecl(anyOf(functionDecl(isDefinition(), unless(InMovedClass),
Haojian Wuef247cb2016-09-27 08:01:04 +0000292 isStaticStorageClass(), InOldCC),
293 varDecl(isDefinition(), unless(InMovedClass),
294 isStaticStorageClass(), InOldCC)))
Haojian Wu357ef992016-09-21 13:18:19 +0000295 .bind("static_decls"),
296 this);
297
298 // Match forward declarations in old header.
299 Finder->addMatcher(
300 cxxRecordDecl(unless(anyOf(isImplicit(), isDefinition())), InOldHeader)
301 .bind("fwd_decl"),
302 this);
303}
304
305void ClangMoveTool::run(const ast_matchers::MatchFinder::MatchResult &Result) {
306 if (const auto *CMD =
307 Result.Nodes.getNodeAs<clang::CXXMethodDecl>("class_method")) {
308 // Skip inline class methods. isInline() ast matcher doesn't ignore this
309 // case.
310 if (!CMD->isInlined()) {
311 MovedDecls.emplace_back(CMD, &Result.Context->getSourceManager());
312 RemovedDecls.push_back(MovedDecls.back());
313 }
314 } else if (const auto *VD = Result.Nodes.getNodeAs<clang::VarDecl>(
315 "class_static_var_decl")) {
316 MovedDecls.emplace_back(VD, &Result.Context->getSourceManager());
317 RemovedDecls.push_back(MovedDecls.back());
318 } else if (const auto *class_decl =
319 Result.Nodes.getNodeAs<clang::CXXRecordDecl>("moved_class")) {
320 MovedDecls.emplace_back(class_decl, &Result.Context->getSourceManager());
321 RemovedDecls.push_back(MovedDecls.back());
322 } else if (const auto *FWD =
323 Result.Nodes.getNodeAs<clang::CXXRecordDecl>("fwd_decl")) {
324 // Skip all forwad declarations which appear after moved class declaration.
325 if (RemovedDecls.empty())
326 MovedDecls.emplace_back(FWD, &Result.Context->getSourceManager());
327 } else if (const auto *FD = Result.Nodes.getNodeAs<clang::NamedDecl>(
328 "decls_in_anonymous_ns")) {
329 MovedDecls.emplace_back(FD, &Result.Context->getSourceManager());
330 } else if (const auto *ND =
331 Result.Nodes.getNodeAs<clang::NamedDecl>("static_decls")) {
332 MovedDecls.emplace_back(ND, &Result.Context->getSourceManager());
333 }
334}
335
Haojian Wud2a6d7b2016-10-04 09:05:31 +0000336void ClangMoveTool::addIncludes(llvm::StringRef IncludeHeader,
337 bool IsAngled,
338 llvm::StringRef SearchPath,
339 llvm::StringRef FileName,
340 const SourceManager& SM) {
341 auto AbsoluteSearchPath = MakeAbsolutePath(SM, SearchPath);
Haojian Wudaf4cb82016-09-23 13:28:38 +0000342 // FIXME: Add old.h to the new.cc/h when the new target has dependencies on
343 // old.h/c. For instance, when moved class uses another class defined in
344 // old.h, the old.h should be added in new.h.
Haojian Wud2a6d7b2016-10-04 09:05:31 +0000345 if (MakeAbsolutePath(OriginalRunningDirectory, Spec.OldHeader) ==
346 MakeAbsolutePath(AbsoluteSearchPath, IncludeHeader))
Haojian Wudaf4cb82016-09-23 13:28:38 +0000347 return;
348
349 std::string IncludeLine =
350 IsAngled ? ("#include <" + IncludeHeader + ">\n").str()
351 : ("#include \"" + IncludeHeader + "\"\n").str();
Haojian Wud2a6d7b2016-10-04 09:05:31 +0000352
353 std::string AbsolutePath = MakeAbsolutePath(SM, FileName);
354 if (MakeAbsolutePath(OriginalRunningDirectory, Spec.OldHeader) ==
355 AbsolutePath) {
Haojian Wudaf4cb82016-09-23 13:28:38 +0000356 HeaderIncludes.push_back(IncludeLine);
Haojian Wud2a6d7b2016-10-04 09:05:31 +0000357 } else if (MakeAbsolutePath(OriginalRunningDirectory, Spec.OldCC) ==
358 AbsolutePath) {
Haojian Wudaf4cb82016-09-23 13:28:38 +0000359 CCIncludes.push_back(IncludeLine);
Haojian Wud2a6d7b2016-10-04 09:05:31 +0000360 }
Haojian Wu357ef992016-09-21 13:18:19 +0000361}
362
363void ClangMoveTool::removeClassDefinitionInOldFiles() {
364 for (const auto &MovedDecl : RemovedDecls) {
365 auto EndLoc = getLocForEndOfDecl(MovedDecl.Decl, MovedDecl.SM);
366 clang::tooling::Replacement RemoveReplacement(
367 *MovedDecl.SM, clang::CharSourceRange::getTokenRange(
368 MovedDecl.Decl->getLocStart(), EndLoc),
369 "");
370 std::string FilePath = RemoveReplacement.getFilePath().str();
371 addOrMergeReplacement(RemoveReplacement, &FileToReplacements[FilePath]);
372 }
373}
374
375void ClangMoveTool::moveClassDefinitionToNewFiles() {
376 std::vector<MovedDecl> NewHeaderDecls;
377 std::vector<MovedDecl> NewCCDecls;
378 for (const auto &MovedDecl : MovedDecls) {
Haojian Wud2a6d7b2016-10-04 09:05:31 +0000379 if (isInHeaderFile(*MovedDecl.SM, MovedDecl.Decl, OriginalRunningDirectory,
380 Spec.OldHeader))
Haojian Wu357ef992016-09-21 13:18:19 +0000381 NewHeaderDecls.push_back(MovedDecl);
382 else
383 NewCCDecls.push_back(MovedDecl);
384 }
385
386 if (!Spec.NewHeader.empty())
387 FileToReplacements[Spec.NewHeader] = createInsertedReplacements(
388 HeaderIncludes, NewHeaderDecls, Spec.NewHeader);
389 if (!Spec.NewCC.empty())
390 FileToReplacements[Spec.NewCC] =
391 createInsertedReplacements(CCIncludes, NewCCDecls, Spec.NewCC);
392}
393
394void ClangMoveTool::onEndOfTranslationUnit() {
395 if (RemovedDecls.empty())
396 return;
397 removeClassDefinitionInOldFiles();
398 moveClassDefinitionToNewFiles();
399}
400
401} // namespace move
402} // namespace clang