ccls/src/clang_complete.cc

517 lines
17 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"
#include "clang_utils.h"
2018-05-27 19:24:56 +00:00
#include "filesystem.hh"
#include "log.hh"
2017-05-10 04:00:05 +00:00
#include "platform.h"
2018-07-16 05:49:32 +00:00
#include <clang/Frontend/CompilerInstance.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 <thread>
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;
void Flush() {
if (!last)
return;
bool mentions = last->inside_main || last->edits.size();
if (!mentions)
for (auto &N : last->notes)
if (N.inside_main)
mentions = true;
if (mentions)
output.push_back(std::move(*last));
last.reset();
}
public:
std::vector<Diag> Take() {
return std::move(output);
}
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();
bool inside_main = SM.isInMainFile(L);
auto fillDiagBase = [&](DiagBase &d) {
llvm::SmallString<64> Message;
Info.FormatDiagnostic(Message);
d.range =
FromCharSourceRange(SM, *LangOpts, DiagnosticRange(Info, *LangOpts));
d.message = Message.str();
d.inside_main = inside_main;
d.file = SM.getFilename(Info.getLocation());
d.level = Level;
d.category = DiagnosticIDs::getCategoryNumberForDiag(Info.getID());
};
auto addFix = [&](bool SyntheticMessage) -> bool {
if (!inside_main)
return false;
for (const FixItHint &FixIt : Info.getFixItHints()) {
if (!SM.isInMainFile(FixIt.RemoveRange.getBegin()))
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) {
2018-08-29 05:49:53 +00:00
for (auto &file : snapshot.files) {
Bufs.push_back(llvm::MemoryBuffer::getMemBuffer(file.content));
if (file.filename == session.file.filename) {
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
} else {
CI->getPreprocessorOpts().addRemappedFile(
CI->getFrontendOpts().Inputs[0].getFile(), Bufs.back().get());
}
} else {
CI->getPreprocessorOpts().addRemappedFile(file.filename,
Bufs.back().get());
}
}
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) {
// Fetching the completion request blocks until we have a request.
2018-09-11 23:42:04 +00:00
auto request = manager->preload_requests_.Dequeue();
2017-06-10 01:02:48 +00:00
// If we don't get a session then that means we don't care about the file
// anymore - abandon the request.
2018-09-11 23:42:04 +00:00
std::shared_ptr<CompletionSession> session = manager->TryGetSession(
request.path, false /*mark_as_completion*/, false /*create_if_needed*/);
if (!session)
continue;
const auto &args = session->file.args;
WorkingFiles::Snapshot snapshot = session->wfiles->AsSnapshot(
{StripFileType(session->file.filename)});
LOG_S(INFO) << "create completion session for " << session->file.filename;
if (std::unique_ptr<CompilerInvocation> CI =
BuildCompilerInvocation(args, session->FS))
session->BuildPreamble(*CI);
2018-09-11 23:42:04 +00:00
if (g_config->diagnostics.onSave) {
lsTextDocumentIdentifier document;
document.uri = lsDocumentUri::FromPath(request.path);
manager->diagnostic_request_.PushBack({document}, true);
}
}
}
void CompletionMain(CompletionManager *completion_manager) {
while (true) {
// Fetching the completion request blocks until we have a request.
std::unique_ptr<CompletionManager::CompletionRequest> request =
completion_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 &&
!completion_manager->completion_request_.IsEmpty()) {
completion_manager->on_dropped_(request->id);
request->Consumer.reset();
request->on_complete(nullptr);
request = completion_manager->completion_request_.Dequeue();
}
std::string path = request->document.uri.GetPath();
2017-09-22 01:14:57 +00:00
std::shared_ptr<CompletionSession> session =
completion_manager->TryGetSession(path, true /*mark_as_completion*/,
true /*create_if_needed*/);
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 =
completion_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());
}
}
void DiagnosticMain(CompletionManager *manager) {
2018-04-14 16:52:17 +00:00
while (true) {
// Fetching the completion request blocks until we have a request.
CompletionManager::DiagnosticRequest request =
2018-07-15 17:10:24 +00:00
manager->diagnostic_request_.Dequeue();
2018-04-14 16:52:17 +00:00
std::string path = request.document.uri.GetPath();
2018-07-15 17:10:24 +00:00
std::shared_ptr<CompletionSession> session = manager->TryGetSession(
path, true /*mark_as_completion*/, true /*create_if_needed*/);
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;
2018-08-29 05:49:53 +00:00
StoreDiags DC;
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
std::vector<lsDiagnostic> ls_diags;
2018-08-29 05:49:53 +00:00
for (auto &d : DC.Take()) {
if (!d.inside_main)
2018-08-09 17:08:14 +00:00
continue;
2018-08-29 05:49:53 +00:00
lsDiagnostic &ls_diag = ls_diags.emplace_back();
ls_diag.range = lsRange{{d.range.start.line, d.range.start.column},
{d.range.end.line, d.range.end.column}};
ls_diag.message = d.message;
switch (d.level) {
case DiagnosticsEngine::Ignored:
// llvm_unreachable
case DiagnosticsEngine::Note:
case DiagnosticsEngine::Remark:
ls_diag.severity = lsDiagnosticSeverity::Information;
2018-07-15 17:10:24 +00:00
continue;
case DiagnosticsEngine::Warning:
ls_diag.severity = lsDiagnosticSeverity::Warning;
break;
case DiagnosticsEngine::Error:
case DiagnosticsEngine::Fatal:
ls_diag.severity = lsDiagnosticSeverity::Error;
}
2018-08-29 05:49:53 +00:00
ls_diag.code = d.category;
ls_diag.fixits_ = d.edits;
2018-04-14 16:52:17 +00:00
}
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) {
std::shared_ptr<PreambleData> OldP = GetPreamble();
2018-08-29 05:49:53 +00:00
std::string content = wfiles->GetContent(file.filename);
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
2018-08-29 05:49:53 +00:00
StoreDiags DC;
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),
preloaded_sessions_(kMaxPreloadedSessions),
completion_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::CodeComplete(
2018-08-09 17:08:14 +00:00
const lsRequestId &id,
const lsTextDocumentPositionParams &completion_location,
const OnComplete &on_complete) {
}
void CompletionManager::DiagnosticsUpdate(
2018-08-09 17:08:14 +00:00
const lsTextDocumentIdentifier &document) {
2018-04-14 16:52:17 +00:00
bool has = false;
2018-08-09 17:08:14 +00:00
diagnostic_request_.Iterate([&](const DiagnosticRequest &request) {
2018-04-14 16:52:17 +00:00
if (request.document.uri == document.uri)
has = true;
});
if (!has)
diagnostic_request_.PushBack(DiagnosticRequest{document},
true /*priority*/);
}
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) {
//
// On save, always reparse.
//
EnsureCompletionOrCreatePreloadSession(filename);
2018-09-11 23:42:04 +00:00
preload_requests_.PushBack(PreloadRequest{filename}, true);
}
void CompletionManager::NotifyClose(const std::string &filename) {
//
// On close, we clear any existing CompletionSession instance.
//
std::lock_guard<std::mutex> lock(sessions_lock_);
// Take and drop. It's okay if we don't actually drop the file, it'll
// eventually get pushed out of the caches as the user opens other files.
auto preloaded_ptr = preloaded_sessions_.TryTake(filename);
LOG_IF_S(INFO, !!preloaded_ptr)
<< "Dropped preloaded-based code completion session for " << filename;
auto completion_ptr = completion_sessions_.TryTake(filename);
LOG_IF_S(INFO, !!completion_ptr)
<< "Dropped completion-based code completion session for " << filename;
// We should never have both a preloaded and completion session.
assert((preloaded_ptr && completion_ptr) == false);
}
bool CompletionManager::EnsureCompletionOrCreatePreloadSession(
const std::string &path) {
std::lock_guard<std::mutex> lock(sessions_lock_);
// Check for an existing CompletionSession.
if (preloaded_sessions_.TryGet(path) ||
completion_sessions_.TryGet(path)) {
return false;
2017-10-23 15:39:33 +00:00
}
// 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);
preloaded_sessions_.Insert(session->file.filename, session);
return true;
}
2018-08-29 05:49:53 +00:00
std::shared_ptr<ccls::CompletionSession>
CompletionManager::TryGetSession(const std::string &path,
bool mark_as_completion,
bool create_if_needed) {
std::lock_guard<std::mutex> lock(sessions_lock_);
// Try to find a preloaded session.
2018-08-29 05:49:53 +00:00
std::shared_ptr<ccls::CompletionSession> preloaded =
preloaded_sessions_.TryGet(path);
2018-08-29 05:49:53 +00:00
if (preloaded) {
// If this request is for a completion, we should move it to
// |completion_sessions|.
if (mark_as_completion) {
preloaded_sessions_.TryTake(path);
completion_sessions_.Insert(path, preloaded);
}
2018-08-29 05:49:53 +00:00
return preloaded;
}
2017-06-10 01:02:48 +00:00
// Try to find a completion session. If none create one.
2018-08-29 05:49:53 +00:00
std::shared_ptr<ccls::CompletionSession> session =
completion_sessions_.TryGet(path);
2018-08-29 05:49:53 +00:00
if (!session && create_if_needed) {
session = std::make_shared<ccls::CompletionSession>(
project_->FindCompilationEntryForFile(path), working_files_, PCH);
completion_sessions_.Insert(path, session);
}
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::FlushSession(const std::string &path) {
2018-08-09 17:08:14 +00:00
std::lock_guard<std::mutex> lock(sessions_lock_);
preloaded_sessions_.TryTake(path);
completion_sessions_.TryTake(path);
}
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_);
2018-05-27 19:24:56 +00:00
preloaded_sessions_.Clear();
completion_sessions_.Clear();
}