Nathan Ridge 3093d731df
[clangd] Avoid libFormat's objective-c guessing heuristic where possible (#84133)
This avoids a known libFormat bug where the heuristic can OOM on certain
large files (particularly single-header libraries such as miniaudio.h).

The OOM will still happen on affected files if you actually try to
format them (this is harder to avoid since the underlyting issue affects
the actual formatting logic, not just the language-guessing heuristic),
but at least it's avoided during non-modifying operations like hover,
and modifying operations that do local formatting like code completion.

Fixes https://github.com/clangd/clangd/issues/719
Fixes https://github.com/clangd/clangd/issues/1384
Fixes https://github.com/llvm/llvm-project/issues/70945
2024-03-11 04:16:45 -04:00

1160 lines
44 KiB
C++

//===--- ClangdServer.cpp - Main clangd server code --------------*- C++-*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===-------------------------------------------------------------------===//
#include "ClangdServer.h"
#include "CodeComplete.h"
#include "Config.h"
#include "Diagnostics.h"
#include "DumpAST.h"
#include "FindSymbols.h"
#include "Format.h"
#include "HeaderSourceSwitch.h"
#include "InlayHints.h"
#include "ParsedAST.h"
#include "Preamble.h"
#include "Protocol.h"
#include "SemanticHighlighting.h"
#include "SemanticSelection.h"
#include "SourceCode.h"
#include "TUScheduler.h"
#include "XRefs.h"
#include "clang-include-cleaner/Record.h"
#include "index/FileIndex.h"
#include "index/Merge.h"
#include "index/StdLib.h"
#include "refactor/Rename.h"
#include "refactor/Tweak.h"
#include "support/Cancellation.h"
#include "support/Logger.h"
#include "support/MemoryTree.h"
#include "support/ThreadsafeFS.h"
#include "support/Trace.h"
#include "clang/Basic/Stack.h"
#include "clang/Format/Format.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Tooling/CompilationDatabase.h"
#include "clang/Tooling/Core/Replacement.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <chrono>
#include <future>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
namespace clang {
namespace clangd {
namespace {
// Tracks number of times a tweak has been offered.
static constexpr trace::Metric TweakAvailable(
"tweak_available", trace::Metric::Counter, "tweak_id");
// Update the FileIndex with new ASTs and plumb the diagnostics responses.
struct UpdateIndexCallbacks : public ParsingCallbacks {
UpdateIndexCallbacks(FileIndex *FIndex,
ClangdServer::Callbacks *ServerCallbacks,
const ThreadsafeFS &TFS, AsyncTaskRunner *Tasks,
bool CollectInactiveRegions)
: FIndex(FIndex), ServerCallbacks(ServerCallbacks), TFS(TFS),
Stdlib{std::make_shared<StdLibSet>()}, Tasks(Tasks),
CollectInactiveRegions(CollectInactiveRegions) {}
void onPreambleAST(
PathRef Path, llvm::StringRef Version, CapturedASTCtx ASTCtx,
std::shared_ptr<const include_cleaner::PragmaIncludes> PI) override {
if (!FIndex)
return;
auto &PP = ASTCtx.getPreprocessor();
auto &CI = ASTCtx.getCompilerInvocation();
if (auto Loc = Stdlib->add(CI.getLangOpts(), PP.getHeaderSearchInfo()))
indexStdlib(CI, std::move(*Loc));
// FIndex outlives the UpdateIndexCallbacks.
auto Task = [FIndex(FIndex), Path(Path.str()), Version(Version.str()),
ASTCtx(std::move(ASTCtx)), PI(std::move(PI))]() mutable {
trace::Span Tracer("PreambleIndexing");
FIndex->updatePreamble(Path, Version, ASTCtx.getASTContext(),
ASTCtx.getPreprocessor(), *PI);
};
if (Tasks) {
Tasks->runAsync("Preamble indexing for:" + Path + Version,
std::move(Task));
} else
Task();
}
void indexStdlib(const CompilerInvocation &CI, StdLibLocation Loc) {
// This task is owned by Tasks, which outlives the TUScheduler and
// therefore the UpdateIndexCallbacks.
// We must be careful that the references we capture outlive TUScheduler.
auto Task = [LO(CI.getLangOpts()), Loc(std::move(Loc)),
CI(std::make_unique<CompilerInvocation>(CI)),
// External values that outlive ClangdServer
TFS(&TFS),
// Index outlives TUScheduler (declared first)
FIndex(FIndex),
// shared_ptr extends lifetime
Stdlib(Stdlib)]() mutable {
clang::noteBottomOfStack();
IndexFileIn IF;
IF.Symbols = indexStandardLibrary(std::move(CI), Loc, *TFS);
if (Stdlib->isBest(LO))
FIndex->updatePreamble(std::move(IF));
};
if (Tasks)
// This doesn't have a semaphore to enforce -j, but it's rare.
Tasks->runAsync("IndexStdlib", std::move(Task));
else
Task();
}
void onMainAST(PathRef Path, ParsedAST &AST, PublishFn Publish) override {
if (FIndex)
FIndex->updateMain(Path, AST);
if (ServerCallbacks)
Publish([&]() {
ServerCallbacks->onDiagnosticsReady(Path, AST.version(),
AST.getDiagnostics());
if (CollectInactiveRegions) {
ServerCallbacks->onInactiveRegionsReady(Path,
getInactiveRegions(AST));
}
});
}
void onFailedAST(PathRef Path, llvm::StringRef Version,
std::vector<Diag> Diags, PublishFn Publish) override {
if (ServerCallbacks)
Publish(
[&]() { ServerCallbacks->onDiagnosticsReady(Path, Version, Diags); });
}
void onFileUpdated(PathRef File, const TUStatus &Status) override {
if (ServerCallbacks)
ServerCallbacks->onFileUpdated(File, Status);
}
void onPreamblePublished(PathRef File) override {
if (ServerCallbacks)
ServerCallbacks->onSemanticsMaybeChanged(File);
}
private:
FileIndex *FIndex;
ClangdServer::Callbacks *ServerCallbacks;
const ThreadsafeFS &TFS;
std::shared_ptr<StdLibSet> Stdlib;
AsyncTaskRunner *Tasks;
bool CollectInactiveRegions;
};
class DraftStoreFS : public ThreadsafeFS {
public:
DraftStoreFS(const ThreadsafeFS &Base, const DraftStore &Drafts)
: Base(Base), DirtyFiles(Drafts) {}
private:
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> viewImpl() const override {
auto OFS = llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(
Base.view(std::nullopt));
OFS->pushOverlay(DirtyFiles.asVFS());
return OFS;
}
const ThreadsafeFS &Base;
const DraftStore &DirtyFiles;
};
} // namespace
ClangdServer::Options ClangdServer::optsForTest() {
ClangdServer::Options Opts;
Opts.UpdateDebounce = DebouncePolicy::fixed(/*zero*/ {});
Opts.StorePreamblesInMemory = true;
Opts.AsyncThreadsCount = 4; // Consistent!
return Opts;
}
ClangdServer::Options::operator TUScheduler::Options() const {
TUScheduler::Options Opts;
Opts.AsyncThreadsCount = AsyncThreadsCount;
Opts.RetentionPolicy = RetentionPolicy;
Opts.StorePreamblesInMemory = StorePreamblesInMemory;
Opts.UpdateDebounce = UpdateDebounce;
Opts.ContextProvider = ContextProvider;
Opts.PreambleThrottler = PreambleThrottler;
return Opts;
}
ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB,
const ThreadsafeFS &TFS, const Options &Opts,
Callbacks *Callbacks)
: FeatureModules(Opts.FeatureModules), CDB(CDB), TFS(TFS),
DynamicIdx(Opts.BuildDynamicSymbolIndex ? new FileIndex() : nullptr),
ClangTidyProvider(Opts.ClangTidyProvider),
UseDirtyHeaders(Opts.UseDirtyHeaders),
LineFoldingOnly(Opts.LineFoldingOnly),
PreambleParseForwardingFunctions(Opts.PreambleParseForwardingFunctions),
ImportInsertions(Opts.ImportInsertions),
PublishInactiveRegions(Opts.PublishInactiveRegions),
WorkspaceRoot(Opts.WorkspaceRoot),
Transient(Opts.ImplicitCancellation ? TUScheduler::InvalidateOnUpdate
: TUScheduler::NoInvalidation),
DirtyFS(std::make_unique<DraftStoreFS>(TFS, DraftMgr)) {
if (Opts.AsyncThreadsCount != 0)
IndexTasks.emplace();
// Pass a callback into `WorkScheduler` to extract symbols from a newly
// parsed file and rebuild the file index synchronously each time an AST
// is parsed.
WorkScheduler.emplace(CDB, TUScheduler::Options(Opts),
std::make_unique<UpdateIndexCallbacks>(
DynamicIdx.get(), Callbacks, TFS,
IndexTasks ? &*IndexTasks : nullptr,
PublishInactiveRegions));
// Adds an index to the stack, at higher priority than existing indexes.
auto AddIndex = [&](SymbolIndex *Idx) {
if (this->Index != nullptr) {
MergedIdx.push_back(std::make_unique<MergedIndex>(Idx, this->Index));
this->Index = MergedIdx.back().get();
} else {
this->Index = Idx;
}
};
if (Opts.StaticIndex)
AddIndex(Opts.StaticIndex);
if (Opts.BackgroundIndex) {
BackgroundIndex::Options BGOpts;
BGOpts.ThreadPoolSize = std::max(Opts.AsyncThreadsCount, 1u);
BGOpts.OnProgress = [Callbacks](BackgroundQueue::Stats S) {
if (Callbacks)
Callbacks->onBackgroundIndexProgress(S);
};
BGOpts.ContextProvider = Opts.ContextProvider;
BackgroundIdx = std::make_unique<BackgroundIndex>(
TFS, CDB,
BackgroundIndexStorage::createDiskBackedStorageFactory(
[&CDB](llvm::StringRef File) { return CDB.getProjectInfo(File); }),
std::move(BGOpts));
AddIndex(BackgroundIdx.get());
}
if (DynamicIdx)
AddIndex(DynamicIdx.get());
if (Opts.FeatureModules) {
FeatureModule::Facilities F{
*this->WorkScheduler,
this->Index,
this->TFS,
};
for (auto &Mod : *Opts.FeatureModules)
Mod.initialize(F);
}
}
ClangdServer::~ClangdServer() {
// Destroying TUScheduler first shuts down request threads that might
// otherwise access members concurrently.
// (Nobody can be using TUScheduler because we're on the main thread).
WorkScheduler.reset();
// Now requests have stopped, we can shut down feature modules.
if (FeatureModules) {
for (auto &Mod : *FeatureModules)
Mod.stop();
for (auto &Mod : *FeatureModules)
Mod.blockUntilIdle(Deadline::infinity());
}
}
void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents,
llvm::StringRef Version,
WantDiagnostics WantDiags, bool ForceRebuild) {
std::string ActualVersion = DraftMgr.addDraft(File, Version, Contents);
ParseOptions Opts;
Opts.PreambleParseForwardingFunctions = PreambleParseForwardingFunctions;
Opts.ImportInsertions = ImportInsertions;
// Compile command is set asynchronously during update, as it can be slow.
ParseInputs Inputs;
Inputs.TFS = &getHeaderFS();
Inputs.Contents = std::string(Contents);
Inputs.Version = std::move(ActualVersion);
Inputs.ForceRebuild = ForceRebuild;
Inputs.Opts = std::move(Opts);
Inputs.Index = Index;
Inputs.ClangTidyProvider = ClangTidyProvider;
Inputs.FeatureModules = FeatureModules;
bool NewFile = WorkScheduler->update(File, Inputs, WantDiags);
// If we loaded Foo.h, we want to make sure Foo.cpp is indexed.
if (NewFile && BackgroundIdx)
BackgroundIdx->boostRelated(File);
}
void ClangdServer::reparseOpenFilesIfNeeded(
llvm::function_ref<bool(llvm::StringRef File)> Filter) {
// Reparse only opened files that were modified.
for (const Path &FilePath : DraftMgr.getActiveFiles())
if (Filter(FilePath))
if (auto Draft = DraftMgr.getDraft(FilePath)) // else disappeared in race?
addDocument(FilePath, *Draft->Contents, Draft->Version,
WantDiagnostics::Auto);
}
std::shared_ptr<const std::string> ClangdServer::getDraft(PathRef File) const {
auto Draft = DraftMgr.getDraft(File);
if (!Draft)
return nullptr;
return std::move(Draft->Contents);
}
std::function<Context(PathRef)>
ClangdServer::createConfiguredContextProvider(const config::Provider *Provider,
Callbacks *Publish) {
if (!Provider)
return [](llvm::StringRef) { return Context::current().clone(); };
struct Impl {
const config::Provider *Provider;
ClangdServer::Callbacks *Publish;
std::mutex PublishMu;
Impl(const config::Provider *Provider, ClangdServer::Callbacks *Publish)
: Provider(Provider), Publish(Publish) {}
Context operator()(llvm::StringRef File) {
config::Params Params;
// Don't reread config files excessively often.
// FIXME: when we see a config file change event, use the event timestamp?
Params.FreshTime =
std::chrono::steady_clock::now() - std::chrono::seconds(5);
llvm::SmallString<256> PosixPath;
if (!File.empty()) {
assert(llvm::sys::path::is_absolute(File));
llvm::sys::path::native(File, PosixPath, llvm::sys::path::Style::posix);
Params.Path = PosixPath.str();
}
llvm::StringMap<std::vector<Diag>> ReportableDiagnostics;
Config C = Provider->getConfig(Params, [&](const llvm::SMDiagnostic &D) {
// Create the map entry even for note diagnostics we don't report.
// This means that when the file is parsed with no warnings, we
// publish an empty set of diagnostics, clearing any the client has.
handleDiagnostic(D, !Publish || D.getFilename().empty()
? nullptr
: &ReportableDiagnostics[D.getFilename()]);
});
// Blindly publish diagnostics for the (unopened) parsed config files.
// We must avoid reporting diagnostics for *the same file* concurrently.
// Source diags are published elsewhere, but those are different files.
if (!ReportableDiagnostics.empty()) {
std::lock_guard<std::mutex> Lock(PublishMu);
for (auto &Entry : ReportableDiagnostics)
Publish->onDiagnosticsReady(Entry.first(), /*Version=*/"",
Entry.second);
}
return Context::current().derive(Config::Key, std::move(C));
}
void handleDiagnostic(const llvm::SMDiagnostic &D,
std::vector<Diag> *ClientDiagnostics) {
switch (D.getKind()) {
case llvm::SourceMgr::DK_Error:
elog("config error at {0}:{1}:{2}: {3}", D.getFilename(), D.getLineNo(),
D.getColumnNo(), D.getMessage());
break;
case llvm::SourceMgr::DK_Warning:
log("config warning at {0}:{1}:{2}: {3}", D.getFilename(),
D.getLineNo(), D.getColumnNo(), D.getMessage());
break;
case llvm::SourceMgr::DK_Note:
case llvm::SourceMgr::DK_Remark:
vlog("config note at {0}:{1}:{2}: {3}", D.getFilename(), D.getLineNo(),
D.getColumnNo(), D.getMessage());
ClientDiagnostics = nullptr; // Don't emit notes as LSP diagnostics.
break;
}
if (ClientDiagnostics)
ClientDiagnostics->push_back(toDiag(D, Diag::ClangdConfig));
}
};
// Copyable wrapper.
return [I(std::make_shared<Impl>(Provider, Publish))](llvm::StringRef Path) {
return (*I)(Path);
};
}
void ClangdServer::removeDocument(PathRef File) {
DraftMgr.removeDraft(File);
WorkScheduler->remove(File);
}
void ClangdServer::codeComplete(PathRef File, Position Pos,
const clangd::CodeCompleteOptions &Opts,
Callback<CodeCompleteResult> CB) {
// Copy completion options for passing them to async task handler.
auto CodeCompleteOpts = Opts;
if (!CodeCompleteOpts.Index) // Respect overridden index.
CodeCompleteOpts.Index = Index;
auto Task = [Pos, CodeCompleteOpts, File = File.str(), CB = std::move(CB),
this](llvm::Expected<InputsAndPreamble> IP) mutable {
if (!IP)
return CB(IP.takeError());
if (auto Reason = isCancelled())
return CB(llvm::make_error<CancelledError>(Reason));
std::optional<SpeculativeFuzzyFind> SpecFuzzyFind;
if (!IP->Preamble) {
// No speculation in Fallback mode, as it's supposed to be much faster
// without compiling.
vlog("Build for file {0} is not ready. Enter fallback mode.", File);
} else if (CodeCompleteOpts.Index) {
SpecFuzzyFind.emplace();
{
std::lock_guard<std::mutex> Lock(CachedCompletionFuzzyFindRequestMutex);
SpecFuzzyFind->CachedReq = CachedCompletionFuzzyFindRequestByFile[File];
}
}
ParseInputs ParseInput{IP->Command, &getHeaderFS(), IP->Contents.str()};
// FIXME: Add traling new line if there is none at eof, workaround a crash,
// see https://github.com/clangd/clangd/issues/332
if (!IP->Contents.ends_with("\n"))
ParseInput.Contents.append("\n");
ParseInput.Index = Index;
CodeCompleteOpts.MainFileSignals = IP->Signals;
CodeCompleteOpts.AllScopes = Config::current().Completion.AllScopes;
// FIXME(ibiryukov): even if Preamble is non-null, we may want to check
// both the old and the new version in case only one of them matches.
CodeCompleteResult Result = clangd::codeComplete(
File, Pos, IP->Preamble, ParseInput, CodeCompleteOpts,
SpecFuzzyFind ? &*SpecFuzzyFind : nullptr);
{
clang::clangd::trace::Span Tracer("Completion results callback");
CB(std::move(Result));
}
if (SpecFuzzyFind && SpecFuzzyFind->NewReq) {
std::lock_guard<std::mutex> Lock(CachedCompletionFuzzyFindRequestMutex);
CachedCompletionFuzzyFindRequestByFile[File] = *SpecFuzzyFind->NewReq;
}
// SpecFuzzyFind is only destroyed after speculative fuzzy find finishes.
// We don't want `codeComplete` to wait for the async call if it doesn't use
// the result (e.g. non-index completion, speculation fails), so that `CB`
// is called as soon as results are available.
};
// We use a potentially-stale preamble because latency is critical here.
WorkScheduler->runWithPreamble(
"CodeComplete", File,
(Opts.RunParser == CodeCompleteOptions::AlwaysParse)
? TUScheduler::Stale
: TUScheduler::StaleOrAbsent,
std::move(Task));
}
void ClangdServer::signatureHelp(PathRef File, Position Pos,
MarkupKind DocumentationFormat,
Callback<SignatureHelp> CB) {
auto Action = [Pos, File = File.str(), CB = std::move(CB),
DocumentationFormat,
this](llvm::Expected<InputsAndPreamble> IP) mutable {
if (!IP)
return CB(IP.takeError());
const auto *PreambleData = IP->Preamble;
if (!PreambleData)
return CB(error("Failed to parse includes"));
ParseInputs ParseInput{IP->Command, &getHeaderFS(), IP->Contents.str()};
// FIXME: Add traling new line if there is none at eof, workaround a crash,
// see https://github.com/clangd/clangd/issues/332
if (!IP->Contents.ends_with("\n"))
ParseInput.Contents.append("\n");
ParseInput.Index = Index;
CB(clangd::signatureHelp(File, Pos, *PreambleData, ParseInput,
DocumentationFormat));
};
// Unlike code completion, we wait for a preamble here.
WorkScheduler->runWithPreamble("SignatureHelp", File, TUScheduler::Stale,
std::move(Action));
}
void ClangdServer::formatFile(PathRef File, std::optional<Range> Rng,
Callback<tooling::Replacements> CB) {
auto Code = getDraft(File);
if (!Code)
return CB(llvm::make_error<LSPError>("trying to format non-added document",
ErrorCode::InvalidParams));
tooling::Range RequestedRange;
if (Rng) {
llvm::Expected<size_t> Begin = positionToOffset(*Code, Rng->start);
if (!Begin)
return CB(Begin.takeError());
llvm::Expected<size_t> End = positionToOffset(*Code, Rng->end);
if (!End)
return CB(End.takeError());
RequestedRange = tooling::Range(*Begin, *End - *Begin);
} else {
RequestedRange = tooling::Range(0, Code->size());
}
// Call clang-format.
auto Action = [File = File.str(), Code = std::move(*Code),
Ranges = std::vector<tooling::Range>{RequestedRange},
CB = std::move(CB), this]() mutable {
format::FormatStyle Style = getFormatStyleForFile(File, Code, TFS, true);
tooling::Replacements IncludeReplaces =
format::sortIncludes(Style, Code, Ranges, File);
auto Changed = tooling::applyAllReplacements(Code, IncludeReplaces);
if (!Changed)
return CB(Changed.takeError());
CB(IncludeReplaces.merge(format::reformat(
Style, *Changed,
tooling::calculateRangesAfterReplacements(IncludeReplaces, Ranges),
File)));
};
WorkScheduler->runQuick("Format", File, std::move(Action));
}
void ClangdServer::formatOnType(PathRef File, Position Pos,
StringRef TriggerText,
Callback<std::vector<TextEdit>> CB) {
auto Code = getDraft(File);
if (!Code)
return CB(llvm::make_error<LSPError>("trying to format non-added document",
ErrorCode::InvalidParams));
llvm::Expected<size_t> CursorPos = positionToOffset(*Code, Pos);
if (!CursorPos)
return CB(CursorPos.takeError());
auto Action = [File = File.str(), Code = std::move(*Code),
TriggerText = TriggerText.str(), CursorPos = *CursorPos,
CB = std::move(CB), this]() mutable {
auto Style = getFormatStyleForFile(File, Code, TFS, false);
std::vector<TextEdit> Result;
for (const tooling::Replacement &R :
formatIncremental(Code, CursorPos, TriggerText, Style))
Result.push_back(replacementToEdit(Code, R));
return CB(Result);
};
WorkScheduler->runQuick("FormatOnType", File, std::move(Action));
}
void ClangdServer::prepareRename(PathRef File, Position Pos,
std::optional<std::string> NewName,
const RenameOptions &RenameOpts,
Callback<RenameResult> CB) {
auto Action = [Pos, File = File.str(), CB = std::move(CB),
NewName = std::move(NewName),
RenameOpts](llvm::Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
// prepareRename is latency-sensitive: we don't query the index, as we
// only need main-file references
auto Results =
clangd::rename({Pos, NewName.value_or("__clangd_rename_placeholder"),
InpAST->AST, File, /*FS=*/nullptr,
/*Index=*/nullptr, RenameOpts});
if (!Results) {
// LSP says to return null on failure, but that will result in a generic
// failure message. If we send an LSP error response, clients can surface
// the message to users (VSCode does).
return CB(Results.takeError());
}
return CB(*Results);
};
WorkScheduler->runWithAST("PrepareRename", File, std::move(Action));
}
void ClangdServer::rename(PathRef File, Position Pos, llvm::StringRef NewName,
const RenameOptions &Opts,
Callback<RenameResult> CB) {
auto Action = [File = File.str(), NewName = NewName.str(), Pos, Opts,
CB = std::move(CB),
this](llvm::Expected<InputsAndAST> InpAST) mutable {
// Tracks number of files edited per invocation.
static constexpr trace::Metric RenameFiles("rename_files",
trace::Metric::Distribution);
if (!InpAST)
return CB(InpAST.takeError());
auto R = clangd::rename({Pos, NewName, InpAST->AST, File,
DirtyFS->view(std::nullopt), Index, Opts});
if (!R)
return CB(R.takeError());
if (Opts.WantFormat) {
auto Style = getFormatStyleForFile(File, InpAST->Inputs.Contents,
*InpAST->Inputs.TFS, false);
llvm::Error Err = llvm::Error::success();
for (auto &E : R->GlobalChanges)
Err =
llvm::joinErrors(reformatEdit(E.getValue(), Style), std::move(Err));
if (Err)
return CB(std::move(Err));
}
RenameFiles.record(R->GlobalChanges.size());
return CB(*R);
};
WorkScheduler->runWithAST("Rename", File, std::move(Action));
}
namespace {
// May generate several candidate selections, due to SelectionTree ambiguity.
// vector of pointers because GCC doesn't like non-copyable Selection.
llvm::Expected<std::vector<std::unique_ptr<Tweak::Selection>>>
tweakSelection(const Range &Sel, const InputsAndAST &AST,
llvm::vfs::FileSystem *FS) {
auto Begin = positionToOffset(AST.Inputs.Contents, Sel.start);
if (!Begin)
return Begin.takeError();
auto End = positionToOffset(AST.Inputs.Contents, Sel.end);
if (!End)
return End.takeError();
std::vector<std::unique_ptr<Tweak::Selection>> Result;
SelectionTree::createEach(
AST.AST.getASTContext(), AST.AST.getTokens(), *Begin, *End,
[&](SelectionTree T) {
Result.push_back(std::make_unique<Tweak::Selection>(
AST.Inputs.Index, AST.AST, *Begin, *End, std::move(T), FS));
return false;
});
assert(!Result.empty() && "Expected at least one SelectionTree");
return std::move(Result);
}
// Some fixes may perform local renaming, we want to convert those to clangd
// rename commands, such that we can leverage the index for more accurate
// results.
std::optional<ClangdServer::CodeActionResult::Rename>
tryConvertToRename(const Diag *Diag, const Fix &Fix) {
bool IsClangTidyRename = Diag->Source == Diag::ClangTidy &&
Diag->Name == "readability-identifier-naming" &&
!Fix.Edits.empty();
if (IsClangTidyRename && Diag->InsideMainFile) {
ClangdServer::CodeActionResult::Rename R;
R.NewName = Fix.Edits.front().newText;
R.FixMessage = Fix.Message;
R.Diag = {Diag->Range, Diag->Message};
return R;
}
return std::nullopt;
}
} // namespace
void ClangdServer::codeAction(const CodeActionInputs &Params,
Callback<CodeActionResult> CB) {
auto Action = [Params, CB = std::move(CB),
FeatureModules(this->FeatureModules)](
Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
auto KindAllowed =
[Only(Params.RequestedActionKinds)](llvm::StringRef Kind) {
if (Only.empty())
return true;
return llvm::any_of(Only, [&](llvm::StringRef Base) {
return Kind.consume_front(Base) &&
(Kind.empty() || Kind.starts_with("."));
});
};
CodeActionResult Result;
Result.Version = InpAST->AST.version().str();
if (KindAllowed(CodeAction::QUICKFIX_KIND)) {
auto FindMatchedDiag = [&InpAST](const DiagRef &DR) -> const Diag * {
for (const auto &Diag : InpAST->AST.getDiagnostics())
if (Diag.Range == DR.Range && Diag.Message == DR.Message)
return &Diag;
return nullptr;
};
for (const auto &DiagRef : Params.Diagnostics) {
if (const auto *Diag = FindMatchedDiag(DiagRef))
for (const auto &Fix : Diag->Fixes) {
if (auto Rename = tryConvertToRename(Diag, Fix)) {
Result.Renames.emplace_back(std::move(*Rename));
} else {
Result.QuickFixes.push_back({DiagRef, Fix});
}
}
}
}
// Collect Tweaks
auto Selections = tweakSelection(Params.Selection, *InpAST, /*FS=*/nullptr);
if (!Selections)
return CB(Selections.takeError());
// Don't allow a tweak to fire more than once across ambiguous selections.
llvm::DenseSet<llvm::StringRef> PreparedTweaks;
auto DeduplicatingFilter = [&](const Tweak &T) {
return KindAllowed(T.kind()) && Params.TweakFilter(T) &&
!PreparedTweaks.count(T.id());
};
for (const auto &Sel : *Selections) {
for (auto &T : prepareTweaks(*Sel, DeduplicatingFilter, FeatureModules)) {
Result.TweakRefs.push_back(TweakRef{T->id(), T->title(), T->kind()});
PreparedTweaks.insert(T->id());
TweakAvailable.record(1, T->id());
}
}
CB(std::move(Result));
};
WorkScheduler->runWithAST("codeAction", Params.File, std::move(Action),
Transient);
}
void ClangdServer::applyTweak(PathRef File, Range Sel, StringRef TweakID,
Callback<Tweak::Effect> CB) {
// Tracks number of times a tweak has been attempted.
static constexpr trace::Metric TweakAttempt(
"tweak_attempt", trace::Metric::Counter, "tweak_id");
// Tracks number of times a tweak has failed to produce edits.
static constexpr trace::Metric TweakFailed(
"tweak_failed", trace::Metric::Counter, "tweak_id");
TweakAttempt.record(1, TweakID);
auto Action = [File = File.str(), Sel, TweakID = TweakID.str(),
CB = std::move(CB),
this](Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
auto FS = DirtyFS->view(std::nullopt);
auto Selections = tweakSelection(Sel, *InpAST, FS.get());
if (!Selections)
return CB(Selections.takeError());
std::optional<llvm::Expected<Tweak::Effect>> Effect;
// Try each selection, take the first one that prepare()s.
// If they all fail, Effect will hold get the last error.
for (const auto &Selection : *Selections) {
auto T = prepareTweak(TweakID, *Selection, FeatureModules);
if (T) {
Effect = (*T)->apply(*Selection);
break;
}
Effect = T.takeError();
}
assert(Effect && "Expected at least one selection");
if (*Effect && (*Effect)->FormatEdits) {
// Format tweaks that require it centrally here.
for (auto &It : (*Effect)->ApplyEdits) {
Edit &E = It.second;
format::FormatStyle Style =
getFormatStyleForFile(File, E.InitialCode, TFS, false);
if (llvm::Error Err = reformatEdit(E, Style))
elog("Failed to format {0}: {1}", It.first(), std::move(Err));
}
} else {
TweakFailed.record(1, TweakID);
}
return CB(std::move(*Effect));
};
WorkScheduler->runWithAST("ApplyTweak", File, std::move(Action));
}
void ClangdServer::locateSymbolAt(PathRef File, Position Pos,
Callback<std::vector<LocatedSymbol>> CB) {
auto Action = [Pos, CB = std::move(CB),
this](llvm::Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
CB(clangd::locateSymbolAt(InpAST->AST, Pos, Index));
};
WorkScheduler->runWithAST("Definitions", File, std::move(Action));
}
void ClangdServer::switchSourceHeader(
PathRef Path, Callback<std::optional<clangd::Path>> CB) {
// We want to return the result as fast as possible, strategy is:
// 1) use the file-only heuristic, it requires some IO but it is much
// faster than building AST, but it only works when .h/.cc files are in
// the same directory.
// 2) if 1) fails, we use the AST&Index approach, it is slower but supports
// different code layout.
if (auto CorrespondingFile =
getCorrespondingHeaderOrSource(Path, TFS.view(std::nullopt)))
return CB(std::move(CorrespondingFile));
auto Action = [Path = Path.str(), CB = std::move(CB),
this](llvm::Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
CB(getCorrespondingHeaderOrSource(Path, InpAST->AST, Index));
};
WorkScheduler->runWithAST("SwitchHeaderSource", Path, std::move(Action));
}
void ClangdServer::findDocumentHighlights(
PathRef File, Position Pos, Callback<std::vector<DocumentHighlight>> CB) {
auto Action =
[Pos, CB = std::move(CB)](llvm::Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
CB(clangd::findDocumentHighlights(InpAST->AST, Pos));
};
WorkScheduler->runWithAST("Highlights", File, std::move(Action), Transient);
}
void ClangdServer::findHover(PathRef File, Position Pos,
Callback<std::optional<HoverInfo>> CB) {
auto Action = [File = File.str(), Pos, CB = std::move(CB),
this](llvm::Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
format::FormatStyle Style = getFormatStyleForFile(
File, InpAST->Inputs.Contents, *InpAST->Inputs.TFS, false);
CB(clangd::getHover(InpAST->AST, Pos, std::move(Style), Index));
};
WorkScheduler->runWithAST("Hover", File, std::move(Action), Transient);
}
void ClangdServer::typeHierarchy(PathRef File, Position Pos, int Resolve,
TypeHierarchyDirection Direction,
Callback<std::vector<TypeHierarchyItem>> CB) {
auto Action = [File = File.str(), Pos, Resolve, Direction, CB = std::move(CB),
this](Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
CB(clangd::getTypeHierarchy(InpAST->AST, Pos, Resolve, Direction, Index,
File));
};
WorkScheduler->runWithAST("TypeHierarchy", File, std::move(Action));
}
void ClangdServer::superTypes(
const TypeHierarchyItem &Item,
Callback<std::optional<std::vector<TypeHierarchyItem>>> CB) {
WorkScheduler->run("typeHierarchy/superTypes", /*Path=*/"",
[=, CB = std::move(CB)]() mutable {
CB(clangd::superTypes(Item, Index));
});
}
void ClangdServer::subTypes(const TypeHierarchyItem &Item,
Callback<std::vector<TypeHierarchyItem>> CB) {
WorkScheduler->run(
"typeHierarchy/subTypes", /*Path=*/"",
[=, CB = std::move(CB)]() mutable { CB(clangd::subTypes(Item, Index)); });
}
void ClangdServer::resolveTypeHierarchy(
TypeHierarchyItem Item, int Resolve, TypeHierarchyDirection Direction,
Callback<std::optional<TypeHierarchyItem>> CB) {
WorkScheduler->run(
"Resolve Type Hierarchy", "", [=, CB = std::move(CB)]() mutable {
clangd::resolveTypeHierarchy(Item, Resolve, Direction, Index);
CB(Item);
});
}
void ClangdServer::prepareCallHierarchy(
PathRef File, Position Pos, Callback<std::vector<CallHierarchyItem>> CB) {
auto Action = [File = File.str(), Pos,
CB = std::move(CB)](Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
CB(clangd::prepareCallHierarchy(InpAST->AST, Pos, File));
};
WorkScheduler->runWithAST("CallHierarchy", File, std::move(Action));
}
void ClangdServer::incomingCalls(
const CallHierarchyItem &Item,
Callback<std::vector<CallHierarchyIncomingCall>> CB) {
WorkScheduler->run("Incoming Calls", "",
[CB = std::move(CB), Item, this]() mutable {
CB(clangd::incomingCalls(Item, Index));
});
}
void ClangdServer::inlayHints(PathRef File, std::optional<Range> RestrictRange,
Callback<std::vector<InlayHint>> CB) {
auto Action = [RestrictRange(std::move(RestrictRange)),
CB = std::move(CB)](Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
CB(clangd::inlayHints(InpAST->AST, std::move(RestrictRange)));
};
WorkScheduler->runWithAST("InlayHints", File, std::move(Action), Transient);
}
void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
// FIXME: Do nothing for now. This will be used for indexing and potentially
// invalidating other caches.
}
void ClangdServer::workspaceSymbols(
llvm::StringRef Query, int Limit,
Callback<std::vector<SymbolInformation>> CB) {
WorkScheduler->run(
"getWorkspaceSymbols", /*Path=*/"",
[Query = Query.str(), Limit, CB = std::move(CB), this]() mutable {
CB(clangd::getWorkspaceSymbols(Query, Limit, Index,
WorkspaceRoot.value_or("")));
});
}
void ClangdServer::documentSymbols(llvm::StringRef File,
Callback<std::vector<DocumentSymbol>> CB) {
auto Action =
[CB = std::move(CB)](llvm::Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
CB(clangd::getDocumentSymbols(InpAST->AST));
};
WorkScheduler->runWithAST("DocumentSymbols", File, std::move(Action),
Transient);
}
void ClangdServer::foldingRanges(llvm::StringRef File,
Callback<std::vector<FoldingRange>> CB) {
auto Code = getDraft(File);
if (!Code)
return CB(llvm::make_error<LSPError>(
"trying to compute folding ranges for non-added document",
ErrorCode::InvalidParams));
auto Action = [LineFoldingOnly = LineFoldingOnly, CB = std::move(CB),
Code = std::move(*Code)]() mutable {
CB(clangd::getFoldingRanges(Code, LineFoldingOnly));
};
// We want to make sure folding ranges are always available for all the open
// files, hence prefer runQuick to not wait for operations on other files.
WorkScheduler->runQuick("FoldingRanges", File, std::move(Action));
}
void ClangdServer::findType(llvm::StringRef File, Position Pos,
Callback<std::vector<LocatedSymbol>> CB) {
auto Action = [Pos, CB = std::move(CB),
this](llvm::Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
CB(clangd::findType(InpAST->AST, Pos, Index));
};
WorkScheduler->runWithAST("FindType", File, std::move(Action));
}
void ClangdServer::findImplementations(
PathRef File, Position Pos, Callback<std::vector<LocatedSymbol>> CB) {
auto Action = [Pos, CB = std::move(CB),
this](llvm::Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
CB(clangd::findImplementations(InpAST->AST, Pos, Index));
};
WorkScheduler->runWithAST("Implementations", File, std::move(Action));
}
void ClangdServer::findReferences(PathRef File, Position Pos, uint32_t Limit,
bool AddContainer,
Callback<ReferencesResult> CB) {
auto Action = [Pos, Limit, AddContainer, CB = std::move(CB),
this](llvm::Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
CB(clangd::findReferences(InpAST->AST, Pos, Limit, Index, AddContainer));
};
WorkScheduler->runWithAST("References", File, std::move(Action));
}
void ClangdServer::symbolInfo(PathRef File, Position Pos,
Callback<std::vector<SymbolDetails>> CB) {
auto Action =
[Pos, CB = std::move(CB)](llvm::Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
CB(clangd::getSymbolInfo(InpAST->AST, Pos));
};
WorkScheduler->runWithAST("SymbolInfo", File, std::move(Action));
}
void ClangdServer::semanticRanges(PathRef File,
const std::vector<Position> &Positions,
Callback<std::vector<SelectionRange>> CB) {
auto Action = [Positions, CB = std::move(CB)](
llvm::Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
std::vector<SelectionRange> Result;
for (const auto &Pos : Positions) {
if (auto Range = clangd::getSemanticRanges(InpAST->AST, Pos))
Result.push_back(std::move(*Range));
else
return CB(Range.takeError());
}
CB(std::move(Result));
};
WorkScheduler->runWithAST("SemanticRanges", File, std::move(Action));
}
void ClangdServer::documentLinks(PathRef File,
Callback<std::vector<DocumentLink>> CB) {
auto Action =
[CB = std::move(CB)](llvm::Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
CB(clangd::getDocumentLinks(InpAST->AST));
};
WorkScheduler->runWithAST("DocumentLinks", File, std::move(Action),
Transient);
}
void ClangdServer::semanticHighlights(
PathRef File, Callback<std::vector<HighlightingToken>> CB) {
auto Action = [CB = std::move(CB),
PublishInactiveRegions = PublishInactiveRegions](
llvm::Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
// Include inactive regions in semantic highlighting tokens only if the
// client doesn't support a dedicated protocol for being informed about
// them.
CB(clangd::getSemanticHighlightings(InpAST->AST, !PublishInactiveRegions));
};
WorkScheduler->runWithAST("SemanticHighlights", File, std::move(Action),
Transient);
}
void ClangdServer::getAST(PathRef File, std::optional<Range> R,
Callback<std::optional<ASTNode>> CB) {
auto Action =
[R, CB(std::move(CB))](llvm::Expected<InputsAndAST> Inputs) mutable {
if (!Inputs)
return CB(Inputs.takeError());
if (!R) {
// It's safe to pass in the TU, as dumpAST() does not
// deserialize the preamble.
auto Node = DynTypedNode::create(
*Inputs->AST.getASTContext().getTranslationUnitDecl());
return CB(dumpAST(Node, Inputs->AST.getTokens(),
Inputs->AST.getASTContext()));
}
unsigned Start, End;
if (auto Offset = positionToOffset(Inputs->Inputs.Contents, R->start))
Start = *Offset;
else
return CB(Offset.takeError());
if (auto Offset = positionToOffset(Inputs->Inputs.Contents, R->end))
End = *Offset;
else
return CB(Offset.takeError());
bool Success = SelectionTree::createEach(
Inputs->AST.getASTContext(), Inputs->AST.getTokens(), Start, End,
[&](SelectionTree T) {
if (const SelectionTree::Node *N = T.commonAncestor()) {
CB(dumpAST(N->ASTNode, Inputs->AST.getTokens(),
Inputs->AST.getASTContext()));
return true;
}
return false;
});
if (!Success)
CB(std::nullopt);
};
WorkScheduler->runWithAST("GetAST", File, std::move(Action));
}
void ClangdServer::customAction(PathRef File, llvm::StringRef Name,
Callback<InputsAndAST> Action) {
WorkScheduler->runWithAST(Name, File, std::move(Action));
}
void ClangdServer::diagnostics(PathRef File, Callback<std::vector<Diag>> CB) {
auto Action =
[CB = std::move(CB)](llvm::Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
return CB(InpAST->AST.getDiagnostics());
};
WorkScheduler->runWithAST("Diagnostics", File, std::move(Action));
}
llvm::StringMap<TUScheduler::FileStats> ClangdServer::fileStats() const {
return WorkScheduler->fileStats();
}
[[nodiscard]] bool
ClangdServer::blockUntilIdleForTest(std::optional<double> TimeoutSeconds) {
// Order is important here: we don't want to block on A and then B,
// if B might schedule work on A.
#if defined(__has_feature) && \
(__has_feature(address_sanitizer) || __has_feature(hwaddress_sanitizer) || \
__has_feature(memory_sanitizer) || __has_feature(thread_sanitizer))
if (TimeoutSeconds.has_value())
(*TimeoutSeconds) *= 10;
#endif
// Nothing else can schedule work on TUScheduler, because it's not threadsafe
// and we're blocking the main thread.
if (!WorkScheduler->blockUntilIdle(timeoutSeconds(TimeoutSeconds)))
return false;
// TUScheduler is the only thing that starts background indexing work.
if (IndexTasks && !IndexTasks->wait(timeoutSeconds(TimeoutSeconds)))
return false;
// Unfortunately we don't have strict topological order between the rest of
// the components. E.g. CDB broadcast triggers backrgound indexing.
// This queries the CDB which may discover new work if disk has changed.
//
// So try each one a few times in a loop.
// If there are no tricky interactions then all after the first are no-ops.
// Then on the last iteration, verify they're idle without waiting.
//
// There's a small chance they're juggling work and we didn't catch them :-(
for (std::optional<double> Timeout :
{TimeoutSeconds, TimeoutSeconds, std::optional<double>(0)}) {
if (!CDB.blockUntilIdle(timeoutSeconds(Timeout)))
return false;
if (BackgroundIdx && !BackgroundIdx->blockUntilIdleForTest(Timeout))
return false;
if (FeatureModules && llvm::any_of(*FeatureModules, [&](FeatureModule &M) {
return !M.blockUntilIdle(timeoutSeconds(Timeout));
}))
return false;
}
assert(WorkScheduler->blockUntilIdle(Deadline::zero()) &&
"Something scheduled work while we're blocking the main thread!");
return true;
}
void ClangdServer::profile(MemoryTree &MT) const {
if (DynamicIdx)
DynamicIdx->profile(MT.child("dynamic_index"));
if (BackgroundIdx)
BackgroundIdx->profile(MT.child("background_index"));
WorkScheduler->profile(MT.child("tuscheduler"));
}
} // namespace clangd
} // namespace clang