| //===- CIndexDiagnostic.cpp - Diagnostics C Interface ---------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // Implements the diagnostic functions of the Clang C interface. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "CIndexDiagnostic.h" |
| #include "CIndexer.h" |
| #include "CXTranslationUnit.h" |
| #include "CXSourceLocation.h" |
| #include "CXString.h" |
| |
| #include "clang/Basic/DiagnosticOptions.h" |
| #include "clang/Frontend/ASTUnit.h" |
| #include "clang/Frontend/DiagnosticRenderer.h" |
| #include "llvm/ADT/SmallString.h" |
| #include "llvm/Support/raw_ostream.h" |
| |
| using namespace clang; |
| using namespace clang::cxloc; |
| using namespace clang::cxdiag; |
| using namespace llvm; |
| |
| CXDiagnosticSetImpl::~CXDiagnosticSetImpl() {} |
| |
| void |
| CXDiagnosticSetImpl::appendDiagnostic(std::unique_ptr<CXDiagnosticImpl> D) { |
| Diagnostics.push_back(std::move(D)); |
| } |
| |
| CXDiagnosticImpl::~CXDiagnosticImpl() {} |
| |
| namespace { |
| class CXDiagnosticCustomNoteImpl : public CXDiagnosticImpl { |
| std::string Message; |
| CXSourceLocation Loc; |
| public: |
| CXDiagnosticCustomNoteImpl(StringRef Msg, CXSourceLocation L) |
| : CXDiagnosticImpl(CustomNoteDiagnosticKind), Message(std::string(Msg)), |
| Loc(L) {} |
| |
| ~CXDiagnosticCustomNoteImpl() override {} |
| |
| CXDiagnosticSeverity getSeverity() const override { |
| return CXDiagnostic_Note; |
| } |
| |
| CXSourceLocation getLocation() const override { return Loc; } |
| |
| CXString getSpelling() const override { |
| return cxstring::createRef(Message.c_str()); |
| } |
| |
| CXString getDiagnosticOption(CXString *Disable) const override { |
| if (Disable) |
| *Disable = cxstring::createEmpty(); |
| return cxstring::createEmpty(); |
| } |
| |
| unsigned getCategory() const override { return 0; } |
| CXString getCategoryText() const override { return cxstring::createEmpty(); } |
| |
| unsigned getNumRanges() const override { return 0; } |
| CXSourceRange getRange(unsigned Range) const override { |
| return clang_getNullRange(); |
| } |
| unsigned getNumFixIts() const override { return 0; } |
| CXString getFixIt(unsigned FixIt, |
| CXSourceRange *ReplacementRange) const override { |
| if (ReplacementRange) |
| *ReplacementRange = clang_getNullRange(); |
| return cxstring::createEmpty(); |
| } |
| }; |
| |
| class CXDiagnosticRenderer : public DiagnosticNoteRenderer { |
| public: |
| CXDiagnosticRenderer(const LangOptions &LangOpts, |
| DiagnosticOptions *DiagOpts, |
| CXDiagnosticSetImpl *mainSet) |
| : DiagnosticNoteRenderer(LangOpts, DiagOpts), |
| CurrentSet(mainSet), MainSet(mainSet) {} |
| |
| ~CXDiagnosticRenderer() override {} |
| |
| void beginDiagnostic(DiagOrStoredDiag D, |
| DiagnosticsEngine::Level Level) override { |
| |
| const StoredDiagnostic *SD = D.dyn_cast<const StoredDiagnostic*>(); |
| if (!SD) |
| return; |
| |
| if (Level != DiagnosticsEngine::Note) |
| CurrentSet = MainSet; |
| |
| auto Owner = std::make_unique<CXStoredDiagnostic>(*SD, LangOpts); |
| CXStoredDiagnostic &CD = *Owner; |
| CurrentSet->appendDiagnostic(std::move(Owner)); |
| |
| if (Level != DiagnosticsEngine::Note) |
| CurrentSet = &CD.getChildDiagnostics(); |
| } |
| |
| void emitDiagnosticMessage(FullSourceLoc Loc, PresumedLoc PLoc, |
| DiagnosticsEngine::Level Level, StringRef Message, |
| ArrayRef<CharSourceRange> Ranges, |
| DiagOrStoredDiag D) override { |
| if (!D.isNull()) |
| return; |
| |
| CXSourceLocation L; |
| if (Loc.hasManager()) |
| L = translateSourceLocation(Loc.getManager(), LangOpts, Loc); |
| else |
| L = clang_getNullLocation(); |
| CurrentSet->appendDiagnostic( |
| std::make_unique<CXDiagnosticCustomNoteImpl>(Message, L)); |
| } |
| |
| void emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc, |
| DiagnosticsEngine::Level Level, |
| ArrayRef<CharSourceRange> Ranges) override {} |
| |
| void emitCodeContext(FullSourceLoc Loc, DiagnosticsEngine::Level Level, |
| SmallVectorImpl<CharSourceRange> &Ranges, |
| ArrayRef<FixItHint> Hints) override {} |
| |
| void emitNote(FullSourceLoc Loc, StringRef Message) override { |
| CXSourceLocation L; |
| if (Loc.hasManager()) |
| L = translateSourceLocation(Loc.getManager(), LangOpts, Loc); |
| else |
| L = clang_getNullLocation(); |
| CurrentSet->appendDiagnostic( |
| std::make_unique<CXDiagnosticCustomNoteImpl>(Message, L)); |
| } |
| |
| CXDiagnosticSetImpl *CurrentSet; |
| CXDiagnosticSetImpl *MainSet; |
| }; |
| } |
| |
| CXDiagnosticSetImpl *cxdiag::lazyCreateDiags(CXTranslationUnit TU, |
| bool checkIfChanged) { |
| ASTUnit *AU = cxtu::getASTUnit(TU); |
| |
| if (TU->Diagnostics && checkIfChanged) { |
| // In normal use, ASTUnit's diagnostics should not change unless we reparse. |
| // Currently they can only change by using the internal testing flag |
| // '-error-on-deserialized-decl' which will error during deserialization of |
| // a declaration. What will happen is: |
| // |
| // -c-index-test gets a CXTranslationUnit |
| // -checks the diagnostics, the diagnostics set is lazily created, |
| // no errors are reported |
| // -later does an operation, like annotation of tokens, that triggers |
| // -error-on-deserialized-decl, that will emit a diagnostic error, |
| // that ASTUnit will catch and add to its stored diagnostics vector. |
| // -c-index-test wants to check whether an error occurred after performing |
| // the operation but can only query the lazily created set. |
| // |
| // We check here if a new diagnostic was appended since the last time the |
| // diagnostic set was created, in which case we reset it. |
| |
| CXDiagnosticSetImpl * |
| Set = static_cast<CXDiagnosticSetImpl*>(TU->Diagnostics); |
| if (AU->stored_diag_size() != Set->getNumDiagnostics()) { |
| // Diagnostics in the ASTUnit were updated, reset the associated |
| // diagnostics. |
| delete Set; |
| TU->Diagnostics = nullptr; |
| } |
| } |
| |
| if (!TU->Diagnostics) { |
| CXDiagnosticSetImpl *Set = new CXDiagnosticSetImpl(); |
| TU->Diagnostics = Set; |
| IntrusiveRefCntPtr<DiagnosticOptions> DOpts = new DiagnosticOptions; |
| CXDiagnosticRenderer Renderer(AU->getASTContext().getLangOpts(), |
| &*DOpts, Set); |
| |
| for (ASTUnit::stored_diag_iterator it = AU->stored_diag_begin(), |
| ei = AU->stored_diag_end(); it != ei; ++it) { |
| Renderer.emitStoredDiagnostic(*it); |
| } |
| } |
| return static_cast<CXDiagnosticSetImpl*>(TU->Diagnostics); |
| } |
| |
| //----------------------------------------------------------------------------- |
| // C Interface Routines |
| //----------------------------------------------------------------------------- |
| unsigned clang_getNumDiagnostics(CXTranslationUnit Unit) { |
| if (cxtu::isNotUsableTU(Unit)) { |
| LOG_BAD_TU(Unit); |
| return 0; |
| } |
| if (!cxtu::getASTUnit(Unit)) |
| return 0; |
| return lazyCreateDiags(Unit, /*checkIfChanged=*/true)->getNumDiagnostics(); |
| } |
| |
| CXDiagnostic clang_getDiagnostic(CXTranslationUnit Unit, unsigned Index) { |
| if (cxtu::isNotUsableTU(Unit)) { |
| LOG_BAD_TU(Unit); |
| return nullptr; |
| } |
| |
| CXDiagnosticSet D = clang_getDiagnosticSetFromTU(Unit); |
| if (!D) |
| return nullptr; |
| |
| CXDiagnosticSetImpl *Diags = static_cast<CXDiagnosticSetImpl*>(D); |
| if (Index >= Diags->getNumDiagnostics()) |
| return nullptr; |
| |
| return Diags->getDiagnostic(Index); |
| } |
| |
| CXDiagnosticSet clang_getDiagnosticSetFromTU(CXTranslationUnit Unit) { |
| if (cxtu::isNotUsableTU(Unit)) { |
| LOG_BAD_TU(Unit); |
| return nullptr; |
| } |
| if (!cxtu::getASTUnit(Unit)) |
| return nullptr; |
| return static_cast<CXDiagnostic>(lazyCreateDiags(Unit)); |
| } |
| |
| void clang_disposeDiagnostic(CXDiagnostic Diagnostic) { |
| // No-op. Kept as a legacy API. CXDiagnostics are now managed |
| // by the enclosing CXDiagnosticSet. |
| } |
| |
| CXString clang_formatDiagnostic(CXDiagnostic Diagnostic, unsigned Options) { |
| if (!Diagnostic) |
| return cxstring::createEmpty(); |
| |
| CXDiagnosticSeverity Severity = clang_getDiagnosticSeverity(Diagnostic); |
| |
| SmallString<256> Str; |
| llvm::raw_svector_ostream Out(Str); |
| |
| if (Options & CXDiagnostic_DisplaySourceLocation) { |
| // Print source location (file:line), along with optional column |
| // and source ranges. |
| CXFile File; |
| unsigned Line, Column; |
| clang_getSpellingLocation(clang_getDiagnosticLocation(Diagnostic), |
| &File, &Line, &Column, nullptr); |
| if (File) { |
| CXString FName = clang_getFileName(File); |
| Out << clang_getCString(FName) << ":" << Line << ":"; |
| clang_disposeString(FName); |
| if (Options & CXDiagnostic_DisplayColumn) |
| Out << Column << ":"; |
| |
| if (Options & CXDiagnostic_DisplaySourceRanges) { |
| unsigned N = clang_getDiagnosticNumRanges(Diagnostic); |
| bool PrintedRange = false; |
| for (unsigned I = 0; I != N; ++I) { |
| CXFile StartFile, EndFile; |
| CXSourceRange Range = clang_getDiagnosticRange(Diagnostic, I); |
| |
| unsigned StartLine, StartColumn, EndLine, EndColumn; |
| clang_getSpellingLocation(clang_getRangeStart(Range), |
| &StartFile, &StartLine, &StartColumn, |
| nullptr); |
| clang_getSpellingLocation(clang_getRangeEnd(Range), |
| &EndFile, &EndLine, &EndColumn, nullptr); |
| |
| if (StartFile != EndFile || StartFile != File) |
| continue; |
| |
| Out << "{" << StartLine << ":" << StartColumn << "-" |
| << EndLine << ":" << EndColumn << "}"; |
| PrintedRange = true; |
| } |
| if (PrintedRange) |
| Out << ":"; |
| } |
| |
| Out << " "; |
| } |
| } |
| |
| /* Print warning/error/etc. */ |
| switch (Severity) { |
| case CXDiagnostic_Ignored: llvm_unreachable("impossible"); |
| case CXDiagnostic_Note: Out << "note: "; break; |
| case CXDiagnostic_Warning: Out << "warning: "; break; |
| case CXDiagnostic_Error: Out << "error: "; break; |
| case CXDiagnostic_Fatal: Out << "fatal error: "; break; |
| } |
| |
| CXString Text = clang_getDiagnosticSpelling(Diagnostic); |
| if (clang_getCString(Text)) |
| Out << clang_getCString(Text); |
| else |
| Out << "<no diagnostic text>"; |
| clang_disposeString(Text); |
| |
| if (Options & (CXDiagnostic_DisplayOption | CXDiagnostic_DisplayCategoryId | |
| CXDiagnostic_DisplayCategoryName)) { |
| bool NeedBracket = true; |
| bool NeedComma = false; |
| |
| if (Options & CXDiagnostic_DisplayOption) { |
| CXString OptionName = clang_getDiagnosticOption(Diagnostic, nullptr); |
| if (const char *OptionText = clang_getCString(OptionName)) { |
| if (OptionText[0]) { |
| Out << " [" << OptionText; |
| NeedBracket = false; |
| NeedComma = true; |
| } |
| } |
| clang_disposeString(OptionName); |
| } |
| |
| if (Options & (CXDiagnostic_DisplayCategoryId | |
| CXDiagnostic_DisplayCategoryName)) { |
| if (unsigned CategoryID = clang_getDiagnosticCategory(Diagnostic)) { |
| if (Options & CXDiagnostic_DisplayCategoryId) { |
| if (NeedBracket) |
| Out << " ["; |
| if (NeedComma) |
| Out << ", "; |
| Out << CategoryID; |
| NeedBracket = false; |
| NeedComma = true; |
| } |
| |
| if (Options & CXDiagnostic_DisplayCategoryName) { |
| CXString CategoryName = clang_getDiagnosticCategoryText(Diagnostic); |
| if (NeedBracket) |
| Out << " ["; |
| if (NeedComma) |
| Out << ", "; |
| Out << clang_getCString(CategoryName); |
| NeedBracket = false; |
| NeedComma = true; |
| clang_disposeString(CategoryName); |
| } |
| } |
| } |
| |
| (void) NeedComma; // Silence dead store warning. |
| if (!NeedBracket) |
| Out << "]"; |
| } |
| |
| return cxstring::createDup(Out.str()); |
| } |
| |
| unsigned clang_defaultDiagnosticDisplayOptions() { |
| return CXDiagnostic_DisplaySourceLocation | CXDiagnostic_DisplayColumn | |
| CXDiagnostic_DisplayOption; |
| } |
| |
| enum CXDiagnosticSeverity clang_getDiagnosticSeverity(CXDiagnostic Diag) { |
| if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl*>(Diag)) |
| return D->getSeverity(); |
| return CXDiagnostic_Ignored; |
| } |
| |
| CXSourceLocation clang_getDiagnosticLocation(CXDiagnostic Diag) { |
| if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl*>(Diag)) |
| return D->getLocation(); |
| return clang_getNullLocation(); |
| } |
| |
| CXString clang_getDiagnosticSpelling(CXDiagnostic Diag) { |
| if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag)) |
| return D->getSpelling(); |
| return cxstring::createEmpty(); |
| } |
| |
| CXString clang_getDiagnosticOption(CXDiagnostic Diag, CXString *Disable) { |
| if (Disable) |
| *Disable = cxstring::createEmpty(); |
| |
| if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag)) |
| return D->getDiagnosticOption(Disable); |
| |
| return cxstring::createEmpty(); |
| } |
| |
| unsigned clang_getDiagnosticCategory(CXDiagnostic Diag) { |
| if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag)) |
| return D->getCategory(); |
| return 0; |
| } |
| |
| CXString clang_getDiagnosticCategoryName(unsigned Category) { |
| // Kept for backward compatibility. |
| return cxstring::createRef(DiagnosticIDs::getCategoryNameFromID(Category)); |
| } |
| |
| CXString clang_getDiagnosticCategoryText(CXDiagnostic Diag) { |
| if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag)) |
| return D->getCategoryText(); |
| return cxstring::createEmpty(); |
| } |
| |
| unsigned clang_getDiagnosticNumRanges(CXDiagnostic Diag) { |
| if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag)) |
| return D->getNumRanges(); |
| return 0; |
| } |
| |
| CXSourceRange clang_getDiagnosticRange(CXDiagnostic Diag, unsigned Range) { |
| CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag); |
| if (!D || Range >= D->getNumRanges()) |
| return clang_getNullRange(); |
| return D->getRange(Range); |
| } |
| |
| unsigned clang_getDiagnosticNumFixIts(CXDiagnostic Diag) { |
| if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag)) |
| return D->getNumFixIts(); |
| return 0; |
| } |
| |
| CXString clang_getDiagnosticFixIt(CXDiagnostic Diag, unsigned FixIt, |
| CXSourceRange *ReplacementRange) { |
| CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag); |
| if (!D || FixIt >= D->getNumFixIts()) { |
| if (ReplacementRange) |
| *ReplacementRange = clang_getNullRange(); |
| return cxstring::createEmpty(); |
| } |
| return D->getFixIt(FixIt, ReplacementRange); |
| } |
| |
| void clang_disposeDiagnosticSet(CXDiagnosticSet Diags) { |
| if (CXDiagnosticSetImpl *D = static_cast<CXDiagnosticSetImpl *>(Diags)) { |
| if (D->isExternallyManaged()) |
| delete D; |
| } |
| } |
| |
| CXDiagnostic clang_getDiagnosticInSet(CXDiagnosticSet Diags, |
| unsigned Index) { |
| if (CXDiagnosticSetImpl *D = static_cast<CXDiagnosticSetImpl*>(Diags)) |
| if (Index < D->getNumDiagnostics()) |
| return D->getDiagnostic(Index); |
| return nullptr; |
| } |
| |
| CXDiagnosticSet clang_getChildDiagnostics(CXDiagnostic Diag) { |
| if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag)) { |
| CXDiagnosticSetImpl &ChildDiags = D->getChildDiagnostics(); |
| return ChildDiags.empty() ? nullptr : (CXDiagnosticSet) &ChildDiags; |
| } |
| return nullptr; |
| } |
| |
| unsigned clang_getNumDiagnosticsInSet(CXDiagnosticSet Diags) { |
| if (CXDiagnosticSetImpl *D = static_cast<CXDiagnosticSetImpl*>(Diags)) |
| return D->getNumDiagnostics(); |
| return 0; |
| } |