Use Clang C++ for completion and diagnostics

This commit is contained in:
Fangrui Song 2018-07-14 16:02:59 -07:00
parent d1c90ec85a
commit c4e22bde23
10 changed files with 456 additions and 561 deletions

View File

@ -5,6 +5,8 @@
#include "log.hh" #include "log.hh"
#include "platform.h" #include "platform.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendDiagnostic.h"
#include <clang/Sema/CodeCompleteConsumer.h> #include <clang/Sema/CodeCompleteConsumer.h>
#include <llvm/ADT/Twine.h> #include <llvm/ADT/Twine.h>
#include <llvm/Support/Threading.h> #include <llvm/Support/Threading.h>
@ -16,25 +18,15 @@ using namespace llvm;
namespace { namespace {
unsigned Flags() {
// TODO: use clang_defaultEditingTranslationUnitOptions()?
return CXTranslationUnit_Incomplete | CXTranslationUnit_KeepGoing |
CXTranslationUnit_CacheCompletionResults |
CXTranslationUnit_PrecompiledPreamble |
CXTranslationUnit_IncludeBriefCommentsInCodeCompletion |
CXTranslationUnit_DetailedPreprocessingRecord |
CXTranslationUnit_CreatePreambleOnFirstParse;
}
std::string StripFileType(const std::string& path) { std::string StripFileType(const std::string& path) {
SmallString<128> Ret; SmallString<128> Ret;
sys::path::append(Ret, sys::path::parent_path(path), sys::path::stem(path)); sys::path::append(Ret, sys::path::parent_path(path), sys::path::stem(path));
return Ret.str(); return Ret.str();
} }
unsigned GetCompletionPriority(const CodeCompletionString& CCS, unsigned GetCompletionPriority(const CodeCompletionString &CCS,
CXCursorKind result_kind, CXCursorKind result_kind,
const std::optional<std::string>& typedText) { const std::optional<std::string> &typedText) {
unsigned priority = CCS.getPriority(); unsigned priority = CCS.getPriority();
if (CCS.getAvailability() != CXAvailability_Available || if (CCS.getAvailability() != CXAvailability_Available ||
result_kind == CXCursor_Destructor || result_kind == CXCursor_Destructor ||
@ -196,35 +188,29 @@ void BuildCompletionItemTexts(std::vector<lsCompletionItem> &out,
// clang-format on // clang-format on
} }
for (auto i = out_first; i < out.size(); ++i) if (Kind != CodeCompletionString::CK_Informative)
out[i].label += text; for (auto i = out_first; i < out.size(); ++i) {
out[i].label += text;
if (!include_snippets && !out[i].parameters_.empty())
continue;
if (Kind == CodeCompletionString::CK_Informative) if (Kind == CodeCompletionString::CK_Placeholder) {
continue; out[i].insertText +=
for (auto i = out_first; i < out.size(); ++i) {
if (!include_snippets && !out[i].parameters_.empty())
continue;
if (Kind == CodeCompletionString::CK_Placeholder) {
out[i].insertText +=
"${" + std::to_string(out[i].parameters_.size()) + ":" + text + "}"; "${" + std::to_string(out[i].parameters_.size()) + ":" + text + "}";
out[i].insertTextFormat = lsInsertTextFormat::Snippet; out[i].insertTextFormat = lsInsertTextFormat::Snippet;
} else { } else {
out[i].insertText += text; out[i].insertText += text;
}
} }
}
if (result_type.size())
for (auto i = out_first; i < out.size(); ++i) {
// ' : ' for variables,
// ' -> ' (trailing return type-like) for functions
out[i].label += (out[i].label == out[i].filterText ? " : " : " -> ");
out[i].label += result_type;
} }
}
if (result_type.empty())
return;
for (auto i = out_first; i < out.size(); ++i) {
// ' : ' for variables,
// ' -> ' (trailing return type-like) for functions
out[i].label += (out[i].label == out[i].filterText ? " : " : " -> ");
out[i].label += result_type;
}
} }
// |do_insert|: if |!do_insert|, do not append strings to |insert| after // |do_insert|: if |!do_insert|, do not append strings to |insert| after
@ -298,56 +284,87 @@ void BuildDetailString(const CodeCompletionString &CCS, lsCompletionItem &item,
} }
} }
class CaptureCompletionResults : public CodeCompleteConsumer {
std::shared_ptr<clang::GlobalCodeCompletionAllocator> Alloc;
CodeCompletionTUInfo CCTUInfo;
public:
std::vector<lsCompletionItem> ls_items;
CaptureCompletionResults(const CodeCompleteOptions &Opts)
: CodeCompleteConsumer(Opts, false),
Alloc(std::make_shared<clang::GlobalCodeCompletionAllocator>()),
CCTUInfo(Alloc) {}
void ProcessCodeCompleteResults(Sema &S,
CodeCompletionContext Context,
CodeCompletionResult *Results,
unsigned NumResults) override {
ls_items.reserve(NumResults);
for (unsigned i = 0; i != NumResults; i++) {
CodeCompletionString *CCS = Results[i].CreateCodeCompletionString(
S, Context, getAllocator(), getCodeCompletionTUInfo(),
includeBriefComments());
if (CCS->getAvailability() == CXAvailability_NotAvailable)
continue;
lsCompletionItem ls_item;
ls_item.kind = GetCompletionKind(Results[i].CursorKind);
if (const char* brief = CCS->getBriefComment())
ls_item.documentation = brief;
// label/detail/filterText/insertText/priority
if (g_config->completion.detailedLabel) {
ls_item.detail = CCS->getParentContextName().str();
size_t first_idx = ls_items.size();
ls_items.push_back(ls_item);
BuildCompletionItemTexts(ls_items, *CCS,
g_config->client.snippetSupport);
for (size_t j = first_idx; j < ls_items.size(); j++) {
if (g_config->client.snippetSupport &&
ls_items[j].insertTextFormat == lsInsertTextFormat::Snippet)
ls_items[j].insertText += "$0";
ls_items[j].priority_ = GetCompletionPriority(
*CCS, Results[i].CursorKind, ls_items[i].filterText);
}
} else {
bool do_insert = true;
int angle_stack = 0;
BuildDetailString(*CCS, ls_item, do_insert,
&ls_item.parameters_,
g_config->client.snippetSupport, angle_stack);
if (g_config->client.snippetSupport &&
ls_item.insertTextFormat == lsInsertTextFormat::Snippet)
ls_item.insertText += "$0";
ls_item.priority_ =
GetCompletionPriority(*CCS, Results[i].CursorKind, ls_item.label);
ls_items.push_back(ls_item);
}
}
}
CodeCompletionAllocator &getAllocator() override { return *Alloc; }
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo;}
};
void TryEnsureDocumentParsed(ClangCompleteManager* manager, void TryEnsureDocumentParsed(ClangCompleteManager* manager,
std::shared_ptr<CompletionSession> session, std::shared_ptr<CompletionSession> session,
std::unique_ptr<ClangTranslationUnit>* tu, std::unique_ptr<ClangTranslationUnit>* tu,
ClangIndex* index,
bool emit_diag) { bool emit_diag) {
// Nothing to do. We already have a translation unit. // Nothing to do. We already have a translation unit.
if (*tu) if (*tu)
return; return;
std::vector<std::string> args = session->file.args; const auto &args = session->file.args;
// -fspell-checking enables FixIts for, ie, misspelled types.
if (!std::count(args.begin(), args.end(), "-fno-spell-checking") &&
!std::count(args.begin(), args.end(), "-fspell-checking")) {
args.push_back("-fspell-checking");
}
WorkingFiles::Snapshot snapshot = session->working_files->AsSnapshot( WorkingFiles::Snapshot snapshot = session->working_files->AsSnapshot(
{StripFileType(session->file.filename)}); {StripFileType(session->file.filename)});
std::vector<CXUnsavedFile> unsaved = snapshot.AsUnsavedFiles();
LOG_S(INFO) << "Creating completion session with arguments " LOG_S(INFO) << "Creating completion session with arguments "
<< StringJoin(args, " "); << StringJoin(args, " ");
*tu = ClangTranslationUnit::Create(index, session->file.filename, args, *tu = ClangTranslationUnit::Create(session->file.filename, args, snapshot);
unsaved, Flags());
// Build diagnostics.
if (g_config->diagnostics.onParse && *tu) {
// If we're emitting diagnostics, do an immediate reparse, otherwise we will
// emit stale/bad diagnostics.
*tu = ClangTranslationUnit::Reparse(std::move(*tu), unsaved);
if (!*tu) {
LOG_S(ERROR) << "Reparsing translation unit for diagnostics failed for "
<< session->file.filename;
return;
}
std::vector<lsDiagnostic> ls_diagnostics;
unsigned num_diagnostics = clang_getNumDiagnostics((*tu)->cx_tu);
for (unsigned i = 0; i < num_diagnostics; ++i) {
std::optional<lsDiagnostic> diagnostic = BuildAndDisposeDiagnostic(
clang_getDiagnostic((*tu)->cx_tu, i), session->file.filename);
// Filter messages like "too many errors emitted, stopping now
// [-ferror-limit=]" which has line = 0 and got subtracted by 1 after
// conversion to lsDiagnostic
if (diagnostic && diagnostic->range.start.line >= 0)
ls_diagnostics.push_back(*diagnostic);
}
manager->on_diagnostic_(session->file.filename, ls_diagnostics);
}
} }
void CompletionPreloadMain(ClangCompleteManager* completion_manager) { void CompletionPreloadMain(ClangCompleteManager* completion_manager) {
@ -374,8 +391,7 @@ void CompletionPreloadMain(ClangCompleteManager* completion_manager) {
continue; continue;
std::unique_ptr<ClangTranslationUnit> parsing; std::unique_ptr<ClangTranslationUnit> parsing;
TryEnsureDocumentParsed(completion_manager, session, &parsing, &tu->index, TryEnsureDocumentParsed(completion_manager, session, &parsing, true);
true);
// Activate new translation unit. // Activate new translation unit.
std::lock_guard<std::mutex> lock(tu->lock); std::lock_guard<std::mutex> lock(tu->lock);
@ -404,84 +420,43 @@ void CompletionQueryMain(ClangCompleteManager* completion_manager) {
true /*create_if_needed*/); true /*create_if_needed*/);
std::lock_guard<std::mutex> lock(session->completion.lock); std::lock_guard<std::mutex> lock(session->completion.lock);
TryEnsureDocumentParsed(completion_manager, session, &session->completion.tu, TryEnsureDocumentParsed(completion_manager, session, &session->completion.tu, false);
&session->completion.index, false);
// It is possible we failed to create the document despite // It is possible we failed to create the document despite
// |TryEnsureDocumentParsed|. // |TryEnsureDocumentParsed|.
if (!session->completion.tu) if (ClangTranslationUnit* tu = session->completion.tu.get()) {
continue; WorkingFiles::Snapshot snapshot =
completion_manager->working_files_->AsSnapshot({StripFileType(path)});
IntrusiveRefCntPtr<FileManager> FileMgr(&tu->Unit->getFileManager());
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions());
IntrusiveRefCntPtr<DiagnosticsEngine> Diag(new DiagnosticsEngine(
IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), &*DiagOpts));
// StoreDiags Diags;
// IntrusiveRefCntPtr<DiagnosticsEngine> DiagE =
// CompilerInstance::createDiagnostics(DiagOpts.get(), &Diags, false);
WorkingFiles::Snapshot snapshot = IntrusiveRefCntPtr<SourceManager> SrcMgr(
completion_manager->working_files_->AsSnapshot({StripFileType(path)}); new SourceManager(*Diag, *FileMgr));
std::vector<CXUnsavedFile> unsaved = snapshot.AsUnsavedFiles(); std::vector<ASTUnit::RemappedFile> Remapped = GetRemapped(snapshot);
SmallVector<StoredDiagnostic, 8> Diagnostics;
SmallVector<const llvm::MemoryBuffer *, 1> TemporaryBuffers;
unsigned const kCompleteOptions = CodeCompleteOptions Opts;
CXCodeComplete_IncludeMacros | CXCodeComplete_IncludeBriefComments; LangOptions LangOpts;
CXCodeCompleteResults* cx_results = clang_codeCompleteAt( Opts.IncludeBriefComments = true;
session->completion.tu->cx_tu, session->file.filename.c_str(), Opts.LoadExternal = false;
request->position.line + 1, request->position.character + 1, Opts.IncludeFixIts = true;
unsaved.data(), (unsigned)unsaved.size(), kCompleteOptions); CaptureCompletionResults capture(Opts);
if (!cx_results) { tu->Unit->CodeComplete(session->file.filename, request->position.line + 1,
request->on_complete({}, false /*is_cached_result*/); request->position.character + 1, Remapped,
continue; /*IncludeMacros=*/true,
/*IncludeCodePatterns=*/false,
/*IncludeBriefComments=*/true, capture, tu->PCHCO,
*Diag, LangOpts, *SrcMgr, *FileMgr, Diagnostics,
TemporaryBuffers);
request->on_complete(capture.ls_items, false /*is_cached_result*/);
// completion_manager->on_diagnostic_(session->file.filename, Diags.take());
} }
std::vector<lsCompletionItem> ls_result;
// this is a guess but can be larger in case of std::optional
// parameters, as they may be expanded into multiple items
ls_result.reserve(cx_results->NumResults);
for (unsigned i = 0; i < cx_results->NumResults; ++i) {
CXCompletionResult& result = cx_results->Results[i];
auto CCS = (CodeCompletionString *)result.CompletionString;
if (CCS->getAvailability() == CXAvailability_NotAvailable)
continue;
lsCompletionItem ls_item;
ls_item.kind = GetCompletionKind(result.CursorKind);
if (const char* brief = CCS->getBriefComment())
ls_item.documentation = brief;
// label/detail/filterText/insertText/priority
if (g_config->completion.detailedLabel) {
ls_item.detail = CCS->getParentContextName().str();
auto first_idx = ls_result.size();
ls_result.push_back(ls_item);
// label/filterText/insertText
BuildCompletionItemTexts(ls_result, *CCS,
g_config->client.snippetSupport);
for (auto i = first_idx; i < ls_result.size(); ++i) {
if (g_config->client.snippetSupport &&
ls_result[i].insertTextFormat == lsInsertTextFormat::Snippet) {
ls_result[i].insertText += "$0";
}
ls_result[i].priority_ = GetCompletionPriority(
*CCS, result.CursorKind, ls_result[i].filterText);
}
} else {
bool do_insert = true;
int angle_stack = 0;
BuildDetailString(*CCS, ls_item, do_insert,
&ls_item.parameters_,
g_config->client.snippetSupport, angle_stack);
if (g_config->client.snippetSupport &&
ls_item.insertTextFormat == lsInsertTextFormat::Snippet)
ls_item.insertText += "$0";
ls_item.priority_ =
GetCompletionPriority(*CCS, result.CursorKind, ls_item.label);
ls_result.push_back(ls_item);
}
}
request->on_complete(ls_result, false /*is_cached_result*/);
// Make sure |ls_results| is destroyed before clearing |cx_results|.
clang_disposeCodeCompleteResults(cx_results);
} }
} }
@ -500,44 +475,72 @@ void DiagnosticQueryMain(ClangCompleteManager* completion_manager) {
// At this point, we must have a translation unit. Block until we have one. // At this point, we must have a translation unit. Block until we have one.
std::lock_guard<std::mutex> lock(session->diagnostics.lock); std::lock_guard<std::mutex> lock(session->diagnostics.lock);
TryEnsureDocumentParsed( TryEnsureDocumentParsed(completion_manager, session,
completion_manager, session, &session->diagnostics.tu, &session->diagnostics.tu,
&session->diagnostics.index, false /*emit_diagnostics*/); false /*emit_diagnostics*/);
// It is possible we failed to create the document despite // It is possible we failed to create the document despite
// |TryEnsureDocumentParsed|. // |TryEnsureDocumentParsed|.
if (!session->diagnostics.tu) ClangTranslationUnit* tu = session->diagnostics.tu.get();
if (!tu)
continue; continue;
WorkingFiles::Snapshot snapshot = WorkingFiles::Snapshot snapshot =
completion_manager->working_files_->AsSnapshot({StripFileType(path)}); completion_manager->working_files_->AsSnapshot({StripFileType(path)});
std::vector<CXUnsavedFile> unsaved = snapshot.AsUnsavedFiles(); llvm::CrashRecoveryContext CRC;
if (tu->Reparse(CRC, snapshot)) {
// Emit diagnostics.
session->diagnostics.tu = ClangTranslationUnit::Reparse(
std::move(session->diagnostics.tu), unsaved);
if (!session->diagnostics.tu) {
LOG_S(ERROR) << "Reparsing translation unit for diagnostics failed for " LOG_S(ERROR) << "Reparsing translation unit for diagnostics failed for "
<< path; << path;
continue; continue;
} }
size_t num_diagnostics = auto &SM = tu->Unit->getSourceManager();
clang_getNumDiagnostics(session->diagnostics.tu->cx_tu); auto &LangOpts = tu->Unit->getLangOpts();
std::vector<lsDiagnostic> ls_diagnostics; std::vector<lsDiagnostic> ls_diags;
ls_diagnostics.reserve(num_diagnostics); for (ASTUnit::stored_diag_iterator I = tu->Unit->stored_diag_begin(),
for (unsigned i = 0; i < num_diagnostics; ++i) { E = tu->Unit->stored_diag_end();
CXDiagnostic cx_diag = I != E; ++I) {
clang_getDiagnostic(session->diagnostics.tu->cx_tu, i); FullSourceLoc FLoc = I->getLocation();
std::optional<lsDiagnostic> diagnostic = SourceRange R;
BuildAndDisposeDiagnostic(cx_diag, path); for (const auto &CR : I->getRanges()) {
// Filter messages like "too many errors emitted, stopping now auto RT = Lexer::makeFileCharRange(CR, SM, LangOpts);
// [-ferror-limit=]" which has line = 0 and got subtracted by 1 after if (SM.isPointWithin(FLoc, RT.getBegin(), RT.getEnd())) {
// conversion to lsDiagnostic R = CR.getAsRange();
if (diagnostic && diagnostic->range.start.line >= 0) break;
ls_diagnostics.push_back(*diagnostic); }
}
Range r = R.isValid() ? FromCharRange(SM, LangOpts, R)
: FromTokenRange(SM, LangOpts, {FLoc, FLoc});
lsDiagnostic ls_diag;
ls_diag.range =
lsRange{{r.start.line, r.start.column}, {r.end.line, r.end.column}};
switch (I->getLevel()) {
case DiagnosticsEngine::Ignored:
// llvm_unreachable
break;
case DiagnosticsEngine::Note:
case DiagnosticsEngine::Remark:
ls_diag.severity = lsDiagnosticSeverity::Information;
break;
case DiagnosticsEngine::Warning:
ls_diag.severity = lsDiagnosticSeverity::Warning;
break;
case DiagnosticsEngine::Error:
case DiagnosticsEngine::Fatal:
ls_diag.severity = lsDiagnosticSeverity::Error;
}
ls_diag.message = I->getMessage().str();
for (const FixItHint &FixIt : I->getFixIts()) {
lsTextEdit edit;
edit.newText = FixIt.CodeToInsert;
r = FromCharRange(SM, LangOpts, FixIt.RemoveRange.getAsRange());
edit.range =
lsRange{{r.start.line, r.start.column}, {r.end.line, r.end.column}};
ls_diag.fixits_.push_back(edit);
}
ls_diags.push_back(ls_diag);
} }
completion_manager->on_diagnostic_(path, ls_diagnostics); completion_manager->on_diagnostic_(path, ls_diags);
} }
} }

View File

@ -19,8 +19,6 @@ struct CompletionSession
: public std::enable_shared_from_this<CompletionSession> { : public std::enable_shared_from_this<CompletionSession> {
// Translation unit for clang. // Translation unit for clang.
struct Tu { struct Tu {
ClangIndex index{0, 0};
// When |tu| was last parsed. // When |tu| was last parsed.
std::optional<std::chrono::time_point<std::chrono::high_resolution_clock>> std::optional<std::chrono::time_point<std::chrono::high_resolution_clock>>
last_parsed_at; last_parsed_at;

View File

@ -4,173 +4,113 @@
#include "log.hh" #include "log.hh"
#include "platform.h" #include "platform.h"
#include "utils.h" #include "utils.h"
#include "working_files.h"
#include <llvm/Support/CrashRecoveryContext.h>
using namespace clang;
#include <assert.h> #include <assert.h>
#include <string.h>
#include <algorithm>
#include <mutex> #include <mutex>
namespace { Range FromSourceRange(const SourceManager &SM, const LangOptions &LangOpts,
SourceRange R, llvm::sys::fs::UniqueID *UniqueID,
void EmitDiagnostics(std::string path, bool token) {
std::vector<const char*> args, SourceLocation BLoc = R.getBegin(), ELoc = R.getEnd();
CXTranslationUnit tu) { std::pair<FileID, unsigned> BInfo = SM.getDecomposedLoc(BLoc);
std::string output = "Fatal errors while trying to parse " + path + "\n"; std::pair<FileID, unsigned> EInfo = SM.getDecomposedLoc(ELoc);
output += if (token)
"Args: " + EInfo.second += Lexer::MeasureTokenLength(ELoc, SM, LangOpts);
StringJoinMap(args, [](const char* arg) { return std::string(arg); }) + unsigned l0 = SM.getLineNumber(BInfo.first, BInfo.second) - 1,
"\n"; c0 = SM.getColumnNumber(BInfo.first, BInfo.second) - 1,
l1 = SM.getLineNumber(EInfo.first, EInfo.second) - 1,
size_t num_diagnostics = clang_getNumDiagnostics(tu); c1 = SM.getColumnNumber(EInfo.first, EInfo.second) - 1;
for (unsigned i = 0; i < num_diagnostics; ++i) { if (l0 > INT16_MAX)
output += " - "; l0 = 0;
if (c0 > INT16_MAX)
CXDiagnostic diagnostic = clang_getDiagnostic(tu, i); c0 = 0;
if (l1 > INT16_MAX)
// Location. l1 = 0;
CXFile file; if (c1 > INT16_MAX)
unsigned int line, column; c1 = 0;
clang_getSpellingLocation(clang_getDiagnosticLocation(diagnostic), &file, if (UniqueID) {
&line, &column, nullptr); if (const FileEntry *F = SM.getFileEntryForID(BInfo.first))
std::string path = FileName(file); *UniqueID = F->getUniqueID();
output += path + ":" + std::to_string(line - 1) + ":" + else
std::to_string(column) + " "; *UniqueID = llvm::sys::fs::UniqueID(0, 0);
// Severity
switch (clang_getDiagnosticSeverity(diagnostic)) {
case CXDiagnostic_Ignored:
case CXDiagnostic_Note:
output += "[info]";
break;
case CXDiagnostic_Warning:
output += "[warning]";
break;
case CXDiagnostic_Error:
output += "[error]";
break;
case CXDiagnostic_Fatal:
output += "[fatal]";
break;
}
// Content.
output += " " + ToString(clang_getDiagnosticSpelling(diagnostic));
clang_disposeDiagnostic(diagnostic);
output += "\n";
} }
return {{int16_t(l0), int16_t(c0)}, {int16_t(l1), int16_t(c1)}};
LOG_S(WARNING) << output;
}
} // namespace
ClangIndex::ClangIndex() : ClangIndex(1, 0) {}
ClangIndex::ClangIndex(int exclude_declarations_from_pch,
int display_diagnostics) {
// llvm::InitializeAllTargets (and possibly others) called by
// clang_createIndex transtively modifies/reads lib/Support/TargetRegistry.cpp
// FirstTarget. There will be a race condition if two threads call
// clang_createIndex concurrently.
static std::mutex mutex_;
std::lock_guard<std::mutex> lock(mutex_);
cx_index =
clang_createIndex(exclude_declarations_from_pch, display_diagnostics);
} }
ClangIndex::~ClangIndex() { Range FromCharRange(const SourceManager &SM, const LangOptions &LangOpts,
clang_disposeIndex(cx_index); SourceRange R,
llvm::sys::fs::UniqueID *UniqueID) {
return FromSourceRange(SM, LangOpts, R, UniqueID, false);
} }
// static Range FromTokenRange(const SourceManager &SM, const LangOptions &LangOpts,
std::unique_ptr<ClangTranslationUnit> ClangTranslationUnit::Create( SourceRange R,
ClangIndex* index, llvm::sys::fs::UniqueID *UniqueID) {
const std::string& filepath, return FromSourceRange(SM, LangOpts, R, UniqueID, true);
const std::vector<std::string>& arguments, }
std::vector<CXUnsavedFile>& unsaved_files,
unsigned flags) {
std::vector<const char*> args;
for (auto& arg : arguments)
args.push_back(arg.c_str());
CXTranslationUnit cx_tu; std::vector<ASTUnit::RemappedFile>
CXErrorCode error_code; GetRemapped(const WorkingFiles::Snapshot &snapshot) {
{ std::vector<ASTUnit::RemappedFile> Remapped;
error_code = clang_parseTranslationUnit2FullArgv( for (auto &file : snapshot.files) {
index->cx_index, nullptr, args.data(), (int)args.size(), std::unique_ptr<llvm::MemoryBuffer> MB =
unsaved_files.data(), (unsigned)unsaved_files.size(), flags, &cx_tu); llvm::MemoryBuffer::getMemBufferCopy(file.content, file.filename);
Remapped.emplace_back(file.filename, MB.release());
} }
return Remapped;
}
if (error_code != CXError_Success && cx_tu) std::unique_ptr<ClangTranslationUnit>
EmitDiagnostics(filepath, args, cx_tu); ClangTranslationUnit::Create(const std::string &filepath,
const std::vector<std::string> &args,
const WorkingFiles::Snapshot &snapshot) {
std::vector<const char *> Args;
for (auto& arg : args)
Args.push_back(arg.c_str());
Args.push_back("-fno-spell-checking");
// We sometimes dump the command to logs and ask the user to run it. Include auto ret = std::make_unique<ClangTranslationUnit>();
// -fsyntax-only so they don't do a full compile. IntrusiveRefCntPtr<DiagnosticsEngine> Diags(
auto make_msg = [&]() { CompilerInstance::createDiagnostics(new DiagnosticOptions));
return "Please try running the following, identify which flag causes the " std::vector<ASTUnit::RemappedFile> Remapped = GetRemapped(snapshot);
"issue, and report a bug. ccls will then filter the flag for you "
" automatically:\n " + ret->PCHCO = std::make_shared<PCHContainerOperations>();
StringJoin(args, " ") + " -fsyntax-only"; std::unique_ptr<ASTUnit> ErrUnit, Unit;
llvm::CrashRecoveryContext CRC;
auto parse = [&]() {
Unit.reset(ASTUnit::LoadFromCommandLine(
Args.data(), Args.data() + Args.size(),
/*PCHContainerOpts=*/ret->PCHCO, Diags,
/*ResourceFilePath=*/"", /*OnlyLocalDecls=*/false,
/*CaptureDiagnostics=*/true, Remapped,
/*RemappedFilesKeepOriginalName=*/true, 1, TU_Prefix,
/*CacheCodeCompletionResults=*/true, true,
/*AllowPCHWithCompilerErrors=*/true, SkipFunctionBodiesScope::None,
/*SingleFileParse=*/false,
/*UserFilesAreVolatile=*/true, false,
ret->PCHCO->getRawReader().getFormat(), &ErrUnit));
}; };
if (!RunSafely(CRC, parse)) {
switch (error_code) { LOG_S(ERROR)
case CXError_Success: << "clang crashed for " << filepath << "\n"
return std::make_unique<ClangTranslationUnit>(cx_tu); << StringJoin(args, " ") + " -fsyntax-only";
case CXError_Failure: return {};
LOG_S(ERROR) << "libclang generic failure for " << filepath << ". "
<< make_msg();
return nullptr;
case CXError_Crashed:
LOG_S(ERROR) << "libclang crashed for " << filepath << ". " << make_msg();
return nullptr;
case CXError_InvalidArguments:
LOG_S(ERROR) << "libclang had invalid arguments for " << filepath << ". "
<< make_msg();
return nullptr;
case CXError_ASTReadError:
LOG_S(ERROR) << "libclang had ast read error for " << filepath << ". "
<< make_msg();
return nullptr;
} }
if (!Unit && !ErrUnit)
return {};
return nullptr; ret->Unit = std::move(Unit);
return ret;
} }
// static int ClangTranslationUnit::Reparse(llvm::CrashRecoveryContext &CRC,
std::unique_ptr<ClangTranslationUnit> ClangTranslationUnit::Reparse( const WorkingFiles::Snapshot &snapshot) {
std::unique_ptr<ClangTranslationUnit> tu, int ret = 1;
std::vector<CXUnsavedFile>& unsaved) { auto parse = [&]() { ret = Unit->Reparse(PCHCO, GetRemapped(snapshot)); };
int error_code = clang_reparseTranslationUnit( (void)RunSafely(CRC, parse);
tu->cx_tu, (unsigned)unsaved.size(), unsaved.data(), return ret;
clang_defaultReparseOptions(tu->cx_tu));
if (error_code != CXError_Success && tu->cx_tu)
EmitDiagnostics("<unknown>", {}, tu->cx_tu);
switch (error_code) {
case CXError_Success:
return tu;
case CXError_Failure:
LOG_S(ERROR) << "libclang reparse generic failure";
return nullptr;
case CXError_Crashed:
LOG_S(ERROR) << "libclang reparse crashed";
return nullptr;
case CXError_InvalidArguments:
LOG_S(ERROR) << "libclang reparse had invalid arguments";
return nullptr;
case CXError_ASTReadError:
LOG_S(ERROR) << "libclang reparse had ast read error";
return nullptr;
}
return nullptr;
}
ClangTranslationUnit::ClangTranslationUnit(CXTranslationUnit tu) : cx_tu(tu) {}
ClangTranslationUnit::~ClangTranslationUnit() {
clang_disposeTranslationUnit(cx_tu);
} }

View File

@ -1,40 +1,45 @@
#pragma once #pragma once
#include "position.h" #include "position.h"
#include "working_files.h"
#include <clang-c/Index.h> #include <clang/Frontend/ASTUnit.h>
#include <clang/Frontend/CompilerInstance.h>
#include <llvm/Support/CrashRecoveryContext.h>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include <stdlib.h>
// Simple RAII wrapper about CXIndex. std::vector<clang::ASTUnit::RemappedFile>
// Note: building a ClangIndex instance acquires a global lock, since libclang GetRemapped(const WorkingFiles::Snapshot &snapshot);
// API does not appear to be thread-safe here.
class ClangIndex { Range FromCharRange(const clang::SourceManager &SM, const clang::LangOptions &LangOpts,
public: clang::SourceRange R,
ClangIndex(); llvm::sys::fs::UniqueID *UniqueID = nullptr);
ClangIndex(int exclude_declarations_from_pch, int display_diagnostics);
~ClangIndex(); Range FromTokenRange(const clang::SourceManager &SM, const clang::LangOptions &LangOpts,
CXIndex cx_index; clang::SourceRange R,
}; llvm::sys::fs::UniqueID *UniqueID = nullptr);
template <typename Fn>
bool RunSafely(llvm::CrashRecoveryContext &CRC, Fn &&fn) {
const char *env = getenv("CCLS_CRASH_RECOVERY");
if (env && strcmp(env, "0") == 0) {
fn();
return true;
}
return CRC.RunSafely(fn);
}
// RAII wrapper around CXTranslationUnit which also makes it much more
// challenging to use a CXTranslationUnit instance that is not correctly
// initialized.
struct ClangTranslationUnit { struct ClangTranslationUnit {
static std::unique_ptr<ClangTranslationUnit> Create( static std::unique_ptr<ClangTranslationUnit>
ClangIndex* index, Create(const std::string &filepath, const std::vector<std::string> &arguments,
const std::string& filepath, const WorkingFiles::Snapshot &snapshot);
const std::vector<std::string>& arguments,
std::vector<CXUnsavedFile>& unsaved_files,
unsigned flags);
static std::unique_ptr<ClangTranslationUnit> Reparse( int Reparse(llvm::CrashRecoveryContext &CRC,
std::unique_ptr<ClangTranslationUnit> tu, const WorkingFiles::Snapshot &snapshot);
std::vector<CXUnsavedFile>& unsaved);
explicit ClangTranslationUnit(CXTranslationUnit tu); std::shared_ptr<clang::PCHContainerOperations> PCHCO;
~ClangTranslationUnit(); std::unique_ptr<clang::ASTUnit> Unit;
CXTranslationUnit cx_tu;
}; };

View File

@ -1,113 +1,12 @@
#include "clang_utils.h" #include "clang_utils.h"
#include "filesystem.hh"
#include "platform.h" #include "platform.h"
#include "filesystem.hh" #include <clang/AST/Type.h>
using namespace clang; using namespace clang;
using namespace llvm; using namespace llvm;
namespace {
lsRange GetLsRangeForFixIt(const CXSourceRange& range) {
CXSourceLocation start = clang_getRangeStart(range);
CXSourceLocation end = clang_getRangeEnd(range);
unsigned int start_line, start_column;
clang_getSpellingLocation(start, nullptr, &start_line, &start_column,
nullptr);
unsigned int end_line, end_column;
clang_getSpellingLocation(end, nullptr, &end_line, &end_column, nullptr);
return lsRange{lsPosition{int(start_line) - 1, int(start_column) - 1},
lsPosition{int(end_line) - 1, int(end_column)}};
}
} // namespace
// See clang_formatDiagnostic
std::optional<lsDiagnostic> BuildAndDisposeDiagnostic(CXDiagnostic diagnostic,
const std::string& path) {
// Get diagnostic location.
CXFile file;
unsigned start_line, start_column;
clang_getSpellingLocation(clang_getDiagnosticLocation(diagnostic), &file,
&start_line, &start_column, nullptr);
if (file && path != FileName(file)) {
clang_disposeDiagnostic(diagnostic);
return std::nullopt;
}
unsigned end_line = start_line, end_column = start_column,
num_ranges = clang_getDiagnosticNumRanges(diagnostic);
for (unsigned i = 0; i < num_ranges; i++) {
CXFile file0, file1;
unsigned line0, column0, line1, column1;
CXSourceRange range = clang_getDiagnosticRange(diagnostic, i);
clang_getSpellingLocation(clang_getRangeStart(range), &file0, &line0,
&column0, nullptr);
clang_getSpellingLocation(clang_getRangeEnd(range), &file1, &line1,
&column1, nullptr);
if (file0 != file1 || file0 != file)
continue;
if (line0 < start_line || (line0 == start_line && column0 < start_column)) {
start_line = line0;
start_column = column0;
}
if (line1 > end_line || (line1 == end_line && column1 > end_column)) {
end_line = line1;
end_column = column1;
}
}
// Build diagnostic.
lsDiagnostic ls_diagnostic;
ls_diagnostic.range = lsRange{{int(start_line) - 1, int(start_column) - 1},
{int(end_line) - 1, int(end_column) - 1}};
ls_diagnostic.message = ToString(clang_getDiagnosticSpelling(diagnostic));
// Append the flag that enables this diagnostic, ie, [-Wswitch]
std::string enabling_flag =
ToString(clang_getDiagnosticOption(diagnostic, nullptr));
if (!enabling_flag.empty())
ls_diagnostic.message += " [" + enabling_flag + "]";
ls_diagnostic.code = clang_getDiagnosticCategory(diagnostic);
switch (clang_getDiagnosticSeverity(diagnostic)) {
case CXDiagnostic_Ignored:
// llvm_unreachable
break;
case CXDiagnostic_Note:
ls_diagnostic.severity = lsDiagnosticSeverity::Information;
break;
case CXDiagnostic_Warning:
ls_diagnostic.severity = lsDiagnosticSeverity::Warning;
break;
case CXDiagnostic_Error:
case CXDiagnostic_Fatal:
ls_diagnostic.severity = lsDiagnosticSeverity::Error;
break;
}
// Report fixits
unsigned num_fixits = clang_getDiagnosticNumFixIts(diagnostic);
for (unsigned i = 0; i < num_fixits; ++i) {
CXSourceRange replacement_range;
CXString text = clang_getDiagnosticFixIt(diagnostic, i, &replacement_range);
lsTextEdit edit;
edit.newText = ToString(text);
edit.range = GetLsRangeForFixIt(replacement_range);
ls_diagnostic.fixits_.push_back(edit);
}
clang_disposeDiagnostic(diagnostic);
return ls_diagnostic;
}
std::string FileName(CXFile file) { std::string FileName(CXFile file) {
std::string ret; std::string ret;
// clang > 6 // clang > 6
@ -153,34 +52,145 @@ std::string ToString(CXCursorKind kind) {
return ToString(clang_getCursorKindSpelling(kind)); return ToString(clang_getCursorKindSpelling(kind));
} }
const char* ClangBuiltinTypeName(CXTypeKind kind) { // clang::BuiltinType::getName without PrintingPolicy
switch (kind) { const char* ClangBuiltinTypeName(int kind) {
// clang-format off switch (BuiltinType::Kind(kind)) {
case CXType_Bool: return "bool"; case BuiltinType::Void:
case CXType_Char_U: return "char"; return "void";
case CXType_UChar: return "unsigned char"; case BuiltinType::Bool:
case CXType_Char16: return "char16_t"; return "bool";
case CXType_Char32: return "char32_t"; case BuiltinType::Char_S:
case CXType_UShort: return "unsigned short"; return "char";
case CXType_UInt: return "unsigned int"; case BuiltinType::Char_U:
case CXType_ULong: return "unsigned long"; return "char";
case CXType_ULongLong: return "unsigned long long"; case BuiltinType::SChar:
case CXType_UInt128: return "unsigned __int128"; return "signed char";
case CXType_Char_S: return "char"; case BuiltinType::Short:
case CXType_SChar: return "signed char"; return "short";
case CXType_WChar: return "wchar_t"; case BuiltinType::Int:
case CXType_Short: return "short"; return "int";
case CXType_Int: return "int"; case BuiltinType::Long:
case CXType_Long: return "long"; return "long";
case CXType_LongLong: return "long long"; case BuiltinType::LongLong:
case CXType_Int128: return "__int128"; return "long long";
case CXType_Float: return "float"; case BuiltinType::Int128:
case CXType_Double: return "double"; return "__int128";
case CXType_LongDouble: return "long double"; case BuiltinType::UChar:
case CXType_Float128: return "__float128"; return "unsigned char";
case CXType_Half: return "_Float16"; case BuiltinType::UShort:
case CXType_NullPtr: return "nullptr"; return "unsigned short";
default: return ""; case BuiltinType::UInt:
// clang-format on return "unsigned int";
case BuiltinType::ULong:
return "unsigned long";
case BuiltinType::ULongLong:
return "unsigned long long";
case BuiltinType::UInt128:
return "unsigned __int128";
case BuiltinType::Half:
return "__fp16";
case BuiltinType::Float:
return "float";
case BuiltinType::Double:
return "double";
case BuiltinType::LongDouble:
return "long double";
case BuiltinType::ShortAccum:
return "short _Accum";
case BuiltinType::Accum:
return "_Accum";
case BuiltinType::LongAccum:
return "long _Accum";
case BuiltinType::UShortAccum:
return "unsigned short _Accum";
case BuiltinType::UAccum:
return "unsigned _Accum";
case BuiltinType::ULongAccum:
return "unsigned long _Accum";
case BuiltinType::BuiltinType::ShortFract:
return "short _Fract";
case BuiltinType::BuiltinType::Fract:
return "_Fract";
case BuiltinType::BuiltinType::LongFract:
return "long _Fract";
case BuiltinType::BuiltinType::UShortFract:
return "unsigned short _Fract";
case BuiltinType::BuiltinType::UFract:
return "unsigned _Fract";
case BuiltinType::BuiltinType::ULongFract:
return "unsigned long _Fract";
case BuiltinType::BuiltinType::SatShortAccum:
return "_Sat short _Accum";
case BuiltinType::BuiltinType::SatAccum:
return "_Sat _Accum";
case BuiltinType::BuiltinType::SatLongAccum:
return "_Sat long _Accum";
case BuiltinType::BuiltinType::SatUShortAccum:
return "_Sat unsigned short _Accum";
case BuiltinType::BuiltinType::SatUAccum:
return "_Sat unsigned _Accum";
case BuiltinType::BuiltinType::SatULongAccum:
return "_Sat unsigned long _Accum";
case BuiltinType::BuiltinType::SatShortFract:
return "_Sat short _Fract";
case BuiltinType::BuiltinType::SatFract:
return "_Sat _Fract";
case BuiltinType::BuiltinType::SatLongFract:
return "_Sat long _Fract";
case BuiltinType::BuiltinType::SatUShortFract:
return "_Sat unsigned short _Fract";
case BuiltinType::BuiltinType::SatUFract:
return "_Sat unsigned _Fract";
case BuiltinType::BuiltinType::SatULongFract:
return "_Sat unsigned long _Fract";
case BuiltinType::Float16:
return "_Float16";
case BuiltinType::Float128:
return "__float128";
case BuiltinType::WChar_S:
case BuiltinType::WChar_U:
return "wchar_t";
case BuiltinType::Char8:
return "char8_t";
case BuiltinType::Char16:
return "char16_t";
case BuiltinType::Char32:
return "char32_t";
case BuiltinType::NullPtr:
return "nullptr_t";
case BuiltinType::Overload:
return "<overloaded function type>";
case BuiltinType::BoundMember:
return "<bound member function type>";
case BuiltinType::PseudoObject:
return "<pseudo-object type>";
case BuiltinType::Dependent:
return "<dependent type>";
case BuiltinType::UnknownAny:
return "<unknown type>";
case BuiltinType::ARCUnbridgedCast:
return "<ARC unbridged cast type>";
case BuiltinType::BuiltinFn:
return "<builtin fn type>";
case BuiltinType::ObjCId:
return "id";
case BuiltinType::ObjCClass:
return "Class";
case BuiltinType::ObjCSel:
return "SEL";
case BuiltinType::OCLSampler:
return "sampler_t";
case BuiltinType::OCLEvent:
return "event_t";
case BuiltinType::OCLClkEvent:
return "clk_event_t";
case BuiltinType::OCLQueue:
return "queue_t";
case BuiltinType::OCLReserveID:
return "reserve_id_t";
case BuiltinType::OMPArraySection:
return "<OpenMP array section type>";
default:
return "";
} }
} }

View File

@ -8,9 +8,6 @@
#include <optional> #include <optional>
#include <vector> #include <vector>
std::optional<lsDiagnostic> BuildAndDisposeDiagnostic(CXDiagnostic diagnostic,
const std::string& path);
// Returns the absolute path to |file|. // Returns the absolute path to |file|.
std::string FileName(CXFile file); std::string FileName(CXFile file);
std::string FileName(const clang::FileEntry& file); std::string FileName(const clang::FileEntry& file);
@ -19,4 +16,4 @@ std::string ToString(CXString cx_string);
std::string ToString(CXCursorKind cursor_kind); std::string ToString(CXCursorKind cursor_kind);
const char* ClangBuiltinTypeName(CXTypeKind); const char* ClangBuiltinTypeName(int);

View File

@ -1,5 +1,6 @@
#include "indexer.h" #include "indexer.h"
#include "clang_tu.h"
#include "log.hh" #include "log.hh"
#include "platform.h" #include "platform.h"
#include "serializer.h" #include "serializer.h"
@ -83,43 +84,6 @@ StringRef GetSourceInRange(const SourceManager &SM, const LangOptions &LangOpts,
BInfo.second); BInfo.second);
} }
Range FromSourceRange(const SourceManager &SM, const LangOptions &LangOpts,
SourceRange R, llvm::sys::fs::UniqueID *UniqueID,
bool token) {
SourceLocation BLoc = R.getBegin(), ELoc = R.getEnd();
std::pair<FileID, unsigned> BInfo = SM.getDecomposedLoc(BLoc);
std::pair<FileID, unsigned> EInfo = SM.getDecomposedLoc(ELoc);
if (token)
EInfo.second += Lexer::MeasureTokenLength(ELoc, SM, LangOpts);
unsigned l0 = SM.getLineNumber(BInfo.first, BInfo.second) - 1,
c0 = SM.getColumnNumber(BInfo.first, BInfo.second) - 1,
l1 = SM.getLineNumber(EInfo.first, EInfo.second) - 1,
c1 = SM.getColumnNumber(EInfo.first, EInfo.second) - 1;
if (l0 > INT16_MAX) l0 = 0;
if (c0 > INT16_MAX) c0 = 0;
if (l1 > INT16_MAX) l1 = 0;
if (c1 > INT16_MAX) c1 = 0;
if (UniqueID) {
if (const FileEntry *F = SM.getFileEntryForID(BInfo.first))
*UniqueID = F->getUniqueID();
else
*UniqueID = llvm::sys::fs::UniqueID(0, 0);
}
return {{int16_t(l0), int16_t(c0)}, {int16_t(l1), int16_t(c1)}};
}
Range FromCharRange(const SourceManager &SM, const LangOptions &LangOpts,
SourceRange R,
llvm::sys::fs::UniqueID *UniqueID = nullptr) {
return FromSourceRange(SM, LangOpts, R, UniqueID, false);
}
Range FromTokenRange(const SourceManager &SM, const LangOptions &LangOpts,
SourceRange R,
llvm::sys::fs::UniqueID *UniqueID = nullptr) {
return FromSourceRange(SM, LangOpts, R, UniqueID, true);
}
SymbolKind GetSymbolKind(const Decl* D) { SymbolKind GetSymbolKind(const Decl* D) {
switch (D->getKind()) { switch (D->getKind()) {
case Decl::TranslationUnit: case Decl::TranslationUnit:
@ -1154,29 +1118,20 @@ std::vector<std::unique_ptr<IndexFile>> Index(
DataConsumer, IndexOpts, std::make_unique<IndexFrontendAction>(param)); DataConsumer, IndexOpts, std::make_unique<IndexFrontendAction>(param));
DiagnosticErrorTrap DiagTrap(*Diags); DiagnosticErrorTrap DiagTrap(*Diags);
bool success = false;
llvm::CrashRecoveryContext CRC; llvm::CrashRecoveryContext CRC;
{ auto compile = [&]() {
auto compile = [&]() { ASTUnit::LoadFromCompilerInvocationAction(
success = ASTUnit::LoadFromCompilerInvocationAction( std::move(CI), PCHCO, Diags, IndexAction.get(), Unit.get(),
std::move(CI), PCHCO, Diags, IndexAction.get(), Unit.get(), /*Persistent=*/true, /*ResourceDir=*/"",
/*Persistent=*/true, /*ResourceDir=*/"", /*OnlyLocalDecls=*/true,
/*OnlyLocalDecls=*/true, /*CaptureDiagnostics=*/true, 0, false, false, true);
/*CaptureDiagnostics=*/true, 0, false, false, true); };
}; if (!RunSafely(CRC, compile)) {
const char *env = getenv("CCLS_CRASH_RECOVERY"); LOG_S(ERROR) << "clang crashed for " << file;
if (env && strcmp(env, "0") == 0)
compile();
else
CRC.RunSafely(compile);
}
if (!Unit) {
LOG_S(ERROR) << "failed to index " << file;
return {}; return {};
} }
if (!success) { if (!Unit) {
LOG_S(ERROR) << "clang crashed for " << file; LOG_S(ERROR) << "failed to index " << file;
return {}; return {};
} }

View File

@ -51,6 +51,8 @@ struct Handler_TextDocumentDidOpen
include_complete->AddFile(working_file->filename); include_complete->AddFile(working_file->filename);
clang_complete->NotifyView(path); clang_complete->NotifyView(path);
if (g_config->diagnostics.onParse)
clang_complete->DiagnosticsUpdate({params.textDocument.uri});
if (params.args.size()) if (params.args.size())
project->SetFlagsForFile(params.args, path); project->SetFlagsForFile(params.args, path);

View File

@ -195,20 +195,6 @@ std::optional<int> FindMatchingLine(const std::vector<std::string>& index_lines,
} // namespace } // namespace
std::vector<CXUnsavedFile> WorkingFiles::Snapshot::AsUnsavedFiles() const {
std::vector<CXUnsavedFile> result;
result.reserve(files.size());
for (auto& file : files) {
CXUnsavedFile unsaved;
unsaved.Filename = file.filename.c_str();
unsaved.Contents = file.content.c_str();
unsaved.Length = (unsigned long)file.content.size();
result.push_back(unsaved);
}
return result;
}
WorkingFile::WorkingFile(const std::string& filename, WorkingFile::WorkingFile(const std::string& filename,
const std::string& buffer_content) const std::string& buffer_content)
: filename(filename), buffer_content(buffer_content) { : filename(filename), buffer_content(buffer_content) {

View File

@ -84,7 +84,6 @@ struct WorkingFiles {
std::string content; std::string content;
}; };
std::vector<CXUnsavedFile> AsUnsavedFiles() const;
std::vector<File> files; std::vector<File> files;
}; };