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