ccls/src/clang_complete.cc

582 lines
19 KiB
C++
Raw Normal View History

2018-08-21 05:27:52 +00:00
/* Copyright 2017-2018 ccls Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "clang_complete.hh"
2018-09-23 20:31:06 +00:00
#include "clang_tu.hh"
2018-05-27 19:24:56 +00:00
#include "filesystem.hh"
#include "log.hh"
#include "match.h"
2017-05-10 04:00:05 +00:00
#include "platform.h"
#include <clang/Lex/PreprocessorOptions.h>
2018-07-14 17:00:04 +00:00
#include <clang/Sema/CodeCompleteConsumer.h>
#include <clang/Sema/Sema.h>
2018-05-27 19:24:56 +00:00
#include <llvm/ADT/Twine.h>
#include <llvm/Config/llvm-config.h>
2018-07-16 05:49:32 +00:00
#include <llvm/Support/CrashRecoveryContext.h>
2018-05-27 19:24:56 +00:00
#include <llvm/Support/Threading.h>
2018-07-14 17:00:04 +00:00
using namespace clang;
using namespace llvm;
#include <algorithm>
#include <chrono>
#include <ratio>
#include <thread>
namespace chrono = std::chrono;
2018-08-29 05:49:53 +00:00
namespace ccls {
namespace {
2018-08-09 17:08:14 +00:00
std::string StripFileType(const std::string &path) {
SmallString<128> Ret;
sys::path::append(Ret, sys::path::parent_path(path), sys::path::stem(path));
return sys::path::convert_to_slash(Ret);
2018-04-08 00:10:54 +00:00
}
2018-08-29 05:49:53 +00:00
bool LocationInRange(SourceLocation L, CharSourceRange R,
const SourceManager &M) {
assert(R.isCharRange());
if (!R.isValid() || M.getFileID(R.getBegin()) != M.getFileID(R.getEnd()) ||
M.getFileID(R.getBegin()) != M.getFileID(L))
return false;
return L != R.getEnd() && M.isPointWithin(L, R.getBegin(), R.getEnd());
}
CharSourceRange DiagnosticRange(const clang::Diagnostic &D, const LangOptions &L) {
auto &M = D.getSourceManager();
auto Loc = M.getFileLoc(D.getLocation());
// Accept the first range that contains the location.
for (const auto &CR : D.getRanges()) {
auto R = Lexer::makeFileCharRange(CR, M, L);
if (LocationInRange(Loc, R, M))
return R;
}
// The range may be given as a fixit hint instead.
for (const auto &F : D.getFixItHints()) {
auto R = Lexer::makeFileCharRange(F.RemoveRange, M, L);
if (LocationInRange(Loc, R, M))
return R;
}
// If no suitable range is found, just use the token at the location.
auto R = Lexer::makeFileCharRange(CharSourceRange::getTokenRange(Loc), M, L);
if (!R.isValid()) // Fall back to location only, let the editor deal with it.
R = CharSourceRange::getCharRange(Loc);
return R;
}
2018-08-29 05:49:53 +00:00
class StoreDiags : public DiagnosticConsumer {
const LangOptions *LangOpts;
std::optional<Diag> last;
std::vector<Diag> output;
std::string path;
std::unordered_map<unsigned, bool> FID2concerned;
2018-08-29 05:49:53 +00:00
void Flush() {
if (!last)
return;
bool mentions = last->concerned || last->edits.size();
2018-08-29 05:49:53 +00:00
if (!mentions)
for (auto &N : last->notes)
if (N.concerned)
2018-08-29 05:49:53 +00:00
mentions = true;
if (mentions)
output.push_back(std::move(*last));
last.reset();
}
public:
StoreDiags(std::string path) : path(path) {}
2018-08-29 05:49:53 +00:00
std::vector<Diag> Take() {
return std::move(output);
}
bool IsConcerned(const SourceManager &SM, SourceLocation L) {
FileID FID = SM.getFileID(L);
auto it = FID2concerned.try_emplace(FID.getHashValue());
if (it.second) {
const FileEntry *FE = SM.getFileEntryForID(FID);
2018-09-23 20:31:06 +00:00
it.first->second = FE && PathFromFileEntry(*FE) == path;
}
return it.first->second;
}
2018-08-29 05:49:53 +00:00
void BeginSourceFile(const LangOptions &Opts, const Preprocessor *) override {
LangOpts = &Opts;
}
void EndSourceFile() override {
Flush();
}
void HandleDiagnostic(DiagnosticsEngine::Level Level,
const Diagnostic &Info) override {
DiagnosticConsumer::HandleDiagnostic(Level, Info);
SourceLocation L = Info.getLocation();
if (!L.isValid()) return;
const SourceManager &SM = Info.getSourceManager();
StringRef Filename = SM.getFilename(Info.getLocation());
bool concerned = IsConcerned(SM, Info.getLocation());
2018-08-29 05:49:53 +00:00
auto fillDiagBase = [&](DiagBase &d) {
llvm::SmallString<64> Message;
Info.FormatDiagnostic(Message);
d.range =
FromCharSourceRange(SM, *LangOpts, DiagnosticRange(Info, *LangOpts));
d.message = Message.str();
d.concerned = concerned;
d.file = Filename;
2018-08-29 05:49:53 +00:00
d.level = Level;
d.category = DiagnosticIDs::getCategoryNumberForDiag(Info.getID());
};
auto addFix = [&](bool SyntheticMessage) -> bool {
if (!concerned)
2018-08-29 05:49:53 +00:00
return false;
for (const FixItHint &FixIt : Info.getFixItHints()) {
if (!IsConcerned(SM, FixIt.RemoveRange.getBegin()))
2018-08-29 05:49:53 +00:00
return false;
lsTextEdit edit;
edit.newText = FixIt.CodeToInsert;
auto r = FromCharSourceRange(SM, *LangOpts, FixIt.RemoveRange);
edit.range =
lsRange{{r.start.line, r.start.column}, {r.end.line, r.end.column}};
last->edits.push_back(std::move(edit));
}
return true;
};
2018-08-29 05:49:53 +00:00
if (Level == DiagnosticsEngine::Note || Level == DiagnosticsEngine::Remark) {
if (Info.getFixItHints().size()) {
addFix(false);
} else {
Note &n = last->notes.emplace_back();
fillDiagBase(n);
}
} else {
Flush();
last = Diag();
fillDiagBase(*last);
if (!Info.getFixItHints().empty())
addFix(true);
}
}
};
std::unique_ptr<CompilerInstance> BuildCompilerInstance(
CompletionSession &session, std::unique_ptr<CompilerInvocation> CI,
DiagnosticConsumer &DC, const WorkingFiles::Snapshot &snapshot,
std::vector<std::unique_ptr<llvm::MemoryBuffer>> &Bufs) {
std::string main = ResolveIfRelative(
session.file.directory,
sys::path::convert_to_slash(CI->getFrontendOpts().Inputs[0].getFile()));
2018-08-29 05:49:53 +00:00
for (auto &file : snapshot.files) {
Bufs.push_back(llvm::MemoryBuffer::getMemBuffer(file.content));
if (file.filename == main)
2018-08-29 05:49:53 +00:00
if (auto Preamble = session.GetPreamble()) {
#if LLVM_VERSION_MAJOR >= 7
Preamble->Preamble.OverridePreamble(*CI, session.FS,
Bufs.back().get());
#else
Preamble->Preamble.AddImplicitPreamble(*CI, session.FS,
2018-08-29 05:49:53 +00:00
Bufs.back().get());
#endif
continue;
2018-08-29 05:49:53 +00:00
}
CI->getPreprocessorOpts().addRemappedFile(file.filename,
Bufs.back().get());
2018-08-29 05:49:53 +00:00
}
auto Clang = std::make_unique<CompilerInstance>(session.PCH);
Clang->setInvocation(std::move(CI));
Clang->setVirtualFileSystem(session.FS);
Clang->createDiagnostics(&DC, false);
Clang->setTarget(TargetInfo::CreateTargetInfo(
Clang->getDiagnostics(), Clang->getInvocation().TargetOpts));
if (!Clang->hasTarget())
return nullptr;
return Clang;
}
bool Parse(CompilerInstance &Clang) {
SyntaxOnlyAction Action;
if (!Action.BeginSourceFile(Clang, Clang.getFrontendOpts().Inputs[0]))
return false;
if (!Action.Execute())
return false;
Action.EndSourceFile();
return true;
}
2018-09-11 23:42:04 +00:00
void CompletionPreloadMain(CompletionManager *manager) {
while (true) {
2018-09-11 23:42:04 +00:00
auto request = manager->preload_requests_.Dequeue();
2017-06-10 01:02:48 +00:00
bool is_open = false;
std::shared_ptr<CompletionSession> session =
manager->TryGetSession(request.path, true, &is_open);
if (!session)
continue;
// For inferred session, don't build preamble because changes in a.h will
// invalidate it.
if (!session->inferred) {
const auto &args = session->file.args;
WorkingFiles::Snapshot snapshot = session->wfiles->AsSnapshot(
{StripFileType(session->file.filename)});
if (std::unique_ptr<CompilerInvocation> CI =
BuildCompilerInvocation(args, session->FS))
session->BuildPreamble(*CI, request.path);
}
int debounce =
is_open ? g_config->diagnostics.onOpen : g_config->diagnostics.onSave;
if (debounce >= 0) {
2018-09-11 23:42:04 +00:00
lsTextDocumentIdentifier document;
document.uri = lsDocumentUri::FromPath(request.path);
manager->DiagnosticsUpdate(request.path, debounce);
2018-09-11 23:42:04 +00:00
}
}
}
void CompletionMain(CompletionManager *manager) {
while (true) {
// Fetching the completion request blocks until we have a request.
std::unique_ptr<CompletionManager::CompletionRequest> request =
manager->completion_request_.Dequeue();
// Drop older requests if we're not buffering.
2018-04-04 06:05:41 +00:00
while (g_config->completion.dropOldRequests &&
!manager->completion_request_.IsEmpty()) {
manager->on_dropped_(request->id);
request->Consumer.reset();
request->on_complete(nullptr);
request = manager->completion_request_.Dequeue();
}
std::string path = request->document.uri.GetPath();
2017-09-22 01:14:57 +00:00
std::shared_ptr<CompletionSession> session =
manager->TryGetSession(path, false);
2017-06-10 01:02:48 +00:00
std::unique_ptr<CompilerInvocation> CI =
BuildCompilerInvocation(session->file.args, session->FS);
if (!CI)
continue;
auto &FOpts = CI->getFrontendOpts();
FOpts.CodeCompleteOpts = request->CCOpts;
FOpts.CodeCompletionAt.FileName = path;
FOpts.CodeCompletionAt.Line = request->position.line + 1;
FOpts.CodeCompletionAt.Column = request->position.character + 1;
FOpts.SkipFunctionBodies = true;
CI->getLangOpts()->CommentOpts.ParseAllComments = true;
DiagnosticConsumer DC;
WorkingFiles::Snapshot snapshot =
manager->working_files_->AsSnapshot({StripFileType(path)});
std::vector<std::unique_ptr<llvm::MemoryBuffer>> Bufs;
2018-08-29 05:49:53 +00:00
auto Clang = BuildCompilerInstance(*session, std::move(CI), DC, snapshot, Bufs);
if (!Clang)
continue;
Clang->setCodeCompletionConsumer(request->Consumer.release());
2018-08-29 05:49:53 +00:00
if (!Parse(*Clang))
continue;
for (auto &Buf : Bufs)
Buf.release();
request->on_complete(&Clang->getCodeCompletionConsumer());
}
}
2018-09-13 06:07:47 +00:00
llvm::StringRef diagLeveltoString(DiagnosticsEngine::Level Lvl) {
switch (Lvl) {
case DiagnosticsEngine::Ignored:
return "ignored";
case DiagnosticsEngine::Note:
return "note";
case DiagnosticsEngine::Remark:
return "remark";
case DiagnosticsEngine::Warning:
return "warning";
case DiagnosticsEngine::Error:
return "error";
case DiagnosticsEngine::Fatal:
return "fatal error";
}
}
void printDiag(llvm::raw_string_ostream &OS, const DiagBase &d) {
if (d.concerned)
2018-09-13 06:07:47 +00:00
OS << llvm::sys::path::filename(d.file);
else
OS << d.file;
auto pos = d.range.start;
OS << ":" << (pos.line + 1) << ":" << (pos.column + 1) << ":"
<< (d.concerned ? " " : "\n");
2018-09-13 06:07:47 +00:00
OS << diagLeveltoString(d.level) << ": " << d.message;
}
void DiagnosticMain(CompletionManager *manager) {
2018-04-14 16:52:17 +00:00
while (true) {
CompletionManager::DiagnosticRequest request =
2018-07-15 17:10:24 +00:00
manager->diagnostic_request_.Dequeue();
const std::string &path = request.path;
int64_t wait = request.wait_until -
chrono::duration_cast<chrono::milliseconds>(
chrono::high_resolution_clock::now().time_since_epoch())
.count();
if (wait > 0)
std::this_thread::sleep_for(chrono::duration<int64_t, std::milli>(
std::min(wait, request.debounce)));
std::shared_ptr<CompletionSession> session =
manager->TryGetSession(path, false);
2018-08-29 05:49:53 +00:00
std::unique_ptr<CompilerInvocation> CI =
BuildCompilerInvocation(session->file.args, session->FS);
2018-08-29 05:49:53 +00:00
if (!CI)
2018-04-14 16:52:17 +00:00
continue;
CI->getDiagnosticOpts().IgnoreWarnings = false;
CI->getLangOpts()->SpellChecking = g_config->diagnostics.spellChecking;
StoreDiags DC(path);
2018-04-14 16:52:17 +00:00
WorkingFiles::Snapshot snapshot =
2018-07-15 17:10:24 +00:00
manager->working_files_->AsSnapshot({StripFileType(path)});
2018-08-29 05:49:53 +00:00
std::vector<std::unique_ptr<llvm::MemoryBuffer>> Bufs;
auto Clang = BuildCompilerInstance(*session, std::move(CI), DC, snapshot, Bufs);
if (!Clang)
2018-04-14 16:52:17 +00:00
continue;
2018-08-29 05:49:53 +00:00
if (!Parse(*Clang))
continue;
for (auto &Buf : Bufs)
Buf.release();
2018-04-14 16:52:17 +00:00
2018-09-13 06:07:47 +00:00
auto Fill = [](const DiagBase &d, lsDiagnostic &ret) {
ret.range = lsRange{{d.range.start.line, d.range.start.column},
{d.range.end.line, d.range.end.column}};
2018-08-29 05:49:53 +00:00
switch (d.level) {
case DiagnosticsEngine::Ignored:
// llvm_unreachable
case DiagnosticsEngine::Remark:
2018-09-13 06:07:47 +00:00
ret.severity = lsDiagnosticSeverity::Hint;
break;
case DiagnosticsEngine::Note:
ret.severity = lsDiagnosticSeverity::Information;
break;
case DiagnosticsEngine::Warning:
2018-09-13 06:07:47 +00:00
ret.severity = lsDiagnosticSeverity::Warning;
break;
case DiagnosticsEngine::Error:
case DiagnosticsEngine::Fatal:
2018-09-13 06:07:47 +00:00
ret.severity = lsDiagnosticSeverity::Error;
break;
}
2018-09-13 06:07:47 +00:00
ret.code = d.category;
return ret;
};
std::vector<Diag> diags = DC.Take();
if (std::shared_ptr<PreambleData> preamble = session->GetPreamble())
diags.insert(diags.end(), preamble->diags.begin(), preamble->diags.end());
2018-09-13 06:07:47 +00:00
std::vector<lsDiagnostic> ls_diags;
for (auto &d : diags) {
if (!d.concerned)
2018-09-13 06:07:47 +00:00
continue;
std::string buf;
llvm::raw_string_ostream OS(buf);
lsDiagnostic &ls_diag = ls_diags.emplace_back();
Fill(d, ls_diag);
2018-08-29 05:49:53 +00:00
ls_diag.fixits_ = d.edits;
2018-09-13 06:07:47 +00:00
OS << d.message;
for (auto &n : d.notes) {
OS << "\n\n";
printDiag(OS, n);
}
OS.flush();
ls_diag.message = std::move(buf);
for (auto &n : d.notes) {
if (!n.concerned)
2018-09-13 06:07:47 +00:00
continue;
lsDiagnostic &ls_diag1 = ls_diags.emplace_back();
Fill(n, ls_diag1);
OS << n.message << "\n\n";
printDiag(OS, d);
OS.flush();
ls_diag1.message = std::move(buf);
}
2018-04-14 16:52:17 +00:00
}
{
std::lock_guard lock(session->wfiles->files_mutex);
if (WorkingFile *wfile = session->wfiles->GetFileByFilenameNoLock(path))
wfile->diagnostics_ = ls_diags;
}
2018-07-15 17:10:24 +00:00
manager->on_diagnostic_(path, ls_diags);
2018-04-14 16:52:17 +00:00
}
}
2018-08-09 17:08:14 +00:00
} // namespace
std::shared_ptr<PreambleData> CompletionSession::GetPreamble() {
2018-08-29 05:49:53 +00:00
std::lock_guard<std::mutex> lock(mutex);
return preamble;
}
void CompletionSession::BuildPreamble(CompilerInvocation &CI,
const std::string &main) {
std::shared_ptr<PreambleData> OldP = GetPreamble();
std::string content = wfiles->GetContent(main);
std::unique_ptr<llvm::MemoryBuffer> Buf =
2018-08-29 05:49:53 +00:00
llvm::MemoryBuffer::getMemBuffer(content);
auto Bounds = ComputePreambleBounds(*CI.getLangOpts(), Buf.get(), 0);
if (OldP && OldP->Preamble.CanReuse(CI, Buf.get(), Bounds, FS.get()))
return;
CI.getDiagnosticOpts().IgnoreWarnings = false;
CI.getFrontendOpts().SkipFunctionBodies = true;
CI.getLangOpts()->CommentOpts.ParseAllComments = true;
#if LLVM_VERSION_MAJOR >= 7
CI.getPreprocessorOpts().WriteCommentListToPCH = false;
#endif
StoreDiags DC(main);
2018-08-29 05:49:53 +00:00
IntrusiveRefCntPtr<DiagnosticsEngine> DE =
CompilerInstance::createDiagnostics(&CI.getDiagnosticOpts(), &DC, false);
PreambleCallbacks PP;
2018-08-29 05:49:53 +00:00
if (auto NewPreamble = PrecompiledPreamble::Build(CI, Buf.get(), Bounds,
*DE, FS, PCH, true, PP)) {
std::lock_guard<std::mutex> lock(mutex);
preamble =
std::make_shared<PreambleData>(std::move(*NewPreamble), DC.Take());
}
}
2018-08-29 05:49:53 +00:00
} // namespace ccls
CompletionManager::CompletionManager(Project *project,
WorkingFiles *working_files,
OnDiagnostic on_diagnostic,
OnDropped on_dropped)
2018-08-09 17:08:14 +00:00
: project_(project), working_files_(working_files),
on_diagnostic_(on_diagnostic), on_dropped_(on_dropped),
preloads(kMaxPreloadedSessions),
sessions(kMaxCompletionSessions),
PCH(std::make_shared<PCHContainerOperations>()) {
2018-04-16 19:36:02 +00:00
std::thread([&]() {
set_thread_name("comp");
ccls::CompletionMain(this);
2018-08-09 17:08:14 +00:00
})
.detach();
2018-04-16 19:36:02 +00:00
std::thread([&]() {
2018-05-27 19:24:56 +00:00
set_thread_name("comp-preload");
2018-08-29 05:49:53 +00:00
ccls::CompletionPreloadMain(this);
2018-08-09 17:08:14 +00:00
})
.detach();
2018-04-16 19:36:02 +00:00
std::thread([&]() {
set_thread_name("diag");
ccls::DiagnosticMain(this);
2018-08-09 17:08:14 +00:00
})
.detach();
}
void CompletionManager::DiagnosticsUpdate(const std::string &path,
int debounce) {
static GroupMatch match(g_config->diagnostics.whitelist,
g_config->diagnostics.blacklist);
if (!match.IsMatch(path))
return;
int64_t now = chrono::duration_cast<chrono::milliseconds>(
chrono::high_resolution_clock::now().time_since_epoch())
.count();
bool flag = false;
{
std::lock_guard lock(diag_mutex);
int64_t &next = next_diag[path];
auto &d = g_config->diagnostics;
if (next <= now ||
now - next > std::max(d.onChange, std::max(d.onChange, d.onSave))) {
next = now + debounce;
flag = true;
}
}
if (flag)
diagnostic_request_.PushBack({path, now + debounce, debounce}, false);
}
void CompletionManager::NotifyView(const std::string &path) {
// Only reparse the file if we create a new CompletionSession.
if (EnsureCompletionOrCreatePreloadSession(path))
2018-09-11 23:42:04 +00:00
preload_requests_.PushBack(PreloadRequest{path}, true);
}
void CompletionManager::NotifySave(const std::string &filename) {
EnsureCompletionOrCreatePreloadSession(filename);
2018-09-11 23:42:04 +00:00
preload_requests_.PushBack(PreloadRequest{filename}, true);
}
void CompletionManager::OnClose(const std::string &filename) {
std::lock_guard<std::mutex> lock(sessions_lock_);
preloads.TryTake(filename);
sessions.TryTake(filename);
}
bool CompletionManager::EnsureCompletionOrCreatePreloadSession(
const std::string &path) {
std::lock_guard<std::mutex> lock(sessions_lock_);
if (preloads.TryGet(path) || sessions.TryGet(path))
return false;
// No CompletionSession, create new one.
2018-08-29 05:49:53 +00:00
auto session = std::make_shared<ccls::CompletionSession>(
project_->FindCompilationEntryForFile(path), working_files_, PCH);
if (session->file.filename != path) {
session->inferred = true;
session->file.filename = path;
}
preloads.Insert(path, session);
LOG_S(INFO) << "create preload session for " << path;
return true;
}
2018-08-29 05:49:53 +00:00
std::shared_ptr<ccls::CompletionSession>
CompletionManager::TryGetSession(const std::string &path, bool preload,
bool *is_open) {
std::lock_guard<std::mutex> lock(sessions_lock_);
std::shared_ptr<ccls::CompletionSession> session = preloads.TryGet(path);
if (session) {
if (!preload) {
preloads.TryTake(path);
sessions.Insert(path, session);
if (is_open)
*is_open = true;
}
return session;
}
2017-06-10 01:02:48 +00:00
session = sessions.TryGet(path);
if (!session && !preload) {
2018-08-29 05:49:53 +00:00
session = std::make_shared<ccls::CompletionSession>(
project_->FindCompilationEntryForFile(path), working_files_, PCH);
sessions.Insert(path, session);
LOG_S(INFO) << "create session for " << path;
if (is_open)
*is_open = true;
}
2017-06-10 01:02:48 +00:00
2018-08-29 05:49:53 +00:00
return session;
2017-06-10 01:02:48 +00:00
}
void CompletionManager::FlushAllSessions() {
2018-05-27 19:24:56 +00:00
LOG_S(INFO) << "flush all clang complete sessions";
std::lock_guard<std::mutex> lock(sessions_lock_);
preloads.Clear();
sessions.Clear();
}