blob: c04b598e6266af3597e5727eb3b879ce694e39f5 [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"
19
20using namespace clang::ast_matchers;
21
22namespace clang {
23namespace move {
24namespace {
25
26// FIXME: Move to ASTMatcher.
27AST_POLYMORPHIC_MATCHER(isStatic, AST_POLYMORPHIC_SUPPORTED_TYPES(FunctionDecl,
28 VarDecl)) {
29 return Node.getStorageClass() == SC_Static;
30}
31
32class FindAllIncludes : public clang::PPCallbacks {
33public:
34 explicit FindAllIncludes(SourceManager *SM, ClangMoveTool *const MoveTool)
35 : SM(*SM), MoveTool(MoveTool) {}
36
37 void InclusionDirective(clang::SourceLocation HashLoc,
38 const clang::Token & /*IncludeTok*/,
39 StringRef FileName, bool IsAngled,
40 clang::CharSourceRange /*FilenameRange*/,
41 const clang::FileEntry * /*File*/,
42 StringRef /*SearchPath*/, StringRef /*RelativePath*/,
43 const clang::Module * /*Imported*/) override {
44 if (const auto *FileEntry = SM.getFileEntryForID(SM.getFileID(HashLoc))) {
45 if (IsAngled) {
46 MoveTool->addIncludes("#include <" + FileName.str() + ">\n",
47 FileEntry->getName());
48 } else {
49 MoveTool->addIncludes("#include \"" + FileName.str() + "\"\n",
50 FileEntry->getName());
51 }
52 }
53 }
54
55private:
56 const SourceManager &SM;
57 ClangMoveTool *const MoveTool;
58};
59
60clang::tooling::Replacement
61getReplacementInChangedCode(const clang::tooling::Replacements &Replacements,
62 const clang::tooling::Replacement &Replacement) {
63 unsigned Start = Replacements.getShiftedCodePosition(Replacement.getOffset());
64 unsigned End = Replacements.getShiftedCodePosition(Replacement.getOffset() +
65 Replacement.getLength());
66 return clang::tooling::Replacement(Replacement.getFilePath(), Start,
67 End - Start,
68 Replacement.getReplacementText());
69}
70
71void addOrMergeReplacement(const clang::tooling::Replacement &Replacement,
72 clang::tooling::Replacements *Replacements) {
73 auto Err = Replacements->add(Replacement);
74 if (Err) {
75 llvm::consumeError(std::move(Err));
76 auto Replace = getReplacementInChangedCode(*Replacements, Replacement);
77 *Replacements = Replacements->merge(clang::tooling::Replacements(Replace));
78 }
79}
80
81bool IsInHeaderFile(const clang::SourceManager &SM, const clang::Decl *D,
82 llvm::StringRef HeaderFile) {
83 if (HeaderFile.empty())
84 return false;
85 auto ExpansionLoc = SM.getExpansionLoc(D->getLocStart());
86 if (ExpansionLoc.isInvalid())
87 return false;
88
89 if (const auto *FE = SM.getFileEntryForID(SM.getFileID(ExpansionLoc)))
90 return llvm::StringRef(FE->getName()).endswith(HeaderFile);
91
92 return false;
93}
94
95std::vector<std::string> GetNamespaces(const clang::Decl *D) {
96 std::vector<std::string> Namespaces;
97 for (const auto *Context = D->getDeclContext(); Context;
98 Context = Context->getParent()) {
99 if (llvm::isa<clang::TranslationUnitDecl>(Context) ||
100 llvm::isa<clang::LinkageSpecDecl>(Context))
101 break;
102
103 if (const auto *ND = llvm::dyn_cast<clang::NamespaceDecl>(Context))
104 Namespaces.push_back(ND->getName().str());
105 }
106 std::reverse(Namespaces.begin(), Namespaces.end());
107 return Namespaces;
108}
109
110SourceLocation getLocForEndOfDecl(const clang::Decl *D,
111 const clang::SourceManager *SM) {
112 auto End = D->getLocEnd();
113 clang::SourceLocation AfterSemi = clang::Lexer::findLocationAfterToken(
114 End, clang::tok::semi, *SM, clang::LangOptions(),
115 /*SkipTrailingWhitespaceAndNewLine=*/true);
116 if (AfterSemi.isValid())
117 End = AfterSemi.getLocWithOffset(-1);
118 return End;
119}
120
121std::string getDeclarationSourceText(const clang::Decl *D,
122 const clang::SourceManager *SM) {
123 auto EndLoc = getLocForEndOfDecl(D, SM);
124 llvm::StringRef SourceText = clang::Lexer::getSourceText(
125 clang::CharSourceRange::getTokenRange(D->getLocStart(), EndLoc), *SM,
126 clang::LangOptions());
127 return SourceText.str() + "\n";
128}
129
130clang::tooling::Replacements
131createInsertedReplacements(const std::vector<std::string> &Includes,
132 const std::vector<ClangMoveTool::MovedDecl> &Decls,
133 llvm::StringRef FileName) {
134 clang::tooling::Replacements InsertedReplacements;
135
136 // Add #Includes.
137 std::string AllIncludesString;
138 // FIXME: Filter out the old_header.h and add header guard.
139 for (const auto &Include : Includes)
140 AllIncludesString += Include;
141 clang::tooling::Replacement InsertInclude(FileName, 0, 0, AllIncludesString);
142 addOrMergeReplacement(InsertInclude, &InsertedReplacements);
143
144 // Add moved class definition and its related declarations. All declarations
145 // in same namespace are grouped together.
146 std::vector<std::string> CurrentNamespaces;
147 for (const auto &MovedDecl : Decls) {
148 std::vector<std::string> DeclNamespaces = GetNamespaces(MovedDecl.Decl);
149 auto CurrentIt = CurrentNamespaces.begin();
150 auto DeclIt = DeclNamespaces.begin();
151 while (CurrentIt != CurrentNamespaces.end() &&
152 DeclIt != DeclNamespaces.end()) {
153 if (*CurrentIt != *DeclIt)
154 break;
155 ++CurrentIt;
156 ++DeclIt;
157 }
158 std::vector<std::string> NextNamespaces(CurrentNamespaces.begin(),
159 CurrentIt);
160 NextNamespaces.insert(NextNamespaces.end(), DeclIt, DeclNamespaces.end());
161 auto RemainingSize = CurrentNamespaces.end() - CurrentIt;
162 for (auto It = CurrentNamespaces.rbegin(); RemainingSize > 0;
163 --RemainingSize, ++It) {
164 assert(It < CurrentNamespaces.rend());
165 auto code = "} // namespace " + *It + "\n";
166 clang::tooling::Replacement InsertedReplacement(FileName, 0, 0, code);
167 addOrMergeReplacement(InsertedReplacement, &InsertedReplacements);
168 }
169 while (DeclIt != DeclNamespaces.end()) {
170 clang::tooling::Replacement InsertedReplacement(
171 FileName, 0, 0, "namespace " + *DeclIt + " {\n");
172 addOrMergeReplacement(InsertedReplacement, &InsertedReplacements);
173 ++DeclIt;
174 }
175
176 // FIXME: consider moving comments of the moved declaration.
177 clang::tooling::Replacement InsertedReplacement(
178 FileName, 0, 0, getDeclarationSourceText(MovedDecl.Decl, MovedDecl.SM));
179 addOrMergeReplacement(InsertedReplacement, &InsertedReplacements);
180
181 CurrentNamespaces = std::move(NextNamespaces);
182 }
183 std::reverse(CurrentNamespaces.begin(), CurrentNamespaces.end());
184 for (const auto &NS : CurrentNamespaces) {
185 clang::tooling::Replacement InsertedReplacement(
186 FileName, 0, 0, "} // namespace " + NS + "\n");
187 addOrMergeReplacement(InsertedReplacement, &InsertedReplacements);
188 }
189 return InsertedReplacements;
190}
191
192} // namespace
193
194std::unique_ptr<clang::ASTConsumer>
195ClangMoveAction::CreateASTConsumer(clang::CompilerInstance &Compiler,
196 StringRef /*InFile*/) {
197 Compiler.getPreprocessor().addPPCallbacks(llvm::make_unique<FindAllIncludes>(
198 &Compiler.getSourceManager(), &MoveTool));
199 return MatchFinder.newASTConsumer();
200}
201
202
203ClangMoveTool::ClangMoveTool(
204 const MoveDefinitionSpec &MoveSpec,
205 std::map<std::string, tooling::Replacements> &FileToReplacements)
206 : Spec(MoveSpec), FileToReplacements(FileToReplacements) {
207 Spec.Name = llvm::StringRef(Spec.Name).ltrim(':');
208}
209
210void ClangMoveTool::registerMatchers(ast_matchers::MatchFinder *Finder) {
211 std::string FullyQualifiedName = "::" + Spec.Name;
212 auto InOldHeader = isExpansionInFileMatching(Spec.OldHeader);
213 auto InOldCC = isExpansionInFileMatching(Spec.OldCC);
214 auto InOldFiles = anyOf(InOldHeader, InOldCC);
215 auto InMovedClass =
216 hasDeclContext(cxxRecordDecl(hasName(FullyQualifiedName)));
217
218 // Match moved class declarations.
219 auto MovedClass = cxxRecordDecl(
220 InOldFiles, hasName(FullyQualifiedName), isDefinition(),
221 hasDeclContext(anyOf(namespaceDecl(), translationUnitDecl())));
222 Finder->addMatcher(MovedClass.bind("moved_class"), this);
223
224 // Match moved class methods (static methods included) which are defined
225 // outside moved class declaration.
226 Finder->addMatcher(cxxMethodDecl(InOldFiles,
227 ofClass(hasName(FullyQualifiedName)),
228 isDefinition())
229 .bind("class_method"),
230 this);
231
232 // Match static member variable definition of the moved class.
233 Finder->addMatcher(varDecl(InMovedClass, InOldCC, isDefinition())
234 .bind("class_static_var_decl"),
235 this);
236
237 auto inAnonymousNamespace = hasParent(namespaceDecl(isAnonymous()));
238 // Match functions/variables definitions which are defined in anonymous
239 // namespace in old cc.
240 Finder->addMatcher(
241 namedDecl(anyOf(functionDecl(isDefinition()), varDecl(isDefinition())),
242 inAnonymousNamespace)
243 .bind("decls_in_anonymous_ns"),
244 this);
245
246 // Match static functions/variabale definitions in old cc.
247 Finder->addMatcher(
248 namedDecl(anyOf(functionDecl(isDefinition(), unless(InMovedClass),
249 isStatic(), InOldCC),
250 varDecl(isDefinition(), unless(InMovedClass), isStatic(),
251 InOldCC)))
252 .bind("static_decls"),
253 this);
254
255 // Match forward declarations in old header.
256 Finder->addMatcher(
257 cxxRecordDecl(unless(anyOf(isImplicit(), isDefinition())), InOldHeader)
258 .bind("fwd_decl"),
259 this);
260}
261
262void ClangMoveTool::run(const ast_matchers::MatchFinder::MatchResult &Result) {
263 if (const auto *CMD =
264 Result.Nodes.getNodeAs<clang::CXXMethodDecl>("class_method")) {
265 // Skip inline class methods. isInline() ast matcher doesn't ignore this
266 // case.
267 if (!CMD->isInlined()) {
268 MovedDecls.emplace_back(CMD, &Result.Context->getSourceManager());
269 RemovedDecls.push_back(MovedDecls.back());
270 }
271 } else if (const auto *VD = Result.Nodes.getNodeAs<clang::VarDecl>(
272 "class_static_var_decl")) {
273 MovedDecls.emplace_back(VD, &Result.Context->getSourceManager());
274 RemovedDecls.push_back(MovedDecls.back());
275 } else if (const auto *class_decl =
276 Result.Nodes.getNodeAs<clang::CXXRecordDecl>("moved_class")) {
277 MovedDecls.emplace_back(class_decl, &Result.Context->getSourceManager());
278 RemovedDecls.push_back(MovedDecls.back());
279 } else if (const auto *FWD =
280 Result.Nodes.getNodeAs<clang::CXXRecordDecl>("fwd_decl")) {
281 // Skip all forwad declarations which appear after moved class declaration.
282 if (RemovedDecls.empty())
283 MovedDecls.emplace_back(FWD, &Result.Context->getSourceManager());
284 } else if (const auto *FD = Result.Nodes.getNodeAs<clang::NamedDecl>(
285 "decls_in_anonymous_ns")) {
286 MovedDecls.emplace_back(FD, &Result.Context->getSourceManager());
287 } else if (const auto *ND =
288 Result.Nodes.getNodeAs<clang::NamedDecl>("static_decls")) {
289 MovedDecls.emplace_back(ND, &Result.Context->getSourceManager());
290 }
291}
292
293void ClangMoveTool::addIncludes(llvm::StringRef IncludeLine,
294 llvm::StringRef FileName) {
295 if (!Spec.OldHeader.empty() && FileName.endswith(Spec.OldHeader))
296 HeaderIncludes.push_back(IncludeLine.str());
297 else if (!Spec.OldCC.empty() && FileName.endswith(Spec.OldCC))
298 CCIncludes.push_back(IncludeLine.str());
299}
300
301void ClangMoveTool::removeClassDefinitionInOldFiles() {
302 for (const auto &MovedDecl : RemovedDecls) {
303 auto EndLoc = getLocForEndOfDecl(MovedDecl.Decl, MovedDecl.SM);
304 clang::tooling::Replacement RemoveReplacement(
305 *MovedDecl.SM, clang::CharSourceRange::getTokenRange(
306 MovedDecl.Decl->getLocStart(), EndLoc),
307 "");
308 std::string FilePath = RemoveReplacement.getFilePath().str();
309 addOrMergeReplacement(RemoveReplacement, &FileToReplacements[FilePath]);
310 }
311}
312
313void ClangMoveTool::moveClassDefinitionToNewFiles() {
314 std::vector<MovedDecl> NewHeaderDecls;
315 std::vector<MovedDecl> NewCCDecls;
316 for (const auto &MovedDecl : MovedDecls) {
317 if (IsInHeaderFile(*MovedDecl.SM, MovedDecl.Decl, Spec.OldHeader))
318 NewHeaderDecls.push_back(MovedDecl);
319 else
320 NewCCDecls.push_back(MovedDecl);
321 }
322
323 if (!Spec.NewHeader.empty())
324 FileToReplacements[Spec.NewHeader] = createInsertedReplacements(
325 HeaderIncludes, NewHeaderDecls, Spec.NewHeader);
326 if (!Spec.NewCC.empty())
327 FileToReplacements[Spec.NewCC] =
328 createInsertedReplacements(CCIncludes, NewCCDecls, Spec.NewCC);
329}
330
331void ClangMoveTool::onEndOfTranslationUnit() {
332 if (RemovedDecls.empty())
333 return;
334 removeClassDefinitionInOldFiles();
335 moveClassDefinitionToNewFiles();
336}
337
338} // namespace move
339} // namespace clang