1 //===--- ClangdServer.cpp - Main clangd server code --------------*- 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 "ClangdServer.h"
11 #include "CodeComplete.h"
12 #include "Headers.h"
13 #include "SourceCode.h"
14 #include "XRefs.h"
15 #include "index/Merge.h"
16 #include "clang/Format/Format.h"
17 #include "clang/Frontend/CompilerInstance.h"
18 #include "clang/Frontend/CompilerInvocation.h"
19 #include "clang/Tooling/CompilationDatabase.h"
20 #include "clang/Tooling/Refactoring/RefactoringResultConsumer.h"
21 #include "clang/Tooling/Refactoring/Rename/RenamingAction.h"
22 #include "llvm/ADT/ArrayRef.h"
23 #include "llvm/ADT/ScopeExit.h"
24 #include "llvm/Support/Errc.h"
25 #include "llvm/Support/FileSystem.h"
26 #include "llvm/Support/Path.h"
27 #include "llvm/Support/raw_ostream.h"
28 #include <future>
29 
30 using namespace clang;
31 using namespace clang::clangd;
32 
33 namespace {
34 
35 void ignoreError(llvm::Error Err) {
36   handleAllErrors(std::move(Err), [](const llvm::ErrorInfoBase &) {});
37 }
38 
39 std::string getStandardResourceDir() {
40   static int Dummy; // Just an address in this process.
41   return CompilerInvocation::GetResourcesPath("clangd", (void *)&Dummy);
42 }
43 
44 class RefactoringResultCollector final
45     : public tooling::RefactoringResultConsumer {
46 public:
47   void handleError(llvm::Error Err) override {
48     assert(!Result.hasValue());
49     // FIXME: figure out a way to return better message for DiagnosticError.
50     // clangd uses llvm::toString to convert the Err to string, however, for
51     // DiagnosticError, only "clang diagnostic" will be generated.
52     Result = std::move(Err);
53   }
54 
55   // Using the handle(SymbolOccurrences) from parent class.
56   using tooling::RefactoringResultConsumer::handle;
57 
58   void handle(tooling::AtomicChanges SourceReplacements) override {
59     assert(!Result.hasValue());
60     Result = std::move(SourceReplacements);
61   }
62 
63   Optional<Expected<tooling::AtomicChanges>> Result;
64 };
65 
66 } // namespace
67 
68 Tagged<IntrusiveRefCntPtr<vfs::FileSystem>>
69 RealFileSystemProvider::getTaggedFileSystem(PathRef File) {
70   return make_tagged(vfs::getRealFileSystem(), VFSTag());
71 }
72 
73 ClangdServer::ClangdServer(GlobalCompilationDatabase &CDB,
74                            DiagnosticsConsumer &DiagConsumer,
75                            FileSystemProvider &FSProvider,
76                            unsigned AsyncThreadsCount,
77                            bool StorePreamblesInMemory,
78                            bool BuildDynamicSymbolIndex, SymbolIndex *StaticIdx,
79                            llvm::Optional<StringRef> ResourceDir)
80     : CompileArgs(CDB,
81                   ResourceDir ? ResourceDir->str() : getStandardResourceDir()),
82       DiagConsumer(DiagConsumer), FSProvider(FSProvider),
83       FileIdx(BuildDynamicSymbolIndex ? new FileIndex() : nullptr),
84       PCHs(std::make_shared<PCHContainerOperations>()),
85       // Pass a callback into `WorkScheduler` to extract symbols from a newly
86       // parsed file and rebuild the file index synchronously each time an AST
87       // is parsed.
88       // FIXME(ioeric): this can be slow and we may be able to index on less
89       // critical paths.
90       WorkScheduler(AsyncThreadsCount, StorePreamblesInMemory,
91                     FileIdx
92                         ? [this](PathRef Path,
93                                  ParsedAST *AST) { FileIdx->update(Path, AST); }
94                         : ASTParsedCallback()) {
95   if (FileIdx && StaticIdx) {
96     MergedIndex = mergeIndex(FileIdx.get(), StaticIdx);
97     Index = MergedIndex.get();
98   } else if (FileIdx)
99     Index = FileIdx.get();
100   else if (StaticIdx)
101     Index = StaticIdx;
102   else
103     Index = nullptr;
104 }
105 
106 void ClangdServer::setRootPath(PathRef RootPath) {
107   std::string NewRootPath = llvm::sys::path::convert_to_slash(
108       RootPath, llvm::sys::path::Style::posix);
109   if (llvm::sys::fs::is_directory(NewRootPath))
110     this->RootPath = NewRootPath;
111 }
112 
113 void ClangdServer::addDocument(PathRef File, StringRef Contents,
114                                WantDiagnostics WantDiags) {
115   DocVersion Version = DraftMgr.updateDraft(File, Contents);
116   auto TaggedFS = FSProvider.getTaggedFileSystem(File);
117   scheduleReparseAndDiags(File, VersionedDraft{Version, Contents.str()},
118                           WantDiags, std::move(TaggedFS));
119 }
120 
121 void ClangdServer::removeDocument(PathRef File) {
122   DraftMgr.removeDraft(File);
123   CompileArgs.invalidate(File);
124   WorkScheduler.remove(File);
125 }
126 
127 void ClangdServer::forceReparse(PathRef File) {
128   auto FileContents = DraftMgr.getDraft(File);
129   assert(FileContents.Draft &&
130          "forceReparse() was called for non-added document");
131 
132   // forceReparse promises to request new compilation flags from CDB, so we
133   // remove any cahced flags.
134   CompileArgs.invalidate(File);
135 
136   auto TaggedFS = FSProvider.getTaggedFileSystem(File);
137   scheduleReparseAndDiags(File, std::move(FileContents), WantDiagnostics::Yes,
138                           std::move(TaggedFS));
139 }
140 
141 void ClangdServer::codeComplete(
142     PathRef File, Position Pos, const clangd::CodeCompleteOptions &Opts,
143     UniqueFunction<void(Tagged<CompletionList>)> Callback,
144     llvm::Optional<StringRef> OverridenContents,
145     IntrusiveRefCntPtr<vfs::FileSystem> *UsedFS) {
146   using CallbackType = UniqueFunction<void(Tagged<CompletionList>)>;
147 
148   auto TaggedFS = FSProvider.getTaggedFileSystem(File);
149   if (UsedFS)
150     *UsedFS = TaggedFS.Value;
151 
152   // Copy completion options for passing them to async task handler.
153   auto CodeCompleteOpts = Opts;
154   if (!CodeCompleteOpts.Index) // Respect overridden index.
155     CodeCompleteOpts.Index = Index;
156 
157   std::string Contents;
158   if (OverridenContents) {
159     Contents = OverridenContents->str();
160   } else {
161     VersionedDraft Latest = DraftMgr.getDraft(File);
162     assert(Latest.Draft && "codeComplete called for non-added document");
163     Contents = *Latest.Draft;
164   }
165 
166   // Copy PCHs to avoid accessing this->PCHs concurrently
167   std::shared_ptr<PCHContainerOperations> PCHs = this->PCHs;
168   auto Task = [PCHs, Pos, TaggedFS, CodeCompleteOpts](
169                   std::string Contents, Path File, CallbackType Callback,
170                   llvm::Expected<InputsAndPreamble> IP) {
171     assert(IP && "error when trying to read preamble for codeComplete");
172     auto PreambleData = IP->Preamble;
173     auto &Command = IP->Inputs.CompileCommand;
174 
175     // FIXME(ibiryukov): even if Preamble is non-null, we may want to check
176     // both the old and the new version in case only one of them matches.
177     CompletionList Result = clangd::codeComplete(
178         File, Command, PreambleData ? &PreambleData->Preamble : nullptr,
179         Contents, Pos, TaggedFS.Value, PCHs, CodeCompleteOpts);
180 
181     Callback(make_tagged(std::move(Result), std::move(TaggedFS.Tag)));
182   };
183 
184   WorkScheduler.runWithPreamble(
185       "CodeComplete", File,
186       Bind(Task, std::move(Contents), File.str(), std::move(Callback)));
187 }
188 
189 void ClangdServer::signatureHelp(
190     PathRef File, Position Pos,
191     UniqueFunction<void(llvm::Expected<Tagged<SignatureHelp>>)> Callback,
192     llvm::Optional<StringRef> OverridenContents,
193     IntrusiveRefCntPtr<vfs::FileSystem> *UsedFS) {
194   auto TaggedFS = FSProvider.getTaggedFileSystem(File);
195   if (UsedFS)
196     *UsedFS = TaggedFS.Value;
197 
198   std::string Contents;
199   if (OverridenContents) {
200     Contents = OverridenContents->str();
201   } else {
202     VersionedDraft Latest = DraftMgr.getDraft(File);
203     if (!Latest.Draft)
204       return Callback(llvm::make_error<llvm::StringError>(
205           "signatureHelp is called for non-added document",
206           llvm::errc::invalid_argument));
207     Contents = std::move(*Latest.Draft);
208   }
209 
210   auto PCHs = this->PCHs;
211   auto Action = [Contents, Pos, TaggedFS,
212                  PCHs](Path File, decltype(Callback) Callback,
213                        llvm::Expected<InputsAndPreamble> IP) {
214     if (!IP)
215       return Callback(IP.takeError());
216 
217     auto PreambleData = IP->Preamble;
218     auto &Command = IP->Inputs.CompileCommand;
219     Callback(make_tagged(
220         clangd::signatureHelp(File, Command,
221                               PreambleData ? &PreambleData->Preamble : nullptr,
222                               Contents, Pos, TaggedFS.Value, PCHs),
223         TaggedFS.Tag));
224   };
225 
226   WorkScheduler.runWithPreamble("SignatureHelp", File,
227                                 Bind(Action, File.str(), std::move(Callback)));
228 }
229 
230 llvm::Expected<tooling::Replacements>
231 ClangdServer::formatRange(StringRef Code, PathRef File, Range Rng) {
232   size_t Begin = positionToOffset(Code, Rng.start);
233   size_t Len = positionToOffset(Code, Rng.end) - Begin;
234   return formatCode(Code, File, {tooling::Range(Begin, Len)});
235 }
236 
237 llvm::Expected<tooling::Replacements> ClangdServer::formatFile(StringRef Code,
238                                                                PathRef File) {
239   // Format everything.
240   return formatCode(Code, File, {tooling::Range(0, Code.size())});
241 }
242 
243 llvm::Expected<tooling::Replacements>
244 ClangdServer::formatOnType(StringRef Code, PathRef File, Position Pos) {
245   // Look for the previous opening brace from the character position and
246   // format starting from there.
247   size_t CursorPos = positionToOffset(Code, Pos);
248   size_t PreviousLBracePos = StringRef(Code).find_last_of('{', CursorPos);
249   if (PreviousLBracePos == StringRef::npos)
250     PreviousLBracePos = CursorPos;
251   size_t Len = CursorPos - PreviousLBracePos;
252 
253   return formatCode(Code, File, {tooling::Range(PreviousLBracePos, Len)});
254 }
255 
256 void ClangdServer::rename(
257     PathRef File, Position Pos, llvm::StringRef NewName,
258     UniqueFunction<void(Expected<std::vector<tooling::Replacement>>)>
259         Callback) {
260   auto Action = [Pos](Path File, std::string NewName,
261                       decltype(Callback) Callback,
262                       Expected<InputsAndAST> InpAST) {
263     if (!InpAST)
264       return Callback(InpAST.takeError());
265     auto &AST = InpAST->AST;
266 
267     RefactoringResultCollector ResultCollector;
268     const SourceManager &SourceMgr = AST.getASTContext().getSourceManager();
269     const FileEntry *FE =
270         SourceMgr.getFileEntryForID(SourceMgr.getMainFileID());
271     if (!FE)
272       return Callback(llvm::make_error<llvm::StringError>(
273           "rename called for non-added document",
274           llvm::errc::invalid_argument));
275     SourceLocation SourceLocationBeg =
276         clangd::getBeginningOfIdentifier(AST, Pos, FE);
277     tooling::RefactoringRuleContext Context(
278         AST.getASTContext().getSourceManager());
279     Context.setASTContext(AST.getASTContext());
280     auto Rename = clang::tooling::RenameOccurrences::initiate(
281         Context, SourceRange(SourceLocationBeg), NewName);
282     if (!Rename)
283       return Callback(Rename.takeError());
284 
285     Rename->invoke(ResultCollector, Context);
286 
287     assert(ResultCollector.Result.hasValue());
288     if (!ResultCollector.Result.getValue())
289       return Callback(ResultCollector.Result->takeError());
290 
291     std::vector<tooling::Replacement> Replacements;
292     for (const tooling::AtomicChange &Change : ResultCollector.Result->get()) {
293       tooling::Replacements ChangeReps = Change.getReplacements();
294       for (const auto &Rep : ChangeReps) {
295         // FIXME: Right now we only support renaming the main file, so we
296         // drop replacements not for the main file. In the future, we might
297         // consider to support:
298         //   * rename in any included header
299         //   * rename only in the "main" header
300         //   * provide an error if there are symbols we won't rename (e.g.
301         //     std::vector)
302         //   * rename globally in project
303         //   * rename in open files
304         if (Rep.getFilePath() == File)
305           Replacements.push_back(Rep);
306       }
307     }
308     return Callback(Replacements);
309   };
310 
311   WorkScheduler.runWithAST(
312       "Rename", File,
313       Bind(Action, File.str(), NewName.str(), std::move(Callback)));
314 }
315 
316 Expected<tooling::Replacements>
317 ClangdServer::insertInclude(PathRef File, StringRef Code,
318                             llvm::StringRef Header) {
319   std::string ToInclude;
320   if (Header.startswith("<") || Header.startswith("\"")) {
321     ToInclude = Header;
322   } else {
323     auto U = URI::parse(Header);
324     if (!U)
325       return U.takeError();
326     auto Resolved = URI::resolve(*U, /*HintPath=*/File);
327     if (!Resolved)
328       return Resolved.takeError();
329 
330     tooling::CompileCommand CompileCommand =
331         CompileArgs.getCompileCommand(File);
332     auto Include =
333         calculateIncludePath(File, Code, *Resolved, CompileCommand,
334                              FSProvider.getTaggedFileSystem(File).Value);
335     if (!Include)
336       return Include.takeError();
337     if (Include->empty())
338       return tooling::Replacements();
339     ToInclude = std::move(*Include);
340   }
341 
342   auto Style = format::getStyle("file", File, "llvm");
343   if (!Style) {
344     llvm::consumeError(Style.takeError());
345     // FIXME(ioeric): needs more consistent style support in clangd server.
346     Style = format::getLLVMStyle();
347   }
348   // Replacement with offset UINT_MAX and length 0 will be treated as include
349   // insertion.
350   tooling::Replacement R(File, /*Offset=*/UINT_MAX, 0, "#include " + ToInclude);
351   return format::cleanupAroundReplacements(Code, tooling::Replacements(R),
352                                            *Style);
353 }
354 
355 llvm::Optional<std::string> ClangdServer::getDocument(PathRef File) {
356   auto Latest = DraftMgr.getDraft(File);
357   if (!Latest.Draft)
358     return llvm::None;
359   return std::move(*Latest.Draft);
360 }
361 
362 void ClangdServer::dumpAST(PathRef File,
363                            UniqueFunction<void(std::string)> Callback) {
364   auto Action = [](decltype(Callback) Callback,
365                    llvm::Expected<InputsAndAST> InpAST) {
366     if (!InpAST) {
367       ignoreError(InpAST.takeError());
368       return Callback("<no-ast>");
369     }
370     std::string Result;
371 
372     llvm::raw_string_ostream ResultOS(Result);
373     clangd::dumpAST(InpAST->AST, ResultOS);
374     ResultOS.flush();
375 
376     Callback(Result);
377   };
378 
379   WorkScheduler.runWithAST("DumpAST", File, Bind(Action, std::move(Callback)));
380 }
381 
382 void ClangdServer::findDefinitions(
383     PathRef File, Position Pos,
384     UniqueFunction<void(llvm::Expected<Tagged<std::vector<Location>>>)>
385         Callback) {
386   auto TaggedFS = FSProvider.getTaggedFileSystem(File);
387   auto Action = [Pos, TaggedFS](decltype(Callback) Callback,
388                                 llvm::Expected<InputsAndAST> InpAST) {
389     if (!InpAST)
390       return Callback(InpAST.takeError());
391     auto Result = clangd::findDefinitions(InpAST->AST, Pos);
392     Callback(make_tagged(std::move(Result), TaggedFS.Tag));
393   };
394 
395   WorkScheduler.runWithAST("Definitions", File,
396                            Bind(Action, std::move(Callback)));
397 }
398 
399 llvm::Optional<Path> ClangdServer::switchSourceHeader(PathRef Path) {
400 
401   StringRef SourceExtensions[] = {".cpp", ".c", ".cc", ".cxx",
402                                   ".c++", ".m", ".mm"};
403   StringRef HeaderExtensions[] = {".h", ".hh", ".hpp", ".hxx", ".inc"};
404 
405   StringRef PathExt = llvm::sys::path::extension(Path);
406 
407   // Lookup in a list of known extensions.
408   auto SourceIter =
409       std::find_if(std::begin(SourceExtensions), std::end(SourceExtensions),
410                    [&PathExt](PathRef SourceExt) {
411                      return SourceExt.equals_lower(PathExt);
412                    });
413   bool IsSource = SourceIter != std::end(SourceExtensions);
414 
415   auto HeaderIter =
416       std::find_if(std::begin(HeaderExtensions), std::end(HeaderExtensions),
417                    [&PathExt](PathRef HeaderExt) {
418                      return HeaderExt.equals_lower(PathExt);
419                    });
420 
421   bool IsHeader = HeaderIter != std::end(HeaderExtensions);
422 
423   // We can only switch between extensions known extensions.
424   if (!IsSource && !IsHeader)
425     return llvm::None;
426 
427   // Array to lookup extensions for the switch. An opposite of where original
428   // extension was found.
429   ArrayRef<StringRef> NewExts;
430   if (IsSource)
431     NewExts = HeaderExtensions;
432   else
433     NewExts = SourceExtensions;
434 
435   // Storage for the new path.
436   SmallString<128> NewPath = StringRef(Path);
437 
438   // Instance of vfs::FileSystem, used for file existence checks.
439   auto FS = FSProvider.getTaggedFileSystem(Path).Value;
440 
441   // Loop through switched extension candidates.
442   for (StringRef NewExt : NewExts) {
443     llvm::sys::path::replace_extension(NewPath, NewExt);
444     if (FS->exists(NewPath))
445       return NewPath.str().str(); // First str() to convert from SmallString to
446                                   // StringRef, second to convert from StringRef
447                                   // to std::string
448 
449     // Also check NewExt in upper-case, just in case.
450     llvm::sys::path::replace_extension(NewPath, NewExt.upper());
451     if (FS->exists(NewPath))
452       return NewPath.str().str();
453   }
454 
455   return llvm::None;
456 }
457 
458 llvm::Expected<tooling::Replacements>
459 ClangdServer::formatCode(llvm::StringRef Code, PathRef File,
460                          ArrayRef<tooling::Range> Ranges) {
461   // Call clang-format.
462   auto TaggedFS = FSProvider.getTaggedFileSystem(File);
463   auto StyleOrError =
464       format::getStyle("file", File, "LLVM", Code, TaggedFS.Value.get());
465   if (!StyleOrError) {
466     return StyleOrError.takeError();
467   } else {
468     return format::reformat(StyleOrError.get(), Code, Ranges, File);
469   }
470 }
471 
472 void ClangdServer::findDocumentHighlights(
473     PathRef File, Position Pos,
474     UniqueFunction<void(llvm::Expected<Tagged<std::vector<DocumentHighlight>>>)>
475         Callback) {
476   auto FileContents = DraftMgr.getDraft(File);
477   if (!FileContents.Draft)
478     return Callback(llvm::make_error<llvm::StringError>(
479         "findDocumentHighlights called on non-added file",
480         llvm::errc::invalid_argument));
481 
482   auto TaggedFS = FSProvider.getTaggedFileSystem(File);
483 
484   auto Action = [TaggedFS, Pos](decltype(Callback) Callback,
485                                 llvm::Expected<InputsAndAST> InpAST) {
486     if (!InpAST)
487       return Callback(InpAST.takeError());
488     auto Result = clangd::findDocumentHighlights(InpAST->AST, Pos);
489     Callback(make_tagged(std::move(Result), TaggedFS.Tag));
490   };
491 
492   WorkScheduler.runWithAST("Highlights", File,
493                            Bind(Action, std::move(Callback)));
494 }
495 
496 void ClangdServer::findHover(
497     PathRef File, Position Pos,
498     UniqueFunction<void(llvm::Expected<Tagged<Hover>>)> Callback) {
499   Hover FinalHover;
500   auto FileContents = DraftMgr.getDraft(File);
501   if (!FileContents.Draft)
502     return Callback(llvm::make_error<llvm::StringError>(
503         "findHover called on non-added file", llvm::errc::invalid_argument));
504 
505   auto TaggedFS = FSProvider.getTaggedFileSystem(File);
506 
507   auto Action = [Pos, TaggedFS](decltype(Callback) Callback,
508                                 llvm::Expected<InputsAndAST> InpAST) {
509     if (!InpAST)
510       return Callback(InpAST.takeError());
511 
512     Hover Result = clangd::getHover(InpAST->AST, Pos);
513     Callback(make_tagged(std::move(Result), TaggedFS.Tag));
514   };
515 
516   WorkScheduler.runWithAST("Hover", File, Bind(Action, std::move(Callback)));
517 }
518 
519 void ClangdServer::scheduleReparseAndDiags(
520     PathRef File, VersionedDraft Contents, WantDiagnostics WantDiags,
521     Tagged<IntrusiveRefCntPtr<vfs::FileSystem>> TaggedFS) {
522   tooling::CompileCommand Command = CompileArgs.getCompileCommand(File);
523 
524   DocVersion Version = Contents.Version;
525   Path FileStr = File.str();
526   VFSTag Tag = std::move(TaggedFS.Tag);
527 
528   auto Callback = [this, Version, FileStr,
529                    Tag](std::vector<DiagWithFixIts> Diags) {
530     // We need to serialize access to resulting diagnostics to avoid calling
531     // `onDiagnosticsReady` in the wrong order.
532     std::lock_guard<std::mutex> DiagsLock(DiagnosticsMutex);
533     DocVersion &LastReportedDiagsVersion = ReportedDiagnosticVersions[FileStr];
534     // FIXME(ibiryukov): get rid of '<' comparison here. In the current
535     // implementation diagnostics will not be reported after version counters'
536     // overflow. This should not happen in practice, since DocVersion is a
537     // 64-bit unsigned integer.
538     if (Version < LastReportedDiagsVersion)
539       return;
540     LastReportedDiagsVersion = Version;
541 
542     DiagConsumer.onDiagnosticsReady(
543         FileStr, make_tagged(std::move(Diags), std::move(Tag)));
544   };
545 
546   WorkScheduler.update(File,
547                        ParseInputs{std::move(Command),
548                                    std::move(TaggedFS.Value),
549                                    std::move(*Contents.Draft)},
550                        WantDiags, std::move(Callback));
551 }
552 
553 void ClangdServer::reparseOpenedFiles() {
554   for (const Path &FilePath : DraftMgr.getActiveFiles())
555     forceReparse(FilePath);
556 }
557 
558 void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
559   // FIXME: Do nothing for now. This will be used for indexing and potentially
560   // invalidating other caches.
561 }
562 
563 std::vector<std::pair<Path, std::size_t>>
564 ClangdServer::getUsedBytesPerFile() const {
565   return WorkScheduler.getUsedBytesPerFile();
566 }
567 
568 LLVM_NODISCARD bool
569 ClangdServer::blockUntilIdleForTest(llvm::Optional<double> TimeoutSeconds) {
570   return WorkScheduler.blockUntilIdle(timeoutSeconds(TimeoutSeconds));
571 }
572