From c4e22bde23dcc149ef52011e334c7fdeddc79d79 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Sat, 14 Jul 2018 16:02:59 -0700 Subject: [PATCH] Use Clang C++ for completion and diagnostics --- src/clang_complete.cc | 349 ++++++++++++++------------- src/clang_complete.h | 2 - src/clang_tu.cc | 242 +++++++------------ src/clang_tu.h | 59 ++--- src/clang_utils.cc | 274 +++++++++++---------- src/clang_utils.h | 5 +- src/indexer.cc | 69 +----- src/messages/textDocument_didOpen.cc | 2 + src/working_files.cc | 14 -- src/working_files.h | 1 - 10 files changed, 456 insertions(+), 561 deletions(-) diff --git a/src/clang_complete.cc b/src/clang_complete.cc index 2d0fb251..42813cc8 100644 --- a/src/clang_complete.cc +++ b/src/clang_complete.cc @@ -5,6 +5,8 @@ #include "log.hh" #include "platform.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendDiagnostic.h" #include #include #include @@ -16,25 +18,15 @@ using namespace llvm; 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) { SmallString<128> Ret; sys::path::append(Ret, sys::path::parent_path(path), sys::path::stem(path)); return Ret.str(); } -unsigned GetCompletionPriority(const CodeCompletionString& CCS, +unsigned GetCompletionPriority(const CodeCompletionString &CCS, CXCursorKind result_kind, - const std::optional& typedText) { + const std::optional &typedText) { unsigned priority = CCS.getPriority(); if (CCS.getAvailability() != CXAvailability_Available || result_kind == CXCursor_Destructor || @@ -196,35 +188,29 @@ void BuildCompletionItemTexts(std::vector &out, // clang-format on } - for (auto i = out_first; i < out.size(); ++i) - out[i].label += text; + if (Kind != CodeCompletionString::CK_Informative) + 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) - continue; - - 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 += + if (Kind == CodeCompletionString::CK_Placeholder) { + out[i].insertText += "${" + std::to_string(out[i].parameters_.size()) + ":" + text + "}"; - out[i].insertTextFormat = lsInsertTextFormat::Snippet; - } else { - out[i].insertText += text; + out[i].insertTextFormat = lsInsertTextFormat::Snippet; + } else { + 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 @@ -298,56 +284,87 @@ void BuildDetailString(const CodeCompletionString &CCS, lsCompletionItem &item, } } +class CaptureCompletionResults : public CodeCompleteConsumer { + std::shared_ptr Alloc; + CodeCompletionTUInfo CCTUInfo; + +public: + std::vector ls_items; + + CaptureCompletionResults(const CodeCompleteOptions &Opts) + : CodeCompleteConsumer(Opts, false), + Alloc(std::make_shared()), + 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, std::shared_ptr session, std::unique_ptr* tu, - ClangIndex* index, bool emit_diag) { // Nothing to do. We already have a translation unit. if (*tu) return; - std::vector 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"); - } - + const auto &args = session->file.args; WorkingFiles::Snapshot snapshot = session->working_files->AsSnapshot( {StripFileType(session->file.filename)}); - std::vector unsaved = snapshot.AsUnsavedFiles(); LOG_S(INFO) << "Creating completion session with arguments " << StringJoin(args, " "); - *tu = ClangTranslationUnit::Create(index, session->file.filename, args, - 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 ls_diagnostics; - unsigned num_diagnostics = clang_getNumDiagnostics((*tu)->cx_tu); - for (unsigned i = 0; i < num_diagnostics; ++i) { - std::optional 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); - } + *tu = ClangTranslationUnit::Create(session->file.filename, args, snapshot); } void CompletionPreloadMain(ClangCompleteManager* completion_manager) { @@ -374,8 +391,7 @@ void CompletionPreloadMain(ClangCompleteManager* completion_manager) { continue; std::unique_ptr parsing; - TryEnsureDocumentParsed(completion_manager, session, &parsing, &tu->index, - true); + TryEnsureDocumentParsed(completion_manager, session, &parsing, true); // Activate new translation unit. std::lock_guard lock(tu->lock); @@ -404,84 +420,43 @@ void CompletionQueryMain(ClangCompleteManager* completion_manager) { true /*create_if_needed*/); std::lock_guard lock(session->completion.lock); - TryEnsureDocumentParsed(completion_manager, session, &session->completion.tu, - &session->completion.index, false); + TryEnsureDocumentParsed(completion_manager, session, &session->completion.tu, false); // It is possible we failed to create the document despite // |TryEnsureDocumentParsed|. - if (!session->completion.tu) - continue; + if (ClangTranslationUnit* tu = session->completion.tu.get()) { + WorkingFiles::Snapshot snapshot = + completion_manager->working_files_->AsSnapshot({StripFileType(path)}); + IntrusiveRefCntPtr FileMgr(&tu->Unit->getFileManager()); + IntrusiveRefCntPtr DiagOpts(new DiagnosticOptions()); + IntrusiveRefCntPtr Diag(new DiagnosticsEngine( + IntrusiveRefCntPtr(new DiagnosticIDs), &*DiagOpts)); + // StoreDiags Diags; + // IntrusiveRefCntPtr DiagE = + // CompilerInstance::createDiagnostics(DiagOpts.get(), &Diags, false); - WorkingFiles::Snapshot snapshot = - completion_manager->working_files_->AsSnapshot({StripFileType(path)}); - std::vector unsaved = snapshot.AsUnsavedFiles(); + IntrusiveRefCntPtr SrcMgr( + new SourceManager(*Diag, *FileMgr)); + std::vector Remapped = GetRemapped(snapshot); + SmallVector Diagnostics; + SmallVector TemporaryBuffers; - unsigned const kCompleteOptions = - CXCodeComplete_IncludeMacros | CXCodeComplete_IncludeBriefComments; - CXCodeCompleteResults* cx_results = clang_codeCompleteAt( - session->completion.tu->cx_tu, session->file.filename.c_str(), - request->position.line + 1, request->position.character + 1, - unsaved.data(), (unsigned)unsaved.size(), kCompleteOptions); - if (!cx_results) { - request->on_complete({}, false /*is_cached_result*/); - continue; + CodeCompleteOptions Opts; + LangOptions LangOpts; + Opts.IncludeBriefComments = true; + Opts.LoadExternal = false; + Opts.IncludeFixIts = true; + CaptureCompletionResults capture(Opts); + tu->Unit->CodeComplete(session->file.filename, request->position.line + 1, + request->position.character + 1, Remapped, + /*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 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. std::lock_guard lock(session->diagnostics.lock); - TryEnsureDocumentParsed( - completion_manager, session, &session->diagnostics.tu, - &session->diagnostics.index, false /*emit_diagnostics*/); + TryEnsureDocumentParsed(completion_manager, session, + &session->diagnostics.tu, + false /*emit_diagnostics*/); // It is possible we failed to create the document despite // |TryEnsureDocumentParsed|. - if (!session->diagnostics.tu) + ClangTranslationUnit* tu = session->diagnostics.tu.get(); + if (!tu) continue; WorkingFiles::Snapshot snapshot = completion_manager->working_files_->AsSnapshot({StripFileType(path)}); - std::vector unsaved = snapshot.AsUnsavedFiles(); - - // Emit diagnostics. - session->diagnostics.tu = ClangTranslationUnit::Reparse( - std::move(session->diagnostics.tu), unsaved); - if (!session->diagnostics.tu) { + llvm::CrashRecoveryContext CRC; + if (tu->Reparse(CRC, snapshot)) { LOG_S(ERROR) << "Reparsing translation unit for diagnostics failed for " << path; continue; } - size_t num_diagnostics = - clang_getNumDiagnostics(session->diagnostics.tu->cx_tu); - std::vector ls_diagnostics; - ls_diagnostics.reserve(num_diagnostics); - for (unsigned i = 0; i < num_diagnostics; ++i) { - CXDiagnostic cx_diag = - clang_getDiagnostic(session->diagnostics.tu->cx_tu, i); - std::optional diagnostic = - BuildAndDisposeDiagnostic(cx_diag, path); - // 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); + auto &SM = tu->Unit->getSourceManager(); + auto &LangOpts = tu->Unit->getLangOpts(); + std::vector ls_diags; + for (ASTUnit::stored_diag_iterator I = tu->Unit->stored_diag_begin(), + E = tu->Unit->stored_diag_end(); + I != E; ++I) { + FullSourceLoc FLoc = I->getLocation(); + SourceRange R; + for (const auto &CR : I->getRanges()) { + auto RT = Lexer::makeFileCharRange(CR, SM, LangOpts); + if (SM.isPointWithin(FLoc, RT.getBegin(), RT.getEnd())) { + R = CR.getAsRange(); + break; + } + } + 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); } } diff --git a/src/clang_complete.h b/src/clang_complete.h index af824df1..d9874c91 100644 --- a/src/clang_complete.h +++ b/src/clang_complete.h @@ -19,8 +19,6 @@ struct CompletionSession : public std::enable_shared_from_this { // Translation unit for clang. struct Tu { - ClangIndex index{0, 0}; - // When |tu| was last parsed. std::optional> last_parsed_at; diff --git a/src/clang_tu.cc b/src/clang_tu.cc index e98d0453..5411c923 100644 --- a/src/clang_tu.cc +++ b/src/clang_tu.cc @@ -4,173 +4,113 @@ #include "log.hh" #include "platform.h" #include "utils.h" +#include "working_files.h" + +#include +using namespace clang; #include -#include -#include #include -namespace { - -void EmitDiagnostics(std::string path, - std::vector args, - CXTranslationUnit tu) { - std::string output = "Fatal errors while trying to parse " + path + "\n"; - output += - "Args: " + - StringJoinMap(args, [](const char* arg) { return std::string(arg); }) + - "\n"; - - size_t num_diagnostics = clang_getNumDiagnostics(tu); - for (unsigned i = 0; i < num_diagnostics; ++i) { - output += " - "; - - CXDiagnostic diagnostic = clang_getDiagnostic(tu, i); - - // Location. - CXFile file; - unsigned int line, column; - clang_getSpellingLocation(clang_getDiagnosticLocation(diagnostic), &file, - &line, &column, nullptr); - std::string path = FileName(file); - output += path + ":" + std::to_string(line - 1) + ":" + - std::to_string(column) + " "; - - // 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"; +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 BInfo = SM.getDecomposedLoc(BLoc); + std::pair 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); } - - 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 lock(mutex_); - - cx_index = - clang_createIndex(exclude_declarations_from_pch, display_diagnostics); + return {{int16_t(l0), int16_t(c0)}, {int16_t(l1), int16_t(c1)}}; } -ClangIndex::~ClangIndex() { - clang_disposeIndex(cx_index); +Range FromCharRange(const SourceManager &SM, const LangOptions &LangOpts, + SourceRange R, + llvm::sys::fs::UniqueID *UniqueID) { + return FromSourceRange(SM, LangOpts, R, UniqueID, false); } -// static -std::unique_ptr ClangTranslationUnit::Create( - ClangIndex* index, - const std::string& filepath, - const std::vector& arguments, - std::vector& unsaved_files, - unsigned flags) { - std::vector args; - for (auto& arg : arguments) - args.push_back(arg.c_str()); +Range FromTokenRange(const SourceManager &SM, const LangOptions &LangOpts, + SourceRange R, + llvm::sys::fs::UniqueID *UniqueID) { + return FromSourceRange(SM, LangOpts, R, UniqueID, true); +} - CXTranslationUnit cx_tu; - CXErrorCode error_code; - { - error_code = clang_parseTranslationUnit2FullArgv( - index->cx_index, nullptr, args.data(), (int)args.size(), - unsaved_files.data(), (unsigned)unsaved_files.size(), flags, &cx_tu); +std::vector +GetRemapped(const WorkingFiles::Snapshot &snapshot) { + std::vector Remapped; + for (auto &file : snapshot.files) { + std::unique_ptr MB = + llvm::MemoryBuffer::getMemBufferCopy(file.content, file.filename); + Remapped.emplace_back(file.filename, MB.release()); } + return Remapped; +} - if (error_code != CXError_Success && cx_tu) - EmitDiagnostics(filepath, args, cx_tu); +std::unique_ptr +ClangTranslationUnit::Create(const std::string &filepath, + const std::vector &args, + const WorkingFiles::Snapshot &snapshot) { + std::vector 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 - // -fsyntax-only so they don't do a full compile. - auto make_msg = [&]() { - return "Please try running the following, identify which flag causes the " - "issue, and report a bug. ccls will then filter the flag for you " - " automatically:\n " + - StringJoin(args, " ") + " -fsyntax-only"; + auto ret = std::make_unique(); + IntrusiveRefCntPtr Diags( + CompilerInstance::createDiagnostics(new DiagnosticOptions)); + std::vector Remapped = GetRemapped(snapshot); + + ret->PCHCO = std::make_shared(); + std::unique_ptr 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)); }; - - switch (error_code) { - case CXError_Success: - return std::make_unique(cx_tu); - case CXError_Failure: - 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 (!RunSafely(CRC, parse)) { + LOG_S(ERROR) + << "clang crashed for " << filepath << "\n" + << StringJoin(args, " ") + " -fsyntax-only"; + return {}; } + if (!Unit && !ErrUnit) + return {}; - return nullptr; + ret->Unit = std::move(Unit); + return ret; } -// static -std::unique_ptr ClangTranslationUnit::Reparse( - std::unique_ptr tu, - std::vector& unsaved) { - int error_code = clang_reparseTranslationUnit( - tu->cx_tu, (unsigned)unsaved.size(), unsaved.data(), - clang_defaultReparseOptions(tu->cx_tu)); - - if (error_code != CXError_Success && tu->cx_tu) - EmitDiagnostics("", {}, 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); +int ClangTranslationUnit::Reparse(llvm::CrashRecoveryContext &CRC, + const WorkingFiles::Snapshot &snapshot) { + int ret = 1; + auto parse = [&]() { ret = Unit->Reparse(PCHCO, GetRemapped(snapshot)); }; + (void)RunSafely(CRC, parse); + return ret; } diff --git a/src/clang_tu.h b/src/clang_tu.h index b363c68b..17b4b0e2 100644 --- a/src/clang_tu.h +++ b/src/clang_tu.h @@ -1,40 +1,45 @@ #pragma once #include "position.h" +#include "working_files.h" -#include +#include +#include +#include #include #include #include +#include -// Simple RAII wrapper about CXIndex. -// Note: building a ClangIndex instance acquires a global lock, since libclang -// API does not appear to be thread-safe here. -class ClangIndex { - public: - ClangIndex(); - ClangIndex(int exclude_declarations_from_pch, int display_diagnostics); - ~ClangIndex(); - CXIndex cx_index; -}; +std::vector +GetRemapped(const WorkingFiles::Snapshot &snapshot); + +Range FromCharRange(const clang::SourceManager &SM, const clang::LangOptions &LangOpts, + clang::SourceRange R, + llvm::sys::fs::UniqueID *UniqueID = nullptr); + +Range FromTokenRange(const clang::SourceManager &SM, const clang::LangOptions &LangOpts, + clang::SourceRange R, + llvm::sys::fs::UniqueID *UniqueID = nullptr); + +template +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 { - static std::unique_ptr Create( - ClangIndex* index, - const std::string& filepath, - const std::vector& arguments, - std::vector& unsaved_files, - unsigned flags); + static std::unique_ptr + Create(const std::string &filepath, const std::vector &arguments, + const WorkingFiles::Snapshot &snapshot); - static std::unique_ptr Reparse( - std::unique_ptr tu, - std::vector& unsaved); + int Reparse(llvm::CrashRecoveryContext &CRC, + const WorkingFiles::Snapshot &snapshot); - explicit ClangTranslationUnit(CXTranslationUnit tu); - ~ClangTranslationUnit(); - - CXTranslationUnit cx_tu; + std::shared_ptr PCHCO; + std::unique_ptr Unit; }; diff --git a/src/clang_utils.cc b/src/clang_utils.cc index 24bbfb89..d1d141ea 100644 --- a/src/clang_utils.cc +++ b/src/clang_utils.cc @@ -1,113 +1,12 @@ #include "clang_utils.h" +#include "filesystem.hh" #include "platform.h" -#include "filesystem.hh" +#include using namespace clang; 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 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 ret; // clang > 6 @@ -153,34 +52,145 @@ std::string ToString(CXCursorKind kind) { return ToString(clang_getCursorKindSpelling(kind)); } -const char* ClangBuiltinTypeName(CXTypeKind kind) { - switch (kind) { - // clang-format off - case CXType_Bool: return "bool"; - case CXType_Char_U: return "char"; - case CXType_UChar: return "unsigned char"; - case CXType_Char16: return "char16_t"; - case CXType_Char32: return "char32_t"; - case CXType_UShort: return "unsigned short"; - case CXType_UInt: return "unsigned int"; - case CXType_ULong: return "unsigned long"; - case CXType_ULongLong: return "unsigned long long"; - case CXType_UInt128: return "unsigned __int128"; - case CXType_Char_S: return "char"; - case CXType_SChar: return "signed char"; - case CXType_WChar: return "wchar_t"; - case CXType_Short: return "short"; - case CXType_Int: return "int"; - case CXType_Long: return "long"; - case CXType_LongLong: return "long long"; - case CXType_Int128: return "__int128"; - case CXType_Float: return "float"; - case CXType_Double: return "double"; - case CXType_LongDouble: return "long double"; - case CXType_Float128: return "__float128"; - case CXType_Half: return "_Float16"; - case CXType_NullPtr: return "nullptr"; - default: return ""; - // clang-format on +// clang::BuiltinType::getName without PrintingPolicy +const char* ClangBuiltinTypeName(int kind) { + switch (BuiltinType::Kind(kind)) { + case BuiltinType::Void: + return "void"; + case BuiltinType::Bool: + return "bool"; + case BuiltinType::Char_S: + return "char"; + case BuiltinType::Char_U: + return "char"; + case BuiltinType::SChar: + return "signed char"; + case BuiltinType::Short: + return "short"; + case BuiltinType::Int: + return "int"; + case BuiltinType::Long: + return "long"; + case BuiltinType::LongLong: + return "long long"; + case BuiltinType::Int128: + return "__int128"; + case BuiltinType::UChar: + return "unsigned char"; + case BuiltinType::UShort: + return "unsigned short"; + case BuiltinType::UInt: + 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 ""; + case BuiltinType::BoundMember: + return ""; + case BuiltinType::PseudoObject: + return ""; + case BuiltinType::Dependent: + return ""; + case BuiltinType::UnknownAny: + return ""; + case BuiltinType::ARCUnbridgedCast: + return ""; + case BuiltinType::BuiltinFn: + return ""; + 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 ""; + default: + return ""; } } diff --git a/src/clang_utils.h b/src/clang_utils.h index 7b7a1da4..7f52eded 100644 --- a/src/clang_utils.h +++ b/src/clang_utils.h @@ -8,9 +8,6 @@ #include #include -std::optional BuildAndDisposeDiagnostic(CXDiagnostic diagnostic, - const std::string& path); - // Returns the absolute path to |file|. std::string FileName(CXFile file); std::string FileName(const clang::FileEntry& file); @@ -19,4 +16,4 @@ std::string ToString(CXString cx_string); std::string ToString(CXCursorKind cursor_kind); -const char* ClangBuiltinTypeName(CXTypeKind); +const char* ClangBuiltinTypeName(int); diff --git a/src/indexer.cc b/src/indexer.cc index 01a67494..0ac6c676 100644 --- a/src/indexer.cc +++ b/src/indexer.cc @@ -1,5 +1,6 @@ #include "indexer.h" +#include "clang_tu.h" #include "log.hh" #include "platform.h" #include "serializer.h" @@ -83,43 +84,6 @@ StringRef GetSourceInRange(const SourceManager &SM, const LangOptions &LangOpts, 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 BInfo = SM.getDecomposedLoc(BLoc); - std::pair 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) { switch (D->getKind()) { case Decl::TranslationUnit: @@ -1154,29 +1118,20 @@ std::vector> Index( DataConsumer, IndexOpts, std::make_unique(param)); DiagnosticErrorTrap DiagTrap(*Diags); - bool success = false; llvm::CrashRecoveryContext CRC; - { - auto compile = [&]() { - success = ASTUnit::LoadFromCompilerInvocationAction( - std::move(CI), PCHCO, Diags, IndexAction.get(), Unit.get(), - /*Persistent=*/true, /*ResourceDir=*/"", - /*OnlyLocalDecls=*/true, - /*CaptureDiagnostics=*/true, 0, false, false, true); - }; - const char *env = getenv("CCLS_CRASH_RECOVERY"); - if (env && strcmp(env, "0") == 0) - compile(); - else - CRC.RunSafely(compile); - } - - if (!Unit) { - LOG_S(ERROR) << "failed to index " << file; + auto compile = [&]() { + ASTUnit::LoadFromCompilerInvocationAction( + std::move(CI), PCHCO, Diags, IndexAction.get(), Unit.get(), + /*Persistent=*/true, /*ResourceDir=*/"", + /*OnlyLocalDecls=*/true, + /*CaptureDiagnostics=*/true, 0, false, false, true); + }; + if (!RunSafely(CRC, compile)) { + LOG_S(ERROR) << "clang crashed for " << file; return {}; } - if (!success) { - LOG_S(ERROR) << "clang crashed for " << file; + if (!Unit) { + LOG_S(ERROR) << "failed to index " << file; return {}; } diff --git a/src/messages/textDocument_didOpen.cc b/src/messages/textDocument_didOpen.cc index ed96e6de..70ae716b 100644 --- a/src/messages/textDocument_didOpen.cc +++ b/src/messages/textDocument_didOpen.cc @@ -51,6 +51,8 @@ struct Handler_TextDocumentDidOpen include_complete->AddFile(working_file->filename); clang_complete->NotifyView(path); + if (g_config->diagnostics.onParse) + clang_complete->DiagnosticsUpdate({params.textDocument.uri}); if (params.args.size()) project->SetFlagsForFile(params.args, path); diff --git a/src/working_files.cc b/src/working_files.cc index 1ad076c2..6f0ca26b 100644 --- a/src/working_files.cc +++ b/src/working_files.cc @@ -195,20 +195,6 @@ std::optional FindMatchingLine(const std::vector& index_lines, } // namespace -std::vector WorkingFiles::Snapshot::AsUnsavedFiles() const { - std::vector 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, const std::string& buffer_content) : filename(filename), buffer_content(buffer_content) { diff --git a/src/working_files.h b/src/working_files.h index 69014989..d5a960ac 100644 --- a/src/working_files.h +++ b/src/working_files.h @@ -84,7 +84,6 @@ struct WorkingFiles { std::string content; }; - std::vector AsUnsavedFiles() const; std::vector files; };