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 "Logger.h"
13 #include "ParsedAST.h"
14 #include "Path.h"
15 #include "Selection.h"
16 #include "SourceCode.h"
17 #include "refactor/Tweak.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 ast_type_traits::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 llvm::createStringError(llvm::inconvertibleErrorCode(),
123                                    "Couldn't get range for function.");
124   // Include template parameter list.
125   if (auto *FTD = FD->getDescribedFunctionTemplate())
126     OrigFuncRange->setBegin(FTD->getBeginLoc());
127 
128   // Get new begin and end positions for the qualified function definition.
129   unsigned FuncBegin = SM.getFileOffset(OrigFuncRange->getBegin());
130   unsigned FuncEnd = Replacements.getShiftedCodePosition(
131       SM.getFileOffset(OrigFuncRange->getEnd()));
132 
133   // Trim the result to function definition.
134   auto QualifiedFunc = tooling::applyAllReplacements(
135       SM.getBufferData(SM.getMainFileID()), Replacements);
136   if (!QualifiedFunc)
137     return QualifiedFunc.takeError();
138   return QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
139 }
140 
141 // Creates a modified version of function definition that can be inserted at a
142 // different location, qualifies return value and function name to achieve that.
143 // Contains function signature, except defaulted parameter arguments, body and
144 // template parameters if applicable. No need to qualify parameters, as they are
145 // looked up in the context containing the function/method.
146 // FIXME: Drop attributes in function signature.
147 llvm::Expected<std::string>
getFunctionSourceCode(const FunctionDecl * FD,llvm::StringRef TargetNamespace,const syntax::TokenBuffer & TokBuf)148 getFunctionSourceCode(const FunctionDecl *FD, llvm::StringRef TargetNamespace,
149                       const syntax::TokenBuffer &TokBuf) {
150   auto &AST = FD->getASTContext();
151   auto &SM = AST.getSourceManager();
152   auto TargetContext = findContextForNS(TargetNamespace, FD->getDeclContext());
153   if (!TargetContext)
154     return llvm::createStringError(
155         llvm::inconvertibleErrorCode(),
156         "define outline: couldn't find a context for target");
157 
158   llvm::Error Errors = llvm::Error::success();
159   tooling::Replacements DeclarationCleanups;
160 
161   // Finds the first unqualified name in function return type and name, then
162   // qualifies those to be valid in TargetContext.
163   findExplicitReferences(FD, [&](ReferenceLoc Ref) {
164     // It is enough to qualify the first qualifier, so skip references with a
165     // qualifier. Also we can't do much if there are no targets or name is
166     // inside a macro body.
167     if (Ref.Qualifier || Ref.Targets.empty() || Ref.NameLoc.isMacroID())
168       return;
169     // Only qualify return type and function name.
170     if (Ref.NameLoc != FD->getReturnTypeSourceRange().getBegin() &&
171         Ref.NameLoc != FD->getLocation())
172       return;
173 
174     for (const NamedDecl *ND : Ref.Targets) {
175       if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) {
176         elog("Targets from multiple contexts: {0}, {1}",
177              printQualifiedName(*Ref.Targets.front()), printQualifiedName(*ND));
178         return;
179       }
180     }
181     const NamedDecl *ND = Ref.Targets.front();
182     const std::string Qualifier = getQualification(
183         AST, *TargetContext, SM.getLocForStartOfFile(SM.getMainFileID()), ND);
184     if (auto Err = DeclarationCleanups.add(
185             tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier)))
186       Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
187   });
188 
189   // Get rid of default arguments, since they should not be specified in
190   // out-of-line definition.
191   for (const auto *PVD : FD->parameters()) {
192     if (PVD->hasDefaultArg()) {
193       // Deletion range initially spans the initializer, excluding the `=`.
194       auto DelRange = CharSourceRange::getTokenRange(PVD->getDefaultArgRange());
195       // Get all tokens before the default argument.
196       auto Tokens = TokBuf.expandedTokens(PVD->getSourceRange())
197                         .take_while([&SM, &DelRange](const syntax::Token &Tok) {
198                           return SM.isBeforeInTranslationUnit(
199                               Tok.location(), DelRange.getBegin());
200                         });
201       // Find the last `=` before the default arg.
202       auto Tok =
203           llvm::find_if(llvm::reverse(Tokens), [](const syntax::Token &Tok) {
204             return Tok.kind() == tok::equal;
205           });
206       assert(Tok != Tokens.rend());
207       DelRange.setBegin(Tok->location());
208       if (auto Err =
209               DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
210         Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
211     }
212   }
213 
214   auto DelAttr = [&](const Attr *A) {
215     if (!A)
216       return;
217     auto AttrTokens =
218         TokBuf.spelledForExpanded(TokBuf.expandedTokens(A->getRange()));
219     assert(A->getLocation().isValid());
220     if (!AttrTokens || AttrTokens->empty()) {
221       Errors = llvm::joinErrors(
222           std::move(Errors),
223           llvm::createStringError(
224               llvm::inconvertibleErrorCode(),
225               llvm::StringRef("define outline: Can't move out of line as "
226                               "function has a macro `") +
227                   A->getSpelling() + "` specifier."));
228       return;
229     }
230     CharSourceRange DelRange =
231         syntax::Token::range(SM, AttrTokens->front(), AttrTokens->back())
232             .toCharRange(SM);
233     if (auto Err =
234             DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
235       Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
236   };
237 
238   DelAttr(FD->getAttr<OverrideAttr>());
239   DelAttr(FD->getAttr<FinalAttr>());
240 
241   if (FD->isVirtualAsWritten()) {
242     SourceRange SpecRange{FD->getBeginLoc(), FD->getLocation()};
243     bool HasErrors = true;
244 
245     // Clang allows duplicating virtual specifiers so check for multiple
246     // occurances.
247     for (const auto &Tok : TokBuf.expandedTokens(SpecRange)) {
248       if (Tok.kind() != tok::kw_virtual)
249         continue;
250       auto Spelling = TokBuf.spelledForExpanded(llvm::makeArrayRef(Tok));
251       if (!Spelling) {
252         HasErrors = true;
253         break;
254       }
255       HasErrors = false;
256       CharSourceRange DelRange =
257           syntax::Token::range(SM, Spelling->front(), Spelling->back())
258               .toCharRange(SM);
259       if (auto Err =
260               DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
261         Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
262     }
263     if (HasErrors) {
264       Errors = llvm::joinErrors(
265           std::move(Errors),
266           llvm::createStringError(llvm::inconvertibleErrorCode(),
267                                   "define outline: Can't move out of line as "
268                                   "function has a macro `virtual` specifier."));
269     }
270   }
271 
272   if (Errors)
273     return std::move(Errors);
274   return getFunctionSourceAfterReplacements(FD, DeclarationCleanups);
275 }
276 
277 struct InsertionPoint {
278   std::string EnclosingNamespace;
279   size_t Offset;
280 };
281 // Returns the most natural insertion point for \p QualifiedName in \p Contents.
282 // This currently cares about only the namespace proximity, but in feature it
283 // should also try to follow ordering of declarations. For example, if decls
284 // come in order `foo, bar, baz` then this function should return some point
285 // between foo and baz for inserting bar.
286 llvm::Expected<InsertionPoint>
getInsertionPoint(llvm::StringRef Contents,llvm::StringRef QualifiedName,const format::FormatStyle & Style)287 getInsertionPoint(llvm::StringRef Contents, llvm::StringRef QualifiedName,
288                   const format::FormatStyle &Style) {
289   auto Region = getEligiblePoints(Contents, QualifiedName, Style);
290 
291   assert(!Region.EligiblePoints.empty());
292   // FIXME: This selection can be made smarter by looking at the definition
293   // locations for adjacent decls to Source. Unfortunately psudeo parsing in
294   // getEligibleRegions only knows about namespace begin/end events so we
295   // can't match function start/end positions yet.
296   auto Offset = positionToOffset(Contents, Region.EligiblePoints.back());
297   if (!Offset)
298     return Offset.takeError();
299   return InsertionPoint{Region.EnclosingNamespace, *Offset};
300 }
301 
302 // Returns the range that should be deleted from declaration, which always
303 // contains function body. In addition to that it might contain constructor
304 // initializers.
getDeletionRange(const FunctionDecl * FD,const syntax::TokenBuffer & TokBuf)305 SourceRange getDeletionRange(const FunctionDecl *FD,
306                              const syntax::TokenBuffer &TokBuf) {
307   auto DeletionRange = FD->getBody()->getSourceRange();
308   if (auto *CD = llvm::dyn_cast<CXXConstructorDecl>(FD)) {
309     // AST doesn't contain the location for ":" in ctor initializers. Therefore
310     // we find it by finding the first ":" before the first ctor initializer.
311     SourceLocation InitStart;
312     // Find the first initializer.
313     for (const auto *CInit : CD->inits()) {
314       // SourceOrder is -1 for implicit initializers.
315       if (CInit->getSourceOrder() != 0)
316         continue;
317       InitStart = CInit->getSourceLocation();
318       break;
319     }
320     if (InitStart.isValid()) {
321       auto Toks = TokBuf.expandedTokens(CD->getSourceRange());
322       // Drop any tokens after the initializer.
323       Toks = Toks.take_while([&TokBuf, &InitStart](const syntax::Token &Tok) {
324         return TokBuf.sourceManager().isBeforeInTranslationUnit(Tok.location(),
325                                                                 InitStart);
326       });
327       // Look for the first colon.
328       auto Tok =
329           llvm::find_if(llvm::reverse(Toks), [](const syntax::Token &Tok) {
330             return Tok.kind() == tok::colon;
331           });
332       assert(Tok != Toks.rend());
333       DeletionRange.setBegin(Tok->location());
334     }
335   }
336   return DeletionRange;
337 }
338 
339 /// Moves definition of a function/method to an appropriate implementation file.
340 ///
341 /// Before:
342 /// a.h
343 ///   void foo() { return; }
344 /// a.cc
345 ///   #include "a.h"
346 ///
347 /// ----------------
348 ///
349 /// After:
350 /// a.h
351 ///   void foo();
352 /// a.cc
353 ///   #include "a.h"
354 ///   void foo() { return; }
355 class DefineOutline : public Tweak {
356 public:
357   const char *id() const override;
358 
hidden() const359   bool hidden() const override { return true; }
intent() const360   Intent intent() const override { return Intent::Refactor; }
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 in templated classes, as it is hard to spell the class name, i.e
380     // if the template parameter is unnamed.
381     if (auto *MD = llvm::dyn_cast<CXXMethodDecl>(Source)) {
382       if (MD->getParent()->isTemplated())
383         return false;
384     }
385 
386     // Note that we don't check whether an implementation file exists or not in
387     // the prepare, since performing disk IO on each prepare request might be
388     // expensive.
389     return true;
390   }
391 
apply(const Selection & Sel)392   Expected<Effect> apply(const Selection &Sel) override {
393     const SourceManager &SM = Sel.AST->getSourceManager();
394     auto MainFileName =
395         getCanonicalPath(SM.getFileEntryForID(SM.getMainFileID()), SM);
396     if (!MainFileName)
397       return llvm::createStringError(
398           llvm::inconvertibleErrorCode(),
399           "Couldn't get absolute path for mainfile.");
400 
401     auto CCFile = getSourceFile(*MainFileName, Sel);
402     if (!CCFile)
403       return llvm::createStringError(
404           llvm::inconvertibleErrorCode(),
405           "Couldn't find a suitable implementation file.");
406 
407     auto &FS =
408         Sel.AST->getSourceManager().getFileManager().getVirtualFileSystem();
409     auto Buffer = FS.getBufferForFile(*CCFile);
410     // FIXME: Maybe we should consider creating the implementation file if it
411     // doesn't exist?
412     if (!Buffer)
413       return llvm::createStringError(Buffer.getError(),
414                                      Buffer.getError().message());
415     auto Contents = Buffer->get()->getBuffer();
416     auto InsertionPoint =
417         getInsertionPoint(Contents, Source->getQualifiedNameAsString(),
418                           getFormatStyleForFile(*CCFile, Contents, &FS));
419     if (!InsertionPoint)
420       return InsertionPoint.takeError();
421 
422     auto FuncDef = getFunctionSourceCode(
423         Source, InsertionPoint->EnclosingNamespace, Sel.AST->getTokens());
424     if (!FuncDef)
425       return FuncDef.takeError();
426 
427     SourceManagerForFile SMFF(*CCFile, Contents);
428     const tooling::Replacement InsertFunctionDef(
429         *CCFile, InsertionPoint->Offset, 0, *FuncDef);
430     auto Effect = Effect::mainFileEdit(
431         SMFF.get(), tooling::Replacements(InsertFunctionDef));
432     if (!Effect)
433       return Effect.takeError();
434 
435     // FIXME: We should also get rid of inline qualifier.
436     const tooling::Replacement DeleteFuncBody(
437         Sel.AST->getSourceManager(),
438         CharSourceRange::getTokenRange(*toHalfOpenFileRange(
439             SM, Sel.AST->getLangOpts(),
440             getDeletionRange(Source, Sel.AST->getTokens()))),
441         ";");
442     auto HeaderFE = Effect::fileEdit(SM, SM.getMainFileID(),
443                                      tooling::Replacements(DeleteFuncBody));
444     if (!HeaderFE)
445       return HeaderFE.takeError();
446 
447     Effect->ApplyEdits.try_emplace(HeaderFE->first,
448                                    std::move(HeaderFE->second));
449     return std::move(*Effect);
450   }
451 
452 private:
453   const FunctionDecl *Source = nullptr;
454 };
455 
456 REGISTER_TWEAK(DefineOutline)
457 
458 } // namespace
459 } // namespace clangd
460 } // namespace clang
461