1 //===--- DefineOutline.cpp ---------------------------------------*- 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 "AST.h"
10 #include "FindTarget.h"
11 #include "HeaderSourceSwitch.h"
12 #include "ParsedAST.h"
13 #include "Selection.h"
14 #include "SourceCode.h"
15 #include "refactor/Tweak.h"
16 #include "support/Logger.h"
17 #include "support/Path.h"
18 #include "clang/AST/ASTTypeTraits.h"
19 #include "clang/AST/Attr.h"
20 #include "clang/AST/Decl.h"
21 #include "clang/AST/DeclBase.h"
22 #include "clang/AST/DeclCXX.h"
23 #include "clang/AST/DeclTemplate.h"
24 #include "clang/AST/Stmt.h"
25 #include "clang/Basic/SourceLocation.h"
26 #include "clang/Basic/SourceManager.h"
27 #include "clang/Basic/TokenKinds.h"
28 #include "clang/Tooling/Core/Replacement.h"
29 #include "clang/Tooling/Syntax/Tokens.h"
30 #include "llvm/ADT/None.h"
31 #include "llvm/ADT/Optional.h"
32 #include "llvm/ADT/STLExtras.h"
33 #include "llvm/ADT/StringRef.h"
34 #include "llvm/Support/Casting.h"
35 #include "llvm/Support/Error.h"
36 #include <cstddef>
37 #include <string>
38 
39 namespace clang {
40 namespace clangd {
41 namespace {
42 
43 // Deduces the FunctionDecl from a selection. Requires either the function body
44 // or the function decl to be selected. Returns null if none of the above
45 // criteria is met.
46 // FIXME: This is shared with define inline, move them to a common header once
47 // we have a place for such.
getSelectedFunction(const SelectionTree::Node * SelNode)48 const FunctionDecl *getSelectedFunction(const SelectionTree::Node *SelNode) {
49   if (!SelNode)
50     return nullptr;
51   const DynTypedNode &AstNode = SelNode->ASTNode;
52   if (const FunctionDecl *FD = AstNode.get<FunctionDecl>())
53     return FD;
54   if (AstNode.get<CompoundStmt>() &&
55       SelNode->Selected == SelectionTree::Complete) {
56     if (const SelectionTree::Node *P = SelNode->Parent)
57       return P->ASTNode.get<FunctionDecl>();
58   }
59   return nullptr;
60 }
61 
getSourceFile(llvm::StringRef FileName,const Tweak::Selection & Sel)62 llvm::Optional<Path> getSourceFile(llvm::StringRef FileName,
63                                    const Tweak::Selection &Sel) {
64   assert(Sel.FS);
65   if (auto Source = getCorrespondingHeaderOrSource(FileName, Sel.FS))
66     return *Source;
67   return getCorrespondingHeaderOrSource(FileName, *Sel.AST, Sel.Index);
68 }
69 
70 // Synthesize a DeclContext for TargetNS from CurContext. TargetNS must be empty
71 // for global namespace, and endwith "::" otherwise.
72 // Returns None if TargetNS is not a prefix of CurContext.
73 llvm::Optional<const DeclContext *>
findContextForNS(llvm::StringRef TargetNS,const DeclContext * CurContext)74 findContextForNS(llvm::StringRef TargetNS, const DeclContext *CurContext) {
75   assert(TargetNS.empty() || TargetNS.endswith("::"));
76   // Skip any non-namespace contexts, e.g. TagDecls, functions/methods.
77   CurContext = CurContext->getEnclosingNamespaceContext();
78   // If TargetNS is empty, it means global ns, which is translation unit.
79   if (TargetNS.empty()) {
80     while (!CurContext->isTranslationUnit())
81       CurContext = CurContext->getParent();
82     return CurContext;
83   }
84   // Otherwise we need to drop any trailing namespaces from CurContext until
85   // we reach TargetNS.
86   std::string TargetContextNS =
87       CurContext->isNamespace()
88           ? llvm::cast<NamespaceDecl>(CurContext)->getQualifiedNameAsString()
89           : "";
90   TargetContextNS.append("::");
91 
92   llvm::StringRef CurrentContextNS(TargetContextNS);
93   // If TargetNS is not a prefix of CurrentContext, there's no way to reach
94   // it.
95   if (!CurrentContextNS.startswith(TargetNS))
96     return llvm::None;
97 
98   while (CurrentContextNS != TargetNS) {
99     CurContext = CurContext->getParent();
100     // These colons always exists since TargetNS is a prefix of
101     // CurrentContextNS, it ends with "::" and they are not equal.
102     CurrentContextNS = CurrentContextNS.take_front(
103         CurrentContextNS.drop_back(2).rfind("::") + 2);
104   }
105   return CurContext;
106 }
107 
108 // Returns source code for FD after applying Replacements.
109 // FIXME: Make the function take a parameter to return only the function body,
110 // afterwards it can be shared with define-inline code action.
111 llvm::Expected<std::string>
getFunctionSourceAfterReplacements(const FunctionDecl * FD,const tooling::Replacements & Replacements)112 getFunctionSourceAfterReplacements(const FunctionDecl *FD,
113                                    const tooling::Replacements &Replacements) {
114   const auto &SM = FD->getASTContext().getSourceManager();
115   auto OrigFuncRange = toHalfOpenFileRange(
116       SM, FD->getASTContext().getLangOpts(), FD->getSourceRange());
117   if (!OrigFuncRange)
118     return error("Couldn't get range for function.");
119   assert(!FD->getDescribedFunctionTemplate() &&
120          "Define out-of-line doesn't apply to function templates.");
121 
122   // Get new begin and end positions for the qualified function definition.
123   unsigned FuncBegin = SM.getFileOffset(OrigFuncRange->getBegin());
124   unsigned FuncEnd = Replacements.getShiftedCodePosition(
125       SM.getFileOffset(OrigFuncRange->getEnd()));
126 
127   // Trim the result to function definition.
128   auto QualifiedFunc = tooling::applyAllReplacements(
129       SM.getBufferData(SM.getMainFileID()), Replacements);
130   if (!QualifiedFunc)
131     return QualifiedFunc.takeError();
132   return QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
133 }
134 
135 // Creates a modified version of function definition that can be inserted at a
136 // different location, qualifies return value and function name to achieve that.
137 // Contains function signature, except defaulted parameter arguments, body and
138 // template parameters if applicable. No need to qualify parameters, as they are
139 // looked up in the context containing the function/method.
140 // FIXME: Drop attributes in function signature.
141 llvm::Expected<std::string>
getFunctionSourceCode(const FunctionDecl * FD,llvm::StringRef TargetNamespace,const syntax::TokenBuffer & TokBuf,const HeuristicResolver * Resolver)142 getFunctionSourceCode(const FunctionDecl *FD, llvm::StringRef TargetNamespace,
143                       const syntax::TokenBuffer &TokBuf,
144                       const HeuristicResolver *Resolver) {
145   auto &AST = FD->getASTContext();
146   auto &SM = AST.getSourceManager();
147   auto TargetContext = findContextForNS(TargetNamespace, FD->getDeclContext());
148   if (!TargetContext)
149     return error("define outline: couldn't find a context for target");
150 
151   llvm::Error Errors = llvm::Error::success();
152   tooling::Replacements DeclarationCleanups;
153 
154   // Finds the first unqualified name in function return type and name, then
155   // qualifies those to be valid in TargetContext.
156   findExplicitReferences(
157       FD,
158       [&](ReferenceLoc Ref) {
159         // It is enough to qualify the first qualifier, so skip references with
160         // a qualifier. Also we can't do much if there are no targets or name is
161         // inside a macro body.
162         if (Ref.Qualifier || Ref.Targets.empty() || Ref.NameLoc.isMacroID())
163           return;
164         // Only qualify return type and function name.
165         if (Ref.NameLoc != FD->getReturnTypeSourceRange().getBegin() &&
166             Ref.NameLoc != FD->getLocation())
167           return;
168 
169         for (const NamedDecl *ND : Ref.Targets) {
170           if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) {
171             elog("Targets from multiple contexts: {0}, {1}",
172                  printQualifiedName(*Ref.Targets.front()),
173                  printQualifiedName(*ND));
174             return;
175           }
176         }
177         const NamedDecl *ND = Ref.Targets.front();
178         const std::string Qualifier =
179             getQualification(AST, *TargetContext,
180                              SM.getLocForStartOfFile(SM.getMainFileID()), ND);
181         if (auto Err = DeclarationCleanups.add(
182                 tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier)))
183           Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
184       },
185       Resolver);
186 
187   // Get rid of default arguments, since they should not be specified in
188   // out-of-line definition.
189   for (const auto *PVD : FD->parameters()) {
190     if (!PVD->hasDefaultArg())
191       continue;
192     // Deletion range spans the initializer, usually excluding the `=`.
193     auto DelRange = CharSourceRange::getTokenRange(PVD->getDefaultArgRange());
194     // Get all tokens before the default argument.
195     auto Tokens = TokBuf.expandedTokens(PVD->getSourceRange())
196                       .take_while([&SM, &DelRange](const syntax::Token &Tok) {
197                         return SM.isBeforeInTranslationUnit(
198                             Tok.location(), DelRange.getBegin());
199                       });
200     if (TokBuf.expandedTokens(DelRange.getAsRange()).front().kind() !=
201         tok::equal) {
202       // Find the last `=` if it isn't included in the initializer, and update
203       // the DelRange to include it.
204       auto Tok =
205           llvm::find_if(llvm::reverse(Tokens), [](const syntax::Token &Tok) {
206             return Tok.kind() == tok::equal;
207           });
208       assert(Tok != Tokens.rend());
209       DelRange.setBegin(Tok->location());
210     }
211     if (auto Err =
212             DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
213       Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
214   }
215 
216   auto DelAttr = [&](const Attr *A) {
217     if (!A)
218       return;
219     auto AttrTokens =
220         TokBuf.spelledForExpanded(TokBuf.expandedTokens(A->getRange()));
221     assert(A->getLocation().isValid());
222     if (!AttrTokens || AttrTokens->empty()) {
223       Errors = llvm::joinErrors(
224           std::move(Errors), error("define outline: Can't move out of line as "
225                                    "function has a macro `{0}` specifier.",
226                                    A->getSpelling()));
227       return;
228     }
229     CharSourceRange DelRange =
230         syntax::Token::range(SM, AttrTokens->front(), AttrTokens->back())
231             .toCharRange(SM);
232     if (auto Err =
233             DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
234       Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
235   };
236 
237   DelAttr(FD->getAttr<OverrideAttr>());
238   DelAttr(FD->getAttr<FinalAttr>());
239 
240   auto DelKeyword = [&](tok::TokenKind Kind, SourceRange FromRange) {
241     bool FoundAny = false;
242     for (const auto &Tok : TokBuf.expandedTokens(FromRange)) {
243       if (Tok.kind() != Kind)
244         continue;
245       FoundAny = true;
246       auto Spelling = TokBuf.spelledForExpanded(llvm::makeArrayRef(Tok));
247       if (!Spelling) {
248         Errors = llvm::joinErrors(
249             std::move(Errors),
250             error("define outline: couldn't remove `{0}` keyword.",
251                   tok::getKeywordSpelling(Kind)));
252         break;
253       }
254       CharSourceRange DelRange =
255           syntax::Token::range(SM, Spelling->front(), Spelling->back())
256               .toCharRange(SM);
257       if (auto Err =
258               DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
259         Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
260     }
261     if (!FoundAny) {
262       Errors = llvm::joinErrors(
263           std::move(Errors),
264           error("define outline: couldn't find `{0}` keyword to remove.",
265                 tok::getKeywordSpelling(Kind)));
266     }
267   };
268 
269   if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
270     if (MD->isVirtualAsWritten())
271       DelKeyword(tok::kw_virtual, {FD->getBeginLoc(), FD->getLocation()});
272     if (MD->isStatic())
273       DelKeyword(tok::kw_static, {FD->getBeginLoc(), FD->getLocation()});
274   }
275   if (const auto *CD = dyn_cast<CXXConstructorDecl>(FD)) {
276     if (CD->isExplicit())
277       DelKeyword(tok::kw_explicit, {FD->getBeginLoc(), FD->getLocation()});
278   }
279 
280   if (Errors)
281     return std::move(Errors);
282   return getFunctionSourceAfterReplacements(FD, DeclarationCleanups);
283 }
284 
285 struct InsertionPoint {
286   std::string EnclosingNamespace;
287   size_t Offset;
288 };
289 // Returns the most natural insertion point for \p QualifiedName in \p Contents.
290 // This currently cares about only the namespace proximity, but in feature it
291 // should also try to follow ordering of declarations. For example, if decls
292 // come in order `foo, bar, baz` then this function should return some point
293 // between foo and baz for inserting bar.
getInsertionPoint(llvm::StringRef Contents,llvm::StringRef QualifiedName,const LangOptions & LangOpts)294 llvm::Expected<InsertionPoint> getInsertionPoint(llvm::StringRef Contents,
295                                                  llvm::StringRef QualifiedName,
296                                                  const LangOptions &LangOpts) {
297   auto Region = getEligiblePoints(Contents, QualifiedName, LangOpts);
298 
299   assert(!Region.EligiblePoints.empty());
300   // FIXME: This selection can be made smarter by looking at the definition
301   // locations for adjacent decls to Source. Unfortunately pseudo parsing in
302   // getEligibleRegions only knows about namespace begin/end events so we
303   // can't match function start/end positions yet.
304   auto Offset = positionToOffset(Contents, Region.EligiblePoints.back());
305   if (!Offset)
306     return Offset.takeError();
307   return InsertionPoint{Region.EnclosingNamespace, *Offset};
308 }
309 
310 // Returns the range that should be deleted from declaration, which always
311 // contains function body. In addition to that it might contain constructor
312 // initializers.
getDeletionRange(const FunctionDecl * FD,const syntax::TokenBuffer & TokBuf)313 SourceRange getDeletionRange(const FunctionDecl *FD,
314                              const syntax::TokenBuffer &TokBuf) {
315   auto DeletionRange = FD->getBody()->getSourceRange();
316   if (auto *CD = llvm::dyn_cast<CXXConstructorDecl>(FD)) {
317     // AST doesn't contain the location for ":" in ctor initializers. Therefore
318     // we find it by finding the first ":" before the first ctor initializer.
319     SourceLocation InitStart;
320     // Find the first initializer.
321     for (const auto *CInit : CD->inits()) {
322       // SourceOrder is -1 for implicit initializers.
323       if (CInit->getSourceOrder() != 0)
324         continue;
325       InitStart = CInit->getSourceLocation();
326       break;
327     }
328     if (InitStart.isValid()) {
329       auto Toks = TokBuf.expandedTokens(CD->getSourceRange());
330       // Drop any tokens after the initializer.
331       Toks = Toks.take_while([&TokBuf, &InitStart](const syntax::Token &Tok) {
332         return TokBuf.sourceManager().isBeforeInTranslationUnit(Tok.location(),
333                                                                 InitStart);
334       });
335       // Look for the first colon.
336       auto Tok =
337           llvm::find_if(llvm::reverse(Toks), [](const syntax::Token &Tok) {
338             return Tok.kind() == tok::colon;
339           });
340       assert(Tok != Toks.rend());
341       DeletionRange.setBegin(Tok->location());
342     }
343   }
344   return DeletionRange;
345 }
346 
347 /// Moves definition of a function/method to an appropriate implementation file.
348 ///
349 /// Before:
350 /// a.h
351 ///   void foo() { return; }
352 /// a.cc
353 ///   #include "a.h"
354 ///
355 /// ----------------
356 ///
357 /// After:
358 /// a.h
359 ///   void foo();
360 /// a.cc
361 ///   #include "a.h"
362 ///   void foo() { return; }
363 class DefineOutline : public Tweak {
364 public:
365   const char *id() const override;
366 
hidden() const367   bool hidden() const override { return false; }
kind() const368   llvm::StringLiteral kind() const override {
369     return CodeAction::REFACTOR_KIND;
370   }
title() const371   std::string title() const override {
372     return "Move function body to out-of-line";
373   }
374 
prepare(const Selection & Sel)375   bool prepare(const Selection &Sel) override {
376     // Bail out if we are not in a header file.
377     // FIXME: We might want to consider moving method definitions below class
378     // definition even if we are inside a source file.
379     if (!isHeaderFile(Sel.AST->getSourceManager().getFilename(Sel.Cursor),
380                       Sel.AST->getLangOpts()))
381       return false;
382 
383     Source = getSelectedFunction(Sel.ASTSelection.commonAncestor());
384     // Bail out if the selection is not a in-line function definition.
385     if (!Source || !Source->doesThisDeclarationHaveABody() ||
386         Source->isOutOfLine())
387       return false;
388 
389     // Bail out if this is a function template or specialization, as their
390     // definitions need to be visible in all including translation units.
391     if (Source->getDescribedFunctionTemplate())
392       return false;
393     if (Source->getTemplateSpecializationInfo())
394       return false;
395 
396     // Bail out in templated classes, as it is hard to spell the class name, i.e
397     // if the template parameter is unnamed.
398     if (auto *MD = llvm::dyn_cast<CXXMethodDecl>(Source)) {
399       if (MD->getParent()->isTemplated())
400         return false;
401     }
402 
403     // Note that we don't check whether an implementation file exists or not in
404     // the prepare, since performing disk IO on each prepare request might be
405     // expensive.
406     return true;
407   }
408 
apply(const Selection & Sel)409   Expected<Effect> apply(const Selection &Sel) override {
410     const SourceManager &SM = Sel.AST->getSourceManager();
411     auto MainFileName =
412         getCanonicalPath(SM.getFileEntryForID(SM.getMainFileID()), SM);
413     if (!MainFileName)
414       return error("Couldn't get absolute path for main file.");
415 
416     auto CCFile = getSourceFile(*MainFileName, Sel);
417 
418     if (!CCFile)
419       return error("Couldn't find a suitable implementation file.");
420     assert(Sel.FS && "FS Must be set in apply");
421     auto Buffer = Sel.FS->getBufferForFile(*CCFile);
422     // FIXME: Maybe we should consider creating the implementation file if it
423     // doesn't exist?
424     if (!Buffer)
425       return llvm::errorCodeToError(Buffer.getError());
426     auto Contents = Buffer->get()->getBuffer();
427     auto InsertionPoint = getInsertionPoint(
428         Contents, Source->getQualifiedNameAsString(), Sel.AST->getLangOpts());
429     if (!InsertionPoint)
430       return InsertionPoint.takeError();
431 
432     auto FuncDef = getFunctionSourceCode(
433         Source, InsertionPoint->EnclosingNamespace, Sel.AST->getTokens(),
434         Sel.AST->getHeuristicResolver());
435     if (!FuncDef)
436       return FuncDef.takeError();
437 
438     SourceManagerForFile SMFF(*CCFile, Contents);
439     const tooling::Replacement InsertFunctionDef(
440         *CCFile, InsertionPoint->Offset, 0, *FuncDef);
441     auto Effect = Effect::mainFileEdit(
442         SMFF.get(), tooling::Replacements(InsertFunctionDef));
443     if (!Effect)
444       return Effect.takeError();
445 
446     // FIXME: We should also get rid of inline qualifier.
447     const tooling::Replacement DeleteFuncBody(
448         Sel.AST->getSourceManager(),
449         CharSourceRange::getTokenRange(*toHalfOpenFileRange(
450             SM, Sel.AST->getLangOpts(),
451             getDeletionRange(Source, Sel.AST->getTokens()))),
452         ";");
453     auto HeaderFE = Effect::fileEdit(SM, SM.getMainFileID(),
454                                      tooling::Replacements(DeleteFuncBody));
455     if (!HeaderFE)
456       return HeaderFE.takeError();
457 
458     Effect->ApplyEdits.try_emplace(HeaderFE->first,
459                                    std::move(HeaderFE->second));
460     return std::move(*Effect);
461   }
462 
463 private:
464   const FunctionDecl *Source = nullptr;
465 };
466 
467 REGISTER_TWEAK(DefineOutline)
468 
469 } // namespace
470 } // namespace clangd
471 } // namespace clang
472