From fea457616ab329290284928c0f1e881fc222d4b0 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Tue, 28 Aug 2018 22:49:53 -0700 Subject: [PATCH] New diagnostics --- src/clang_complete.cc | 339 ++++++++++++++++++++++++++---------------- src/clang_complete.h | 48 +++--- src/clang_tu.cc | 75 +--------- src/clang_tu.h | 23 +-- 4 files changed, 238 insertions(+), 247 deletions(-) diff --git a/src/clang_complete.cc b/src/clang_complete.cc index c2779cca..9d54e356 100644 --- a/src/clang_complete.cc +++ b/src/clang_complete.cc @@ -22,6 +22,7 @@ using namespace llvm; #include #include +namespace ccls { namespace { std::string StripFileType(const std::string &path) { @@ -292,6 +293,38 @@ void BuildDetailString(const CodeCompletionString &CCS, lsCompletionItem &item, } } +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; +} + + class CaptureCompletionResults : public CodeCompleteConsumer { std::shared_ptr Alloc; CodeCompletionTUInfo CCTUInfo; @@ -360,23 +393,83 @@ public: CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } }; -void TryEnsureDocumentParsed(ClangCompleteManager *manager, - std::shared_ptr session, - std::unique_ptr *tu, - bool diagnostic) { - // Nothing to do. We already have a translation unit. - if (*tu) - return; +class StoreDiags : public DiagnosticConsumer { + const LangOptions *LangOpts; + std::optional last; + std::vector 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 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()); + }; - const auto &args = session->file.args; - WorkingFiles::Snapshot snapshot = session->wfiles->AsSnapshot( - {StripFileType(session->file.filename)}); + 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; + }; - LOG_S(INFO) << "create " << (diagnostic ? "diagnostic" : "completion") - << " TU for " << session->file.filename; - *tu = ClangTranslationUnit::Create(session->file.filename, args, snapshot, - diagnostic); -} + 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 buildCompilerInvocation(const std::vector &args, @@ -389,12 +482,61 @@ buildCompilerInvocation(const std::vector &args, std::unique_ptr CI = createInvocationFromCommandLine(cargs, Diags, VFS); if (CI) { + CI->getFrontendOpts().DisableFree = false; CI->getLangOpts()->CommentOpts.ParseAllComments = true; CI->getLangOpts()->SpellChecking = false; } return CI; } +std::unique_ptr +BuildCompilerInstance(CompletionSession &session, + std::unique_ptr CI, + DiagnosticConsumer &DC, + const WorkingFiles::Snapshot &snapshot, + std::vector> &Bufs) { + 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, + 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(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; +} + void CompletionPreloadMain(ClangCompleteManager *completion_manager) { while (true) { // Fetching the completion request blocks until we have a request. @@ -449,56 +591,23 @@ void CompletionQueryMain(ClangCompleteManager *completion_manager) { #endif CCOpts.IncludeCodePatterns = true; auto &FOpts = CI->getFrontendOpts(); - FOpts.DisableFree = false; FOpts.CodeCompleteOpts = CCOpts; FOpts.CodeCompletionAt.FileName = session->file.filename; FOpts.CodeCompletionAt.Line = request->position.line + 1; FOpts.CodeCompletionAt.Column = request->position.character + 1; + StoreDiags DC; WorkingFiles::Snapshot snapshot = completion_manager->working_files_->AsSnapshot({StripFileType(path)}); - std::vector> Bufs; - 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, - 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 = BuildCompilerInstance(*session, std::move(CI), DC, snapshot, Bufs); + if (!Clang) + continue; - IgnoringDiagConsumer DC; - auto Clang = std::make_unique(session->PCH); - Clang->setInvocation(std::move(CI)); - Clang->createDiagnostics(&DC, false); auto Consumer = new CaptureCompletionResults(CCOpts); Clang->setCodeCompletionConsumer(Consumer); - Clang->setVirtualFileSystem(session->FS); - Clang->setTarget(TargetInfo::CreateTargetInfo( - Clang->getDiagnostics(), Clang->getInvocation().TargetOpts)); - if (!Clang->hasTarget()) + if (!Parse(*Clang)) continue; - - SyntaxOnlyAction Action; - if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) - continue; - if (!Action.Execute()) - continue; - Action.EndSourceFile(); for (auto &Buf : Bufs) Buf.release(); @@ -518,51 +627,31 @@ void DiagnosticQueryMain(ClangCompleteManager *manager) { std::shared_ptr session = manager->TryGetSession( path, true /*mark_as_completion*/, true /*create_if_needed*/); - // At this point, we must have a translation unit. Block until we have one. - std::lock_guard lock(session->diagnostics.lock); - TryEnsureDocumentParsed(manager, session, &session->diagnostics.tu, true); - - // It is possible we failed to create the document despite - // |TryEnsureDocumentParsed|. - ClangTranslationUnit *tu = session->diagnostics.tu.get(); - if (!tu) + std::unique_ptr CI = + buildCompilerInvocation(session->file.args, session->FS); + if (!CI) continue; - + StoreDiags DC; WorkingFiles::Snapshot snapshot = manager->working_files_->AsSnapshot({StripFileType(path)}); - llvm::CrashRecoveryContext CRC; - if (tu->Reparse(CRC, snapshot)) { - LOG_S(ERROR) << "Reparsing translation unit for diagnostics failed for " - << path; + std::vector> Bufs; + auto Clang = BuildCompilerInstance(*session, std::move(CI), DC, snapshot, Bufs); + if (!Clang) continue; - } + if (!Parse(*Clang)) + continue; + for (auto &Buf : Bufs) + Buf.release(); - 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(); - if (!FLoc.isValid()) // why? + for (auto &d : DC.Take()) { + if (!d.inside_main) continue; - const FileEntry *FE = FLoc.getFileEntry(); - if (!FE || FileName(*FE) != path) - continue; - const auto &SM = FLoc.getManager(); - 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()) { + 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: @@ -576,16 +665,8 @@ void DiagnosticQueryMain(ClangCompleteManager *manager) { 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 = FromCharSourceRange(SM, LangOpts, FixIt.RemoveRange); - 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); + ls_diag.code = d.category; + ls_diag.fixits_ = d.edits; } manager->on_diagnostic_(path, ls_diags); } @@ -594,15 +675,15 @@ void DiagnosticQueryMain(ClangCompleteManager *manager) { } // namespace std::shared_ptr CompletionSession::GetPreamble() { - std::lock_guard lock(completion.lock); - return completion.preamble; + std::lock_guard lock(mutex); + return preamble; } void CompletionSession::BuildPreamble(CompilerInvocation &CI) { std::shared_ptr OldP = GetPreamble(); - std::string contents = wfiles->GetContent(file.filename); + std::string content = wfiles->GetContent(file.filename); std::unique_ptr Buf = - llvm::MemoryBuffer::getMemBuffer(contents); + llvm::MemoryBuffer::getMemBuffer(content); auto Bounds = ComputePreambleBounds(*CI.getLangOpts(), Buf.get(), 0); if (OldP && OldP->Preamble.CanReuse(CI, Buf.get(), Bounds, FS.get())) return; @@ -612,19 +693,20 @@ void CompletionSession::BuildPreamble(CompilerInvocation &CI) { CI.getPreprocessorOpts().WriteCommentListToPCH = false; #endif - DiagnosticConsumer DC; - IntrusiveRefCntPtr PreambleDE = - CompilerInstance::createDiagnostics(&CI.getDiagnosticOpts(), - &DC, false); + StoreDiags DC; + IntrusiveRefCntPtr DE = + CompilerInstance::createDiagnostics(&CI.getDiagnosticOpts(), &DC, false); PreambleCallbacks PP; - if (auto NewPreamble = PrecompiledPreamble::Build( - CI, Buf.get(), Bounds, *PreambleDE, FS, PCH, true, PP)) { - std::lock_guard lock(completion.lock); - completion.preamble = - std::make_shared(std::move(*NewPreamble)); + if (auto NewPreamble = PrecompiledPreamble::Build(CI, Buf.get(), Bounds, + *DE, FS, PCH, true, PP)) { + std::lock_guard lock(mutex); + preamble = + std::make_shared(std::move(*NewPreamble), DC.Take()); } } +} // namespace ccls + ClangCompleteManager::ClangCompleteManager(Project *project, WorkingFiles *working_files, OnDiagnostic on_diagnostic, @@ -636,17 +718,17 @@ ClangCompleteManager::ClangCompleteManager(Project *project, PCH(std::make_shared()) { std::thread([&]() { set_thread_name("comp-query"); - CompletionQueryMain(this); + ccls::CompletionQueryMain(this); }) .detach(); std::thread([&]() { set_thread_name("comp-preload"); - CompletionPreloadMain(this); + ccls::CompletionPreloadMain(this); }) .detach(); std::thread([&]() { set_thread_name("diag-query"); - DiagnosticQueryMain(this); + ccls::DiagnosticQueryMain(this); }) .detach(); } @@ -734,43 +816,42 @@ bool ClangCompleteManager::EnsureCompletionOrCreatePreloadSession( } // No CompletionSession, create new one. - auto session = std::make_shared( + auto session = std::make_shared( project_->FindCompilationEntryForFile(filename), working_files_, PCH); preloaded_sessions_.Insert(session->file.filename, session); return true; } -std::shared_ptr +std::shared_ptr ClangCompleteManager::TryGetSession(const std::string &filename, bool mark_as_completion, bool create_if_needed) { std::lock_guard lock(sessions_lock_); // Try to find a preloaded session. - std::shared_ptr preloaded_session = + std::shared_ptr preloaded = preloaded_sessions_.TryGet(filename); - if (preloaded_session) { + if (preloaded) { // If this request is for a completion, we should move it to // |completion_sessions|. if (mark_as_completion) { preloaded_sessions_.TryTake(filename); - completion_sessions_.Insert(filename, preloaded_session); + completion_sessions_.Insert(filename, preloaded); } - - return preloaded_session; + return preloaded; } // Try to find a completion session. If none create one. - std::shared_ptr completion_session = + std::shared_ptr session = completion_sessions_.TryGet(filename); - if (!completion_session && create_if_needed) { - completion_session = std::make_shared( + if (!session && create_if_needed) { + session = std::make_shared( project_->FindCompilationEntryForFile(filename), working_files_, PCH); - completion_sessions_.Insert(filename, completion_session); + completion_sessions_.Insert(filename, session); } - return completion_session; + return session; } void ClangCompleteManager::FlushSession(const std::string &filename) { diff --git a/src/clang_complete.h b/src/clang_complete.h index 7cba784f..dd4007ce 100644 --- a/src/clang_complete.h +++ b/src/clang_complete.h @@ -19,27 +19,33 @@ #include #include +namespace ccls { +struct DiagBase { + Range range; + std::string message; + std::string file; + clang::DiagnosticsEngine::Level level = clang::DiagnosticsEngine::Note; + unsigned category; + bool inside_main = false; +}; +struct Note : DiagBase {}; +struct Diag : DiagBase { + std::vector notes; + std::vector edits; +}; + struct PreambleData { - PreambleData(clang::PrecompiledPreamble P) : Preamble(std::move(P)) {} + PreambleData(clang::PrecompiledPreamble P, std::vector diags) + : Preamble(std::move(P)), diags(std::move(diags)) {} clang::PrecompiledPreamble Preamble; + std::vector diags; }; struct CompletionSession : public std::enable_shared_from_this { - // Translation unit for clang. - struct Tu { - // When |tu| was last parsed. - std::optional> - last_parsed_at; - // Acquired when |tu| is being used. - std::mutex lock; - std::unique_ptr tu; - }; - - struct CU { - std::mutex lock; - std::shared_ptr preamble; - }; + std::mutex mutex; + std::shared_ptr preamble; + std::vector diags; Project::Entry file; WorkingFiles *wfiles; @@ -49,9 +55,6 @@ struct CompletionSession clang::vfs::getRealFileSystem(); std::shared_ptr PCH; - CU completion; - Tu diagnostics; - CompletionSession(const Project::Entry &file, WorkingFiles *wfiles, std::shared_ptr PCH) : file(file), wfiles(wfiles), PCH(PCH) {} @@ -59,6 +62,7 @@ struct CompletionSession std::shared_ptr GetPreamble(); void BuildPreamble(clang::CompilerInvocation &CI); }; +} struct ClangCompleteManager { using OnDiagnostic = std::function TryGetSession(const std::string &filename, - bool mark_as_completion, - bool create_if_needed); + std::shared_ptr + TryGetSession(const std::string &filename, bool mark_as_completion, + bool create_if_needed); // Flushes all saved sessions with the supplied filename void FlushSession(const std::string &filename); @@ -137,7 +141,7 @@ struct ClangCompleteManager { OnDiagnostic on_diagnostic_; OnDropped on_dropped_; - using LruSessionCache = LruCache; + using LruSessionCache = LruCache; // CompletionSession instances which are preloaded, ie, files which the user // has viewed but not requested code completion for. diff --git a/src/clang_tu.cc b/src/clang_tu.cc index f149c2a2..ccecd26c 100644 --- a/src/clang_tu.cc +++ b/src/clang_tu.cc @@ -4,12 +4,8 @@ #include "clang_tu.h" #include "clang_utils.h" -#include "log.hh" -#include "platform.h" -#include "utils.h" -#include "working_files.h" -#include +#include using namespace clang; #include @@ -55,72 +51,3 @@ Range FromTokenRange(const SourceManager &SM, const LangOptions &LangOpts, return FromCharSourceRange(SM, LangOpts, CharSourceRange::getTokenRange(R), UniqueID); } - -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; -} - -std::unique_ptr ClangTranslationUnit::Create( - const std::string &filepath, const std::vector &args, - const WorkingFiles::Snapshot &snapshot, bool diagnostic) { - std::vector Args; - for (auto &arg : args) - Args.push_back(arg.c_str()); - Args.push_back("-fallow-editor-placeholders"); - if (!diagnostic) - Args.push_back("-fno-spell-checking"); - - 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=*/g_config->clang.resourceDir, - /*OnlyLocalDecls=*/false, - /*CaptureDiagnostics=*/diagnostic, Remapped, - /*RemappedFilesKeepOriginalName=*/true, 0, - diagnostic ? TU_Complete : TU_Prefix, - /*CacheCodeCompletionResults=*/true, g_config->index.comments, - /*AllowPCHWithCompilerErrors=*/true, -#if LLVM_VERSION_MAJOR >= 7 - SkipFunctionBodiesScope::None, -#else - !diagnostic, -#endif - /*SingleFileParse=*/false, - /*UserFilesAreVolatile=*/true, false, - ret->PCHCO->getRawReader().getFormat(), &ErrUnit)); - }; - if (!CRC.RunSafely(parse)) { - LOG_S(ERROR) << "clang crashed for " << filepath << "\n" - << StringJoin(args, " ") + " -fsyntax-only"; - return {}; - } - if (!Unit && !ErrUnit) - return {}; - - ret->Unit = std::move(Unit); - return ret; -} - -int ClangTranslationUnit::Reparse(llvm::CrashRecoveryContext &CRC, - const WorkingFiles::Snapshot &snapshot) { - int ret = 1; - (void)CRC.RunSafely( - [&]() { ret = Unit->Reparse(PCHCO, GetRemapped(snapshot)); }); - return ret; -} diff --git a/src/clang_tu.h b/src/clang_tu.h index d494f8be..3723c458 100644 --- a/src/clang_tu.h +++ b/src/clang_tu.h @@ -3,19 +3,10 @@ #pragma once #include "position.h" -#include "working_files.h" -#include -#include -#include +#include -#include #include -#include -#include - -std::vector -GetRemapped(const WorkingFiles::Snapshot &snapshot); Range FromCharSourceRange(const clang::SourceManager &SM, const clang::LangOptions &LangOpts, @@ -29,15 +20,3 @@ Range FromCharRange(const clang::SourceManager &SM, Range FromTokenRange(const clang::SourceManager &SM, const clang::LangOptions &LangOpts, clang::SourceRange R, llvm::sys::fs::UniqueID *UniqueID = nullptr); - -struct ClangTranslationUnit { - static std::unique_ptr - Create(const std::string &filepath, const std::vector &args, - const WorkingFiles::Snapshot &snapshot, bool diagnostic); - - int Reparse(llvm::CrashRecoveryContext &CRC, - const WorkingFiles::Snapshot &snapshot); - - std::shared_ptr PCHCO; - std::unique_ptr Unit; -};