blob: 8782fd94764cb26aee831f4b70936ce951a9d184 [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);
Haojian Wuc6f125e2016-10-04 09:49:20 +000039 llvm::sys::path::native(AbsolutePath);
Haojian Wud2a6d7b2016-10-04 09:05:31 +000040 return AbsolutePath.str();
41}
42
43// Make the Path absolute using the current working directory of the given
44// SourceManager if the Path is not an absolute path.
45//
46// The Path can be a path relative to the build directory, or retrieved from
47// the SourceManager.
48std::string MakeAbsolutePath(const SourceManager& SM, StringRef Path) {
49 llvm::SmallString<128> AbsolutePath(Path);
50 if (std::error_code EC =
51 SM.getFileManager().getVirtualFileSystem()->makeAbsolute(AbsolutePath))
52 llvm::errs() << "Warning: could not make absolute file: '" << EC.message()
53 << '\n';
54 llvm::sys::path::remove_dots(AbsolutePath, /*remove_dot_dot=*/true);
Haojian Wuc6f125e2016-10-04 09:49:20 +000055 llvm::sys::path::native(AbsolutePath);
Haojian Wud2a6d7b2016-10-04 09:05:31 +000056 return AbsolutePath.str();
57}
58
59// Matches AST nodes that are expanded within the given AbsoluteFilePath.
60AST_POLYMORPHIC_MATCHER_P(isExpansionInFile,
61 AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt, TypeLoc),
62 std::string, AbsoluteFilePath) {
63 auto &SourceManager = Finder->getASTContext().getSourceManager();
64 auto ExpansionLoc = SourceManager.getExpansionLoc(Node.getLocStart());
65 if (ExpansionLoc.isInvalid())
66 return false;
67 auto FileEntry =
68 SourceManager.getFileEntryForID(SourceManager.getFileID(ExpansionLoc));
69 if (!FileEntry)
70 return false;
71 return MakeAbsolutePath(SourceManager, FileEntry->getName()) ==
72 AbsoluteFilePath;
73}
74
Haojian Wu357ef992016-09-21 13:18:19 +000075class FindAllIncludes : public clang::PPCallbacks {
76public:
77 explicit FindAllIncludes(SourceManager *SM, ClangMoveTool *const MoveTool)
78 : SM(*SM), MoveTool(MoveTool) {}
79
80 void InclusionDirective(clang::SourceLocation HashLoc,
81 const clang::Token & /*IncludeTok*/,
82 StringRef FileName, bool IsAngled,
83 clang::CharSourceRange /*FilenameRange*/,
84 const clang::FileEntry * /*File*/,
Haojian Wud2a6d7b2016-10-04 09:05:31 +000085 StringRef SearchPath, StringRef /*RelativePath*/,
Haojian Wu357ef992016-09-21 13:18:19 +000086 const clang::Module * /*Imported*/) override {
Haojian Wudaf4cb82016-09-23 13:28:38 +000087 if (const auto *FileEntry = SM.getFileEntryForID(SM.getFileID(HashLoc)))
Haojian Wud2a6d7b2016-10-04 09:05:31 +000088 MoveTool->addIncludes(FileName, IsAngled, SearchPath,
89 FileEntry->getName(), SM);
Haojian Wu357ef992016-09-21 13:18:19 +000090 }
91
92private:
93 const SourceManager &SM;
94 ClangMoveTool *const MoveTool;
95};
96
97clang::tooling::Replacement
98getReplacementInChangedCode(const clang::tooling::Replacements &Replacements,
99 const clang::tooling::Replacement &Replacement) {
100 unsigned Start = Replacements.getShiftedCodePosition(Replacement.getOffset());
101 unsigned End = Replacements.getShiftedCodePosition(Replacement.getOffset() +
102 Replacement.getLength());
103 return clang::tooling::Replacement(Replacement.getFilePath(), Start,
104 End - Start,
105 Replacement.getReplacementText());
106}
107
108void addOrMergeReplacement(const clang::tooling::Replacement &Replacement,
109 clang::tooling::Replacements *Replacements) {
110 auto Err = Replacements->add(Replacement);
111 if (Err) {
112 llvm::consumeError(std::move(Err));
113 auto Replace = getReplacementInChangedCode(*Replacements, Replacement);
114 *Replacements = Replacements->merge(clang::tooling::Replacements(Replace));
115 }
116}
117
Haojian Wud2a6d7b2016-10-04 09:05:31 +0000118bool isInHeaderFile(const clang::SourceManager &SM, const clang::Decl *D,
119 llvm::StringRef OriginalRunningDirectory,
120 llvm::StringRef OldHeader) {
121 if (OldHeader.empty())
Haojian Wu357ef992016-09-21 13:18:19 +0000122 return false;
123 auto ExpansionLoc = SM.getExpansionLoc(D->getLocStart());
124 if (ExpansionLoc.isInvalid())
125 return false;
126
Haojian Wud2a6d7b2016-10-04 09:05:31 +0000127 if (const auto *FE = SM.getFileEntryForID(SM.getFileID(ExpansionLoc))) {
128 return MakeAbsolutePath(SM, FE->getName()) ==
129 MakeAbsolutePath(OriginalRunningDirectory, OldHeader);
130 }
Haojian Wu357ef992016-09-21 13:18:19 +0000131
132 return false;
133}
134
135std::vector<std::string> GetNamespaces(const clang::Decl *D) {
136 std::vector<std::string> Namespaces;
137 for (const auto *Context = D->getDeclContext(); Context;
138 Context = Context->getParent()) {
139 if (llvm::isa<clang::TranslationUnitDecl>(Context) ||
140 llvm::isa<clang::LinkageSpecDecl>(Context))
141 break;
142
143 if (const auto *ND = llvm::dyn_cast<clang::NamespaceDecl>(Context))
144 Namespaces.push_back(ND->getName().str());
145 }
146 std::reverse(Namespaces.begin(), Namespaces.end());
147 return Namespaces;
148}
149
150SourceLocation getLocForEndOfDecl(const clang::Decl *D,
151 const clang::SourceManager *SM) {
152 auto End = D->getLocEnd();
153 clang::SourceLocation AfterSemi = clang::Lexer::findLocationAfterToken(
154 End, clang::tok::semi, *SM, clang::LangOptions(),
155 /*SkipTrailingWhitespaceAndNewLine=*/true);
156 if (AfterSemi.isValid())
157 End = AfterSemi.getLocWithOffset(-1);
158 return End;
159}
160
161std::string getDeclarationSourceText(const clang::Decl *D,
162 const clang::SourceManager *SM) {
163 auto EndLoc = getLocForEndOfDecl(D, SM);
164 llvm::StringRef SourceText = clang::Lexer::getSourceText(
165 clang::CharSourceRange::getTokenRange(D->getLocStart(), EndLoc), *SM,
166 clang::LangOptions());
167 return SourceText.str() + "\n";
168}
169
170clang::tooling::Replacements
171createInsertedReplacements(const std::vector<std::string> &Includes,
172 const std::vector<ClangMoveTool::MovedDecl> &Decls,
173 llvm::StringRef FileName) {
174 clang::tooling::Replacements InsertedReplacements;
175
176 // Add #Includes.
177 std::string AllIncludesString;
Haojian Wudaf4cb82016-09-23 13:28:38 +0000178 // FIXME: Add header guard.
Haojian Wu357ef992016-09-21 13:18:19 +0000179 for (const auto &Include : Includes)
180 AllIncludesString += Include;
181 clang::tooling::Replacement InsertInclude(FileName, 0, 0, AllIncludesString);
182 addOrMergeReplacement(InsertInclude, &InsertedReplacements);
183
184 // Add moved class definition and its related declarations. All declarations
185 // in same namespace are grouped together.
186 std::vector<std::string> CurrentNamespaces;
187 for (const auto &MovedDecl : Decls) {
188 std::vector<std::string> DeclNamespaces = GetNamespaces(MovedDecl.Decl);
189 auto CurrentIt = CurrentNamespaces.begin();
190 auto DeclIt = DeclNamespaces.begin();
191 while (CurrentIt != CurrentNamespaces.end() &&
192 DeclIt != DeclNamespaces.end()) {
193 if (*CurrentIt != *DeclIt)
194 break;
195 ++CurrentIt;
196 ++DeclIt;
197 }
198 std::vector<std::string> NextNamespaces(CurrentNamespaces.begin(),
199 CurrentIt);
200 NextNamespaces.insert(NextNamespaces.end(), DeclIt, DeclNamespaces.end());
201 auto RemainingSize = CurrentNamespaces.end() - CurrentIt;
202 for (auto It = CurrentNamespaces.rbegin(); RemainingSize > 0;
203 --RemainingSize, ++It) {
204 assert(It < CurrentNamespaces.rend());
205 auto code = "} // namespace " + *It + "\n";
206 clang::tooling::Replacement InsertedReplacement(FileName, 0, 0, code);
207 addOrMergeReplacement(InsertedReplacement, &InsertedReplacements);
208 }
209 while (DeclIt != DeclNamespaces.end()) {
210 clang::tooling::Replacement InsertedReplacement(
211 FileName, 0, 0, "namespace " + *DeclIt + " {\n");
212 addOrMergeReplacement(InsertedReplacement, &InsertedReplacements);
213 ++DeclIt;
214 }
215
216 // FIXME: consider moving comments of the moved declaration.
217 clang::tooling::Replacement InsertedReplacement(
218 FileName, 0, 0, getDeclarationSourceText(MovedDecl.Decl, MovedDecl.SM));
219 addOrMergeReplacement(InsertedReplacement, &InsertedReplacements);
220
221 CurrentNamespaces = std::move(NextNamespaces);
222 }
223 std::reverse(CurrentNamespaces.begin(), CurrentNamespaces.end());
224 for (const auto &NS : CurrentNamespaces) {
225 clang::tooling::Replacement InsertedReplacement(
226 FileName, 0, 0, "} // namespace " + NS + "\n");
227 addOrMergeReplacement(InsertedReplacement, &InsertedReplacements);
228 }
229 return InsertedReplacements;
230}
231
232} // namespace
233
234std::unique_ptr<clang::ASTConsumer>
235ClangMoveAction::CreateASTConsumer(clang::CompilerInstance &Compiler,
236 StringRef /*InFile*/) {
237 Compiler.getPreprocessor().addPPCallbacks(llvm::make_unique<FindAllIncludes>(
238 &Compiler.getSourceManager(), &MoveTool));
239 return MatchFinder.newASTConsumer();
240}
241
Haojian Wu357ef992016-09-21 13:18:19 +0000242ClangMoveTool::ClangMoveTool(
Haojian Wu253d5962016-10-06 08:29:32 +0000243 const MoveDefinitionSpec &MoveSpec,
244 std::map<std::string, tooling::Replacements> &FileToReplacements,
245 llvm::StringRef OriginalRunningDirectory, llvm::StringRef FallbackStyle)
246 : Spec(MoveSpec), FileToReplacements(FileToReplacements),
247 OriginalRunningDirectory(OriginalRunningDirectory),
248 FallbackStyle(FallbackStyle) {
Haojian Wu357ef992016-09-21 13:18:19 +0000249 Spec.Name = llvm::StringRef(Spec.Name).ltrim(':');
Haojian Wudaf4cb82016-09-23 13:28:38 +0000250 if (!Spec.NewHeader.empty())
251 CCIncludes.push_back("#include \"" + Spec.NewHeader + "\"\n");
Haojian Wu357ef992016-09-21 13:18:19 +0000252}
253
254void ClangMoveTool::registerMatchers(ast_matchers::MatchFinder *Finder) {
255 std::string FullyQualifiedName = "::" + Spec.Name;
Haojian Wud2a6d7b2016-10-04 09:05:31 +0000256 auto InOldHeader = isExpansionInFile(
257 MakeAbsolutePath(OriginalRunningDirectory, Spec.OldHeader));
258 auto InOldCC = isExpansionInFile(
259 MakeAbsolutePath(OriginalRunningDirectory, Spec.OldCC));
Haojian Wu357ef992016-09-21 13:18:19 +0000260 auto InOldFiles = anyOf(InOldHeader, InOldCC);
261 auto InMovedClass =
262 hasDeclContext(cxxRecordDecl(hasName(FullyQualifiedName)));
263
264 // Match moved class declarations.
265 auto MovedClass = cxxRecordDecl(
266 InOldFiles, hasName(FullyQualifiedName), isDefinition(),
267 hasDeclContext(anyOf(namespaceDecl(), translationUnitDecl())));
268 Finder->addMatcher(MovedClass.bind("moved_class"), this);
269
270 // Match moved class methods (static methods included) which are defined
271 // outside moved class declaration.
272 Finder->addMatcher(cxxMethodDecl(InOldFiles,
273 ofClass(hasName(FullyQualifiedName)),
274 isDefinition())
275 .bind("class_method"),
276 this);
277
278 // Match static member variable definition of the moved class.
279 Finder->addMatcher(varDecl(InMovedClass, InOldCC, isDefinition())
280 .bind("class_static_var_decl"),
281 this);
282
283 auto inAnonymousNamespace = hasParent(namespaceDecl(isAnonymous()));
284 // Match functions/variables definitions which are defined in anonymous
285 // namespace in old cc.
286 Finder->addMatcher(
287 namedDecl(anyOf(functionDecl(isDefinition()), varDecl(isDefinition())),
288 inAnonymousNamespace)
289 .bind("decls_in_anonymous_ns"),
290 this);
291
292 // Match static functions/variabale definitions in old cc.
293 Finder->addMatcher(
294 namedDecl(anyOf(functionDecl(isDefinition(), unless(InMovedClass),
Haojian Wuef247cb2016-09-27 08:01:04 +0000295 isStaticStorageClass(), InOldCC),
296 varDecl(isDefinition(), unless(InMovedClass),
297 isStaticStorageClass(), InOldCC)))
Haojian Wu357ef992016-09-21 13:18:19 +0000298 .bind("static_decls"),
299 this);
300
301 // Match forward declarations in old header.
302 Finder->addMatcher(
303 cxxRecordDecl(unless(anyOf(isImplicit(), isDefinition())), InOldHeader)
304 .bind("fwd_decl"),
305 this);
306}
307
308void ClangMoveTool::run(const ast_matchers::MatchFinder::MatchResult &Result) {
309 if (const auto *CMD =
310 Result.Nodes.getNodeAs<clang::CXXMethodDecl>("class_method")) {
311 // Skip inline class methods. isInline() ast matcher doesn't ignore this
312 // case.
313 if (!CMD->isInlined()) {
314 MovedDecls.emplace_back(CMD, &Result.Context->getSourceManager());
315 RemovedDecls.push_back(MovedDecls.back());
316 }
317 } else if (const auto *VD = Result.Nodes.getNodeAs<clang::VarDecl>(
318 "class_static_var_decl")) {
319 MovedDecls.emplace_back(VD, &Result.Context->getSourceManager());
320 RemovedDecls.push_back(MovedDecls.back());
321 } else if (const auto *class_decl =
322 Result.Nodes.getNodeAs<clang::CXXRecordDecl>("moved_class")) {
323 MovedDecls.emplace_back(class_decl, &Result.Context->getSourceManager());
324 RemovedDecls.push_back(MovedDecls.back());
325 } else if (const auto *FWD =
326 Result.Nodes.getNodeAs<clang::CXXRecordDecl>("fwd_decl")) {
327 // Skip all forwad declarations which appear after moved class declaration.
328 if (RemovedDecls.empty())
329 MovedDecls.emplace_back(FWD, &Result.Context->getSourceManager());
330 } else if (const auto *FD = Result.Nodes.getNodeAs<clang::NamedDecl>(
331 "decls_in_anonymous_ns")) {
332 MovedDecls.emplace_back(FD, &Result.Context->getSourceManager());
333 } else if (const auto *ND =
334 Result.Nodes.getNodeAs<clang::NamedDecl>("static_decls")) {
335 MovedDecls.emplace_back(ND, &Result.Context->getSourceManager());
336 }
337}
338
Haojian Wud2a6d7b2016-10-04 09:05:31 +0000339void ClangMoveTool::addIncludes(llvm::StringRef IncludeHeader,
340 bool IsAngled,
341 llvm::StringRef SearchPath,
342 llvm::StringRef FileName,
343 const SourceManager& SM) {
344 auto AbsoluteSearchPath = MakeAbsolutePath(SM, SearchPath);
Haojian Wudaf4cb82016-09-23 13:28:38 +0000345 // FIXME: Add old.h to the new.cc/h when the new target has dependencies on
346 // old.h/c. For instance, when moved class uses another class defined in
347 // old.h, the old.h should be added in new.h.
Haojian Wud2a6d7b2016-10-04 09:05:31 +0000348 if (MakeAbsolutePath(OriginalRunningDirectory, Spec.OldHeader) ==
349 MakeAbsolutePath(AbsoluteSearchPath, IncludeHeader))
Haojian Wudaf4cb82016-09-23 13:28:38 +0000350 return;
351
352 std::string IncludeLine =
353 IsAngled ? ("#include <" + IncludeHeader + ">\n").str()
354 : ("#include \"" + IncludeHeader + "\"\n").str();
Haojian Wud2a6d7b2016-10-04 09:05:31 +0000355
356 std::string AbsolutePath = MakeAbsolutePath(SM, FileName);
357 if (MakeAbsolutePath(OriginalRunningDirectory, Spec.OldHeader) ==
358 AbsolutePath) {
Haojian Wudaf4cb82016-09-23 13:28:38 +0000359 HeaderIncludes.push_back(IncludeLine);
Haojian Wud2a6d7b2016-10-04 09:05:31 +0000360 } else if (MakeAbsolutePath(OriginalRunningDirectory, Spec.OldCC) ==
361 AbsolutePath) {
Haojian Wudaf4cb82016-09-23 13:28:38 +0000362 CCIncludes.push_back(IncludeLine);
Haojian Wud2a6d7b2016-10-04 09:05:31 +0000363 }
Haojian Wu357ef992016-09-21 13:18:19 +0000364}
365
366void ClangMoveTool::removeClassDefinitionInOldFiles() {
367 for (const auto &MovedDecl : RemovedDecls) {
Haojian Wu253d5962016-10-06 08:29:32 +0000368 const auto &SM = *MovedDecl.SM;
369 auto EndLoc = getLocForEndOfDecl(MovedDecl.Decl, &SM);
Haojian Wu357ef992016-09-21 13:18:19 +0000370 clang::tooling::Replacement RemoveReplacement(
Haojian Wu253d5962016-10-06 08:29:32 +0000371 SM, clang::CharSourceRange::getTokenRange(MovedDecl.Decl->getLocStart(),
372 EndLoc),
Haojian Wu357ef992016-09-21 13:18:19 +0000373 "");
374 std::string FilePath = RemoveReplacement.getFilePath().str();
375 addOrMergeReplacement(RemoveReplacement, &FileToReplacements[FilePath]);
Haojian Wu253d5962016-10-06 08:29:32 +0000376
377 llvm::StringRef Code =
378 SM.getBufferData(SM.getFileID(MovedDecl.Decl->getLocation()));
379 format::FormatStyle Style =
380 format::getStyle("file", FilePath, FallbackStyle);
381 auto CleanReplacements = format::cleanupAroundReplacements(
382 Code, FileToReplacements[FilePath], Style);
383
384 if (!CleanReplacements) {
385 llvm::errs() << llvm::toString(CleanReplacements.takeError()) << "\n";
386 continue;
387 }
388 FileToReplacements[FilePath] = *CleanReplacements;
Haojian Wu357ef992016-09-21 13:18:19 +0000389 }
390}
391
392void ClangMoveTool::moveClassDefinitionToNewFiles() {
393 std::vector<MovedDecl> NewHeaderDecls;
394 std::vector<MovedDecl> NewCCDecls;
395 for (const auto &MovedDecl : MovedDecls) {
Haojian Wud2a6d7b2016-10-04 09:05:31 +0000396 if (isInHeaderFile(*MovedDecl.SM, MovedDecl.Decl, OriginalRunningDirectory,
397 Spec.OldHeader))
Haojian Wu357ef992016-09-21 13:18:19 +0000398 NewHeaderDecls.push_back(MovedDecl);
399 else
400 NewCCDecls.push_back(MovedDecl);
401 }
402
403 if (!Spec.NewHeader.empty())
404 FileToReplacements[Spec.NewHeader] = createInsertedReplacements(
405 HeaderIncludes, NewHeaderDecls, Spec.NewHeader);
406 if (!Spec.NewCC.empty())
407 FileToReplacements[Spec.NewCC] =
408 createInsertedReplacements(CCIncludes, NewCCDecls, Spec.NewCC);
409}
410
411void ClangMoveTool::onEndOfTranslationUnit() {
412 if (RemovedDecls.empty())
413 return;
414 removeClassDefinitionInOldFiles();
415 moveClassDefinitionToNewFiles();
416}
417
418} // namespace move
419} // namespace clang