mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2024-12-14 11:39:35 +00:00
serialized diagnostics: implement full deserialization of clang diagnostics via the libclang API.
I've tested it on simple cases and it works. Test cases to follow as well as a few tweaks. llvm-svn: 144269
This commit is contained in:
parent
08ef466048
commit
d010ba4729
@ -521,6 +521,86 @@ enum CXDiagnosticSeverity {
|
||||
*/
|
||||
typedef void *CXDiagnostic;
|
||||
|
||||
/**
|
||||
* \brief A group of CXDiagnostics.
|
||||
*/
|
||||
typedef void *CXDiagnosticSet;
|
||||
|
||||
/**
|
||||
* \brief Determine the number of diagnostics in a CXDiagnosticSet.
|
||||
*/
|
||||
CINDEX_LINKAGE unsigned clang_getNumDiagnosticsInSet(CXDiagnosticSet Diags);
|
||||
|
||||
/**
|
||||
* \brief Retrieve a diagnostic associated with the given CXDiagnosticSet.
|
||||
*
|
||||
* \param Unit the CXDiagnosticSet to query.
|
||||
* \param Index the zero-based diagnostic number to retrieve.
|
||||
*
|
||||
* \returns the requested diagnostic. This diagnostic must be freed
|
||||
* via a call to \c clang_disposeDiagnostic().
|
||||
*/
|
||||
CINDEX_LINKAGE CXDiagnostic clang_getDiagnosticInSet(CXDiagnosticSet Diags,
|
||||
unsigned Index);
|
||||
|
||||
|
||||
/**
|
||||
* \brief Describes the kind of error that occurred (if any) in a call to
|
||||
* \c clang_loadDiagnostics.
|
||||
*/
|
||||
enum CXLoadDiag_Error {
|
||||
/**
|
||||
* \brief Indicates that no error occurred.
|
||||
*/
|
||||
CXLoadDiag_None = 0,
|
||||
|
||||
/**
|
||||
* \brief Indicates that an unknown error occurred while attempting to
|
||||
* deserialize diagnostics.
|
||||
*/
|
||||
CXLoadDiag_Unknown = 1,
|
||||
|
||||
/**
|
||||
* \brief Indicates that the file containing the serialized diagnostics
|
||||
* could not be opened.
|
||||
*/
|
||||
CXLoadDiag_CannotLoad = 2,
|
||||
|
||||
/**
|
||||
* \brief Indicates that the serialized diagnostics file is invalid or
|
||||
* corrupt.
|
||||
*/
|
||||
CXLoadDiag_InvalidFile = 3
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Deserialize a set of diagnostics from a Clang diagnostics bitcode
|
||||
* file.
|
||||
*
|
||||
* \param The name of the file to deserialize.
|
||||
* \param A pointer to a enum value recording if there was a problem
|
||||
* deserializing the diagnostics.
|
||||
* \param A pointer to a CXString for recording the error string
|
||||
* if the file was not successfully loaded.
|
||||
*
|
||||
* \returns A loaded CXDiagnosticSet if successful, and NULL otherwise. These
|
||||
* diagnostics should be released using clang_disposeDiagnosticSet().
|
||||
*/
|
||||
CINDEX_LINKAGE CXDiagnosticSet clang_loadDiagnostics(const char *file,
|
||||
enum CXLoadDiag_Error *error,
|
||||
CXString *errorString);
|
||||
|
||||
/**
|
||||
* \brief Release a CXDiagnosticSet and all of its contained diagnostics.
|
||||
*/
|
||||
CINDEX_LINKAGE void clang_disposeDiagnosticSet(CXDiagnosticSet Diags);
|
||||
|
||||
/**
|
||||
* \brief Retrieve the child diagnostics of a CXDiagnostic. This
|
||||
* CXDiagnosticSet does not need to be released by clang_diposeDiagnosticSet.
|
||||
*/
|
||||
CINDEX_LINKAGE CXDiagnosticSet clang_getChildDiagnostics(CXDiagnostic D);
|
||||
|
||||
/**
|
||||
* \brief Determine the number of diagnostics produced for the given
|
||||
* translation unit.
|
||||
|
@ -39,7 +39,9 @@ enum RecordIDs {
|
||||
RECORD_DIAG_FLAG,
|
||||
RECORD_CATEGORY,
|
||||
RECORD_FILENAME,
|
||||
RECORD_FIXIT
|
||||
RECORD_FIXIT,
|
||||
RECORD_FIRST = RECORD_VERSION,
|
||||
RECORD_LAST = RECORD_FIXIT
|
||||
};
|
||||
|
||||
/// \brief Returns a DiagnosticConsumer that serializes diagnostics to
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "clang/Basic/FileManager.h"
|
||||
#include "clang/Basic/Diagnostic.h"
|
||||
#include "clang/Basic/Version.h"
|
||||
#include "clang/Lex/Lexer.h"
|
||||
#include "clang/Frontend/SerializedDiagnosticPrinter.h"
|
||||
|
||||
using namespace clang;
|
||||
@ -47,7 +48,8 @@ typedef llvm::SmallVectorImpl<uint64_t> RecordDataImpl;
|
||||
class SDiagsWriter : public DiagnosticConsumer {
|
||||
public:
|
||||
SDiagsWriter(DiagnosticsEngine &diags, llvm::raw_ostream *os)
|
||||
: Stream(Buffer), OS(os), Diags(diags), inNonNoteDiagnostic(false)
|
||||
: LangOpts(0), Stream(Buffer), OS(os), Diags(diags),
|
||||
inNonNoteDiagnostic(false)
|
||||
{
|
||||
EmitPreamble();
|
||||
};
|
||||
@ -59,6 +61,11 @@ public:
|
||||
|
||||
void EndSourceFile();
|
||||
|
||||
void BeginSourceFile(const LangOptions &LO,
|
||||
const Preprocessor *PP) {
|
||||
LangOpts = &LO;
|
||||
}
|
||||
|
||||
DiagnosticConsumer *clone(DiagnosticsEngine &Diags) const {
|
||||
// It makes no sense to clone this.
|
||||
return 0;
|
||||
@ -88,7 +95,8 @@ private:
|
||||
unsigned getEmitFile(SourceLocation Loc);
|
||||
|
||||
/// \brief Add SourceLocation information the specified record.
|
||||
void AddLocToRecord(SourceLocation Loc, RecordDataImpl &Record);
|
||||
void AddLocToRecord(SourceLocation Loc, RecordDataImpl &Record,
|
||||
unsigned TokSize = 0);
|
||||
|
||||
/// \brief Add CharSourceRange information the specified record.
|
||||
void AddCharSourceRangeToRecord(CharSourceRange R, RecordDataImpl &Record);
|
||||
@ -96,6 +104,8 @@ private:
|
||||
/// \brief The version of the diagnostics file.
|
||||
enum { Version = 1 };
|
||||
|
||||
const LangOptions *LangOpts;
|
||||
|
||||
/// \brief The byte buffer for the serialized content.
|
||||
std::vector<unsigned char> Buffer;
|
||||
|
||||
@ -181,13 +191,14 @@ static void EmitRecordID(unsigned ID, const char *Name,
|
||||
}
|
||||
|
||||
void SDiagsWriter::AddLocToRecord(SourceLocation Loc,
|
||||
RecordDataImpl &Record) {
|
||||
RecordDataImpl &Record,
|
||||
unsigned TokSize) {
|
||||
if (Loc.isInvalid()) {
|
||||
// Emit a "sentinel" location.
|
||||
Record.push_back((unsigned) 0); // File.
|
||||
Record.push_back(~(unsigned)0); // Line.
|
||||
Record.push_back(~(unsigned)0); // Column.
|
||||
Record.push_back(~(unsigned)0); // Offset.
|
||||
Record.push_back((unsigned)0); // File.
|
||||
Record.push_back((unsigned)0); // Line.
|
||||
Record.push_back((unsigned)0); // Column.
|
||||
Record.push_back((unsigned)0); // Offset.
|
||||
return;
|
||||
}
|
||||
|
||||
@ -195,7 +206,7 @@ void SDiagsWriter::AddLocToRecord(SourceLocation Loc,
|
||||
Loc = SM.getSpellingLoc(Loc);
|
||||
Record.push_back(getEmitFile(Loc));
|
||||
Record.push_back(SM.getSpellingLineNumber(Loc));
|
||||
Record.push_back(SM.getSpellingColumnNumber(Loc));
|
||||
Record.push_back(SM.getSpellingColumnNumber(Loc)+TokSize);
|
||||
|
||||
std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc);
|
||||
FileID FID = LocInfo.first;
|
||||
@ -206,7 +217,13 @@ void SDiagsWriter::AddLocToRecord(SourceLocation Loc,
|
||||
void SDiagsWriter::AddCharSourceRangeToRecord(CharSourceRange Range,
|
||||
RecordDataImpl &Record) {
|
||||
AddLocToRecord(Range.getBegin(), Record);
|
||||
AddLocToRecord(Range.getEnd(), Record);
|
||||
unsigned TokSize = 0;
|
||||
if (Range.isTokenRange())
|
||||
TokSize = Lexer::MeasureTokenLength(Range.getEnd(),
|
||||
Diags.getSourceManager(),
|
||||
*LangOpts);
|
||||
|
||||
AddLocToRecord(Range.getEnd(), Record, TokSize);
|
||||
}
|
||||
|
||||
unsigned SDiagsWriter::getEmitFile(SourceLocation Loc) {
|
||||
|
@ -2430,6 +2430,155 @@ int write_pch_file(const char *filename, int argc, const char *argv[]) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
/* Serialized diagnostics. */
|
||||
/******************************************************************************/
|
||||
|
||||
static const char *getDiagnosticCodeStr(enum CXLoadDiag_Error error) {
|
||||
switch (error) {
|
||||
case CXLoadDiag_CannotLoad: return "Cannot Load File";
|
||||
case CXLoadDiag_None: break;
|
||||
case CXLoadDiag_Unknown: return "Unknown";
|
||||
case CXLoadDiag_InvalidFile: return "Invalid File";
|
||||
}
|
||||
return "None";
|
||||
}
|
||||
|
||||
static const char *getSeverityString(enum CXDiagnosticSeverity severity) {
|
||||
switch (severity) {
|
||||
case CXDiagnostic_Note: return "note";
|
||||
case CXDiagnostic_Error: return "error";
|
||||
case CXDiagnostic_Fatal: return "fatal";
|
||||
case CXDiagnostic_Ignored: return "ignored";
|
||||
case CXDiagnostic_Warning: return "warning";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
static void printIndent(unsigned indent) {
|
||||
while (indent > 0) {
|
||||
fprintf(stderr, " ");
|
||||
--indent;
|
||||
}
|
||||
}
|
||||
|
||||
static void printLocation(CXSourceLocation L) {
|
||||
CXFile File;
|
||||
CXString FileName;
|
||||
unsigned line, column, offset;
|
||||
|
||||
clang_getExpansionLocation(L, &File, &line, &column, &offset);
|
||||
FileName = clang_getFileName(File);
|
||||
|
||||
fprintf(stderr, "%s:%d:%d", clang_getCString(FileName), line, column);
|
||||
clang_disposeString(FileName);
|
||||
}
|
||||
|
||||
static void printRanges(CXDiagnostic D, unsigned indent) {
|
||||
unsigned i, n = clang_getDiagnosticNumRanges(D);
|
||||
|
||||
for (i = 0; i < n; ++i) {
|
||||
CXSourceLocation Start, End;
|
||||
CXSourceRange SR = clang_getDiagnosticRange(D, i);
|
||||
Start = clang_getRangeStart(SR);
|
||||
End = clang_getRangeEnd(SR);
|
||||
|
||||
printIndent(indent);
|
||||
fprintf(stderr, "Range: ");
|
||||
printLocation(Start);
|
||||
fprintf(stderr, " ");
|
||||
printLocation(End);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void printFixIts(CXDiagnostic D, unsigned indent) {
|
||||
unsigned i, n = clang_getDiagnosticNumFixIts(D);
|
||||
for (i = 0 ; i < n; ++i) {
|
||||
CXSourceRange ReplacementRange;
|
||||
CXString text;
|
||||
text = clang_getDiagnosticFixIt(D, i, &ReplacementRange);
|
||||
|
||||
printIndent(indent);
|
||||
fprintf(stderr, "FIXIT: (");
|
||||
printLocation(clang_getRangeStart(ReplacementRange));
|
||||
fprintf(stderr, " - ");
|
||||
printLocation(clang_getRangeEnd(ReplacementRange));
|
||||
fprintf(stderr, "): \"%s\"\n", clang_getCString(text));
|
||||
clang_disposeString(text);
|
||||
}
|
||||
}
|
||||
|
||||
static void printDiagnosticSet(CXDiagnosticSet Diags, unsigned indent) {
|
||||
if (!Diags)
|
||||
return;
|
||||
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
unsigned i = 0;
|
||||
unsigned n = clang_getNumDiagnosticsInSet(Diags);
|
||||
for (i = 0; i < n; ++i) {
|
||||
CXSourceLocation DiagLoc;
|
||||
CXDiagnostic D;
|
||||
CXFile File;
|
||||
CXString FileName, DiagSpelling, DiagOption;
|
||||
unsigned line, column, offset;
|
||||
const char *DiagOptionStr = 0;
|
||||
|
||||
D = clang_getDiagnosticInSet(Diags, i);
|
||||
DiagLoc = clang_getDiagnosticLocation(D);
|
||||
clang_getExpansionLocation(DiagLoc, &File, &line, &column, &offset);
|
||||
FileName = clang_getFileName(File);
|
||||
DiagSpelling = clang_getDiagnosticSpelling(D);
|
||||
|
||||
printIndent(indent);
|
||||
|
||||
fprintf(stderr, "%s:%d:%d: %s: %s",
|
||||
clang_getCString(FileName),
|
||||
line,
|
||||
column,
|
||||
getSeverityString(clang_getDiagnosticSeverity(D)),
|
||||
clang_getCString(DiagSpelling));
|
||||
|
||||
DiagOption = clang_getDiagnosticOption(D, 0);
|
||||
DiagOptionStr = clang_getCString(DiagOption);
|
||||
if (DiagOptionStr) {
|
||||
fprintf(stderr, " [%s]", DiagOptionStr);
|
||||
}
|
||||
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
printRanges(D, indent);
|
||||
printFixIts(D, indent);
|
||||
|
||||
// Print subdiagnostics.
|
||||
printDiagnosticSet(clang_getChildDiagnostics(D), indent+2);
|
||||
|
||||
clang_disposeString(FileName);
|
||||
clang_disposeString(DiagSpelling);
|
||||
clang_disposeString(DiagOption);
|
||||
}
|
||||
}
|
||||
|
||||
static int read_diagnostics(const char *filename) {
|
||||
enum CXLoadDiag_Error error;
|
||||
CXString errorString;
|
||||
CXDiagnosticSet Diags = 0;
|
||||
|
||||
Diags = clang_loadDiagnostics(filename, &error, &errorString);
|
||||
if (!Diags) {
|
||||
fprintf(stderr, "Trouble deserializing file (%s): %s\n",
|
||||
getDiagnosticCodeStr(error),
|
||||
clang_getCString(errorString));
|
||||
clang_disposeString(errorString);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printDiagnosticSet(Diags, 0);
|
||||
clang_disposeDiagnosticSet(Diags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
/* Command line processing. */
|
||||
/******************************************************************************/
|
||||
@ -2475,7 +2624,9 @@ static void print_usage(void) {
|
||||
" c-index-test -test-print-typekind {<args>}*\n"
|
||||
" c-index-test -print-usr [<CursorKind> {<args>}]*\n"
|
||||
" c-index-test -print-usr-file <file>\n"
|
||||
" c-index-test -write-pch <file> <compiler arguments>\n\n");
|
||||
" c-index-test -write-pch <file> <compiler arguments>\n");
|
||||
fprintf(stderr,
|
||||
" c-index-test -read-diagnostics <file>\n\n");
|
||||
fprintf(stderr,
|
||||
" <symbol filter> values:\n%s",
|
||||
" all - load all symbols, including those from PCH\n"
|
||||
@ -2492,6 +2643,8 @@ static void print_usage(void) {
|
||||
|
||||
int cindextest_main(int argc, const char **argv) {
|
||||
clang_enableStackTraces();
|
||||
if (argc > 2 && strcmp(argv[1], "-read-diagnostics") == 0)
|
||||
return read_diagnostics(argv[2]);
|
||||
if (argc > 2 && strstr(argv[1], "-code-completion-at=") == argv[1])
|
||||
return perform_code_completion(argc, argv, 0);
|
||||
if (argc > 2 && strstr(argv[1], "-code-completion-timing=") == argv[1])
|
||||
|
@ -58,6 +58,7 @@ CXTranslationUnit cxtu::MakeCXTranslationUnit(ASTUnit *TU) {
|
||||
CXTranslationUnit D = new CXTranslationUnitImpl();
|
||||
D->TUData = TU;
|
||||
D->StringPool = createCXStringPool();
|
||||
D->Diagnostics = 0;
|
||||
return D;
|
||||
}
|
||||
|
||||
@ -2562,6 +2563,7 @@ void clang_disposeTranslationUnit(CXTranslationUnit CTUnit) {
|
||||
|
||||
delete static_cast<ASTUnit *>(CTUnit->TUData);
|
||||
disposeCXStringPool(CTUnit->StringPool);
|
||||
delete static_cast<CXDiagnosticSetImpl *>(CTUnit->Diagnostics);
|
||||
delete CTUnit;
|
||||
}
|
||||
}
|
||||
@ -2582,6 +2584,11 @@ static void clang_reparseTranslationUnit_Impl(void *UserData) {
|
||||
ReparseTranslationUnitInfo *RTUI =
|
||||
static_cast<ReparseTranslationUnitInfo*>(UserData);
|
||||
CXTranslationUnit TU = RTUI->TU;
|
||||
|
||||
// Reset the associated diagnostics.
|
||||
delete static_cast<CXDiagnosticSetImpl*>(TU->Diagnostics);
|
||||
TU->Diagnostics = 0;
|
||||
|
||||
unsigned num_unsaved_files = RTUI->num_unsaved_files;
|
||||
struct CXUnsavedFile *unsaved_files = RTUI->unsaved_files;
|
||||
unsigned options = RTUI->options;
|
||||
|
@ -28,27 +28,58 @@ using namespace clang::cxloc;
|
||||
using namespace clang::cxstring;
|
||||
using namespace llvm;
|
||||
|
||||
|
||||
CXDiagnosticSetImpl::~CXDiagnosticSetImpl() {
|
||||
for (std::vector<CXDiagnosticImpl *>::iterator it = Diagnostics.begin(),
|
||||
et = Diagnostics.end();
|
||||
it != et; ++it) {
|
||||
delete *it;
|
||||
}
|
||||
}
|
||||
|
||||
CXDiagnosticImpl::~CXDiagnosticImpl() {}
|
||||
|
||||
static CXDiagnosticSetImpl *lazyCreateDiags(CXTranslationUnit TU) {
|
||||
if (!TU->Diagnostics) {
|
||||
ASTUnit *AU = static_cast<ASTUnit *>(TU->TUData);
|
||||
CXDiagnosticSetImpl *Set = new CXDiagnosticSetImpl();
|
||||
TU->Diagnostics = Set;
|
||||
|
||||
for (ASTUnit::stored_diag_iterator it = AU->stored_diag_begin(),
|
||||
ei = AU->stored_diag_end(); it != ei; ++it) {
|
||||
CXStoredDiagnostic *D =
|
||||
new CXStoredDiagnostic(*it, AU->getASTContext().getLangOptions());
|
||||
Set->appendDiagnostic(D);
|
||||
}
|
||||
}
|
||||
return static_cast<CXDiagnosticSetImpl*>(TU->Diagnostics);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// C Interface Routines
|
||||
//-----------------------------------------------------------------------------
|
||||
extern "C" {
|
||||
|
||||
unsigned clang_getNumDiagnostics(CXTranslationUnit Unit) {
|
||||
ASTUnit *CXXUnit = static_cast<ASTUnit *>(Unit->TUData);
|
||||
return CXXUnit? CXXUnit->stored_diag_size() : 0;
|
||||
if (!Unit->TUData)
|
||||
return 0;
|
||||
return lazyCreateDiags(Unit)->getNumDiagnostics();
|
||||
}
|
||||
|
||||
CXDiagnostic clang_getDiagnostic(CXTranslationUnit Unit, unsigned Index) {
|
||||
ASTUnit *CXXUnit = static_cast<ASTUnit *>(Unit->TUData);
|
||||
if (!CXXUnit || Index >= CXXUnit->stored_diag_size())
|
||||
if (!Unit->TUData)
|
||||
return 0;
|
||||
|
||||
return new CXStoredDiagnostic(CXXUnit->stored_diag_begin()[Index],
|
||||
CXXUnit->getASTContext().getLangOptions());
|
||||
CXDiagnosticSetImpl *Diags = lazyCreateDiags(Unit);
|
||||
if (Index >= Diags->getNumDiagnostics())
|
||||
return 0;
|
||||
|
||||
return Diags->getDiagnostic(Index);
|
||||
}
|
||||
|
||||
void clang_disposeDiagnostic(CXDiagnostic Diagnostic) {
|
||||
delete static_cast<CXDiagnosticImpl *>(Diagnostic);
|
||||
// No-op. Kept as a legacy API. CXDiagnostics are now managed
|
||||
// by the enclosing CXDiagnosticSet.
|
||||
}
|
||||
|
||||
CXString clang_formatDiagnostic(CXDiagnostic Diagnostic, unsigned Options) {
|
||||
@ -243,4 +274,32 @@ CXString clang_getDiagnosticFixIt(CXDiagnostic Diag, unsigned FixIt,
|
||||
return D->getFixIt(FixIt, ReplacementRange);
|
||||
}
|
||||
|
||||
void clang_disposeDiagnosticSet(CXDiagnosticSet Diags) {
|
||||
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 0;
|
||||
}
|
||||
|
||||
CXDiagnosticSet clang_getChildDiagnostics(CXDiagnostic Diag) {
|
||||
if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag)) {
|
||||
CXDiagnosticSetImpl &ChildDiags = D->getChildDiagnostics();
|
||||
return ChildDiags.empty() ? 0 : (CXDiagnosticSet) &ChildDiags;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned clang_getNumDiagnosticsInSet(CXDiagnosticSet Diags) {
|
||||
if (CXDiagnosticSetImpl *D = static_cast<CXDiagnosticSetImpl*>(Diags))
|
||||
return D->getNumDiagnostics();
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // end extern "C"
|
||||
|
@ -14,15 +14,47 @@
|
||||
#define LLVM_CLANG_CINDEX_DIAGNOSTIC_H
|
||||
|
||||
#include "clang-c/Index.h"
|
||||
#include <vector>
|
||||
#include <assert.h>
|
||||
|
||||
namespace clang {
|
||||
|
||||
class LangOptions;
|
||||
class StoredDiagnostic;
|
||||
class CXDiagnosticImpl;
|
||||
|
||||
class CXDiagnosticSetImpl {
|
||||
std::vector<CXDiagnosticImpl *> Diagnostics;
|
||||
const bool IsExternallyManaged;
|
||||
public:
|
||||
CXDiagnosticSetImpl(bool isManaged = false)
|
||||
: IsExternallyManaged(isManaged) {}
|
||||
|
||||
virtual ~CXDiagnosticSetImpl();
|
||||
|
||||
size_t getNumDiagnostics() const {
|
||||
return Diagnostics.size();
|
||||
}
|
||||
|
||||
CXDiagnosticImpl *getDiagnostic(unsigned i) const {
|
||||
assert(i < getNumDiagnostics());
|
||||
return Diagnostics[i];
|
||||
}
|
||||
|
||||
void appendDiagnostic(CXDiagnosticImpl *D) {
|
||||
Diagnostics.push_back(D);
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return Diagnostics.empty();
|
||||
}
|
||||
|
||||
bool isExternallyManaged() const { return IsExternallyManaged; }
|
||||
};
|
||||
|
||||
class CXDiagnosticImpl {
|
||||
public:
|
||||
enum Kind { StoredDiagnosticKind, SerializedDiagnosticKind };
|
||||
enum Kind { StoredDiagnosticKind, LoadedDiagnosticKind };
|
||||
|
||||
virtual ~CXDiagnosticImpl();
|
||||
|
||||
@ -55,9 +87,18 @@ public:
|
||||
CXSourceRange *ReplacementRange) const = 0;
|
||||
|
||||
Kind getKind() const { return K; }
|
||||
|
||||
|
||||
CXDiagnosticSetImpl &getChildDiagnostics() {
|
||||
return ChildDiags;
|
||||
}
|
||||
|
||||
protected:
|
||||
CXDiagnosticImpl(Kind k) : K(k) {}
|
||||
CXDiagnosticSetImpl ChildDiags;
|
||||
|
||||
void append(CXDiagnosticImpl *D) {
|
||||
ChildDiags.appendDiagnostic(D);
|
||||
}
|
||||
|
||||
private:
|
||||
Kind K;
|
||||
|
@ -29,6 +29,8 @@ set(SOURCES
|
||||
CIndexer.h
|
||||
CXCursor.cpp
|
||||
CXCursor.h
|
||||
CXLoadedDiagnostic.cpp
|
||||
CXLoadedDiagnostic.h
|
||||
CXSourceLocation.cpp
|
||||
CXSourceLocation.h
|
||||
CXStoredDiagnostic.cpp
|
||||
|
655
clang/tools/libclang/CXLoadedDiagnostic.cpp
Normal file
655
clang/tools/libclang/CXLoadedDiagnostic.cpp
Normal file
@ -0,0 +1,655 @@
|
||||
/*===-- CXLoadedDiagnostic.cpp - Handling of persisent diags -*- C++ -*-===*\
|
||||
|* *|
|
||||
|* The LLVM Compiler Infrastructure *|
|
||||
|* *|
|
||||
|* This file is distributed under the University of Illinois Open Source *|
|
||||
|* License. See LICENSE.TXT for details. *|
|
||||
|* *|
|
||||
|*===----------------------------------------------------------------------===*|
|
||||
|* *|
|
||||
|* Implements handling of persisent diagnostics. *|
|
||||
|* *|
|
||||
\*===----------------------------------------------------------------------===*/
|
||||
|
||||
#include "CXLoadedDiagnostic.h"
|
||||
#include "CXString.h"
|
||||
#include "clang/Basic/Diagnostic.h"
|
||||
#include "clang/Basic/FileManager.h"
|
||||
#include "clang/Frontend/SerializedDiagnosticPrinter.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/ADT/Twine.h"
|
||||
#include "llvm/ADT/Optional.h"
|
||||
#include "clang/Basic/LLVM.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
#include "llvm/Bitcode/BitstreamReader.h"
|
||||
#include "llvm/Support/MemoryBuffer.h"
|
||||
#include <assert.h>
|
||||
|
||||
using namespace clang;
|
||||
using namespace clang::cxstring;
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Extend CXDiagnosticSetImpl which contains strings for diagnostics.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
typedef llvm::DenseMap<unsigned, llvm::StringRef> Strings;
|
||||
|
||||
namespace {
|
||||
class CXLoadedDiagnosticSetImpl : public CXDiagnosticSetImpl {
|
||||
public:
|
||||
CXLoadedDiagnosticSetImpl() : CXDiagnosticSetImpl(true), FakeFiles(FO) {}
|
||||
virtual ~CXLoadedDiagnosticSetImpl() {}
|
||||
|
||||
llvm::StringRef makeString(const char *blob, unsigned blobLen);
|
||||
|
||||
llvm::BumpPtrAllocator Alloc;
|
||||
Strings Categories;
|
||||
Strings WarningFlags;
|
||||
Strings FileNames;
|
||||
|
||||
FileSystemOptions FO;
|
||||
FileManager FakeFiles;
|
||||
llvm::DenseMap<unsigned, const FileEntry *> Files;
|
||||
};
|
||||
}
|
||||
|
||||
llvm::StringRef CXLoadedDiagnosticSetImpl::makeString(const char *blob,
|
||||
unsigned bloblen) {
|
||||
char *mem = Alloc.Allocate<char>(bloblen);
|
||||
memcpy(mem, blob, bloblen);
|
||||
return llvm::StringRef(mem, bloblen);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Cleanup.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
CXLoadedDiagnostic::~CXLoadedDiagnostic() {}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Public CXLoadedDiagnostic methods.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
CXDiagnosticSeverity CXLoadedDiagnostic::getSeverity() const {
|
||||
// FIXME: possibly refactor with logic in CXStoredDiagnostic.
|
||||
switch (severity) {
|
||||
case DiagnosticsEngine::Ignored: return CXDiagnostic_Ignored;
|
||||
case DiagnosticsEngine::Note: return CXDiagnostic_Note;
|
||||
case DiagnosticsEngine::Warning: return CXDiagnostic_Warning;
|
||||
case DiagnosticsEngine::Error: return CXDiagnostic_Error;
|
||||
case DiagnosticsEngine::Fatal: return CXDiagnostic_Fatal;
|
||||
}
|
||||
|
||||
llvm_unreachable("Invalid diagnostic level");
|
||||
return CXDiagnostic_Ignored;
|
||||
}
|
||||
|
||||
static CXSourceLocation makeLocation(const CXLoadedDiagnostic::Location *DLoc) {
|
||||
// The lowest bit of ptr_data[0] is always set to 1 to indicate this
|
||||
// is a persistent diagnostic.
|
||||
uintptr_t V = (uintptr_t) DLoc;
|
||||
V |= 0x1;
|
||||
CXSourceLocation Loc = { { (void*) V, 0 }, 0 };
|
||||
return Loc;
|
||||
}
|
||||
|
||||
CXSourceLocation CXLoadedDiagnostic::getLocation() const {
|
||||
// The lowest bit of ptr_data[0] is always set to 1 to indicate this
|
||||
// is a persistent diagnostic.
|
||||
return makeLocation(&DiagLoc);
|
||||
}
|
||||
|
||||
CXString CXLoadedDiagnostic::getSpelling() const {
|
||||
return cxstring::createCXString(Spelling, false);
|
||||
}
|
||||
|
||||
CXString CXLoadedDiagnostic::getDiagnosticOption(CXString *Disable) const {
|
||||
if (DiagOption.empty())
|
||||
return createCXString("");
|
||||
|
||||
// FIXME: possibly refactor with logic in CXStoredDiagnostic.
|
||||
if (Disable)
|
||||
*Disable = createCXString((Twine("-Wno-") + DiagOption).str());
|
||||
return createCXString((Twine("-W") + DiagOption).str());
|
||||
}
|
||||
|
||||
unsigned CXLoadedDiagnostic::getCategory() const {
|
||||
return category;
|
||||
}
|
||||
|
||||
unsigned CXLoadedDiagnostic::getNumRanges() const {
|
||||
return Ranges.size();
|
||||
}
|
||||
|
||||
CXSourceRange CXLoadedDiagnostic::getRange(unsigned Range) const {
|
||||
assert(Range < Ranges.size());
|
||||
return Ranges[Range];
|
||||
}
|
||||
|
||||
unsigned CXLoadedDiagnostic::getNumFixIts() const {
|
||||
return FixIts.size();
|
||||
}
|
||||
|
||||
CXString CXLoadedDiagnostic::getFixIt(unsigned FixIt,
|
||||
CXSourceRange *ReplacementRange) const {
|
||||
assert(FixIt < FixIts.size());
|
||||
if (ReplacementRange)
|
||||
*ReplacementRange = FixIts[FixIt].first;
|
||||
return FixIts[FixIt].second;
|
||||
}
|
||||
|
||||
void CXLoadedDiagnostic::decodeLocation(CXSourceLocation location,
|
||||
CXFile *file,
|
||||
unsigned int *line,
|
||||
unsigned int *column,
|
||||
unsigned int *offset) {
|
||||
|
||||
|
||||
// CXSourceLocation consists of the following fields:
|
||||
//
|
||||
// void *ptr_data[2];
|
||||
// unsigned int_data;
|
||||
//
|
||||
// The lowest bit of ptr_data[0] is always set to 1 to indicate this
|
||||
// is a persistent diagnostic.
|
||||
//
|
||||
// For now, do the unoptimized approach and store the data in a side
|
||||
// data structure. We can optimize this case later.
|
||||
|
||||
uintptr_t V = (uintptr_t) location.ptr_data[0];
|
||||
assert((V & 0x1) == 1);
|
||||
V &= ~(uintptr_t)1;
|
||||
|
||||
const Location &Loc = *((Location*)V);
|
||||
|
||||
if (file)
|
||||
*file = Loc.file;
|
||||
if (line)
|
||||
*line = Loc.line;
|
||||
if (column)
|
||||
*column = Loc.column;
|
||||
if (offset)
|
||||
*offset = Loc.offset;
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Deserialize diagnostics.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
enum { MaxSupportedVersion = 1 };
|
||||
typedef SmallVector<uint64_t, 64> RecordData;
|
||||
enum LoadResult { Failure = 1, Success = 0 };
|
||||
enum StreamResult { Read_EndOfStream,
|
||||
Read_BlockBegin,
|
||||
Read_Failure,
|
||||
Read_Record,
|
||||
Read_BlockEnd };
|
||||
|
||||
namespace {
|
||||
class DiagLoader {
|
||||
enum CXLoadDiag_Error *error;
|
||||
CXString *errorString;
|
||||
|
||||
void reportBad(enum CXLoadDiag_Error code, llvm::StringRef err) {
|
||||
if (error)
|
||||
*error = code;
|
||||
if (errorString)
|
||||
*errorString = createCXString(err);
|
||||
}
|
||||
|
||||
void reportInvalidFile(llvm::StringRef err) {
|
||||
return reportBad(CXLoadDiag_InvalidFile, err);
|
||||
}
|
||||
|
||||
LoadResult readMetaBlock(llvm::BitstreamCursor &Stream);
|
||||
|
||||
LoadResult readDiagnosticBlock(llvm::BitstreamCursor &Stream,
|
||||
CXDiagnosticSetImpl &Diags,
|
||||
CXLoadedDiagnosticSetImpl &TopDiags);
|
||||
|
||||
StreamResult readToNextRecordOrBlock(llvm::BitstreamCursor &Stream,
|
||||
llvm::StringRef errorContext,
|
||||
unsigned &BlockOrRecordID,
|
||||
const bool atTopLevel = false);
|
||||
|
||||
|
||||
LoadResult readString(CXLoadedDiagnosticSetImpl &TopDiags,
|
||||
Strings &strings, llvm::StringRef errorContext,
|
||||
RecordData &Record,
|
||||
const char *BlobStart,
|
||||
unsigned BlobLen);
|
||||
|
||||
LoadResult readString(CXLoadedDiagnosticSetImpl &TopDiags,
|
||||
llvm::StringRef &RetStr,
|
||||
llvm::StringRef errorContext,
|
||||
RecordData &Record,
|
||||
const char *BlobStart,
|
||||
unsigned BlobLen);
|
||||
|
||||
LoadResult readRange(CXLoadedDiagnosticSetImpl &TopDiags,
|
||||
RecordData &Record, unsigned RecStartIdx,
|
||||
CXSourceRange &SR);
|
||||
|
||||
LoadResult readLocation(CXLoadedDiagnosticSetImpl &TopDiags,
|
||||
RecordData &Record, unsigned &offset,
|
||||
CXLoadedDiagnostic::Location &Loc);
|
||||
|
||||
public:
|
||||
DiagLoader(enum CXLoadDiag_Error *e, CXString *es)
|
||||
: error(e), errorString(es) {
|
||||
if (error)
|
||||
*error = CXLoadDiag_None;
|
||||
if (errorString)
|
||||
*errorString = createCXString("");
|
||||
}
|
||||
|
||||
CXDiagnosticSet load(const char *file);
|
||||
};
|
||||
}
|
||||
|
||||
CXDiagnosticSet DiagLoader::load(const char *file) {
|
||||
// Open the diagnostics file.
|
||||
std::string ErrStr;
|
||||
FileSystemOptions FO;
|
||||
FileManager FileMgr(FO);
|
||||
|
||||
llvm::OwningPtr<llvm::MemoryBuffer> Buffer;
|
||||
Buffer.reset(FileMgr.getBufferForFile(file));
|
||||
|
||||
if (!Buffer) {
|
||||
reportBad(CXLoadDiag_CannotLoad, ErrStr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
llvm::BitstreamReader StreamFile;
|
||||
StreamFile.init((const unsigned char *)Buffer->getBufferStart(),
|
||||
(const unsigned char *)Buffer->getBufferEnd());
|
||||
|
||||
llvm::BitstreamCursor Stream;
|
||||
Stream.init(StreamFile);
|
||||
|
||||
// Sniff for the signature.
|
||||
if (Stream.Read(8) != 'D' ||
|
||||
Stream.Read(8) != 'I' ||
|
||||
Stream.Read(8) != 'A' ||
|
||||
Stream.Read(8) != 'G') {
|
||||
reportBad(CXLoadDiag_InvalidFile,
|
||||
"Bad header in diagnostics file");
|
||||
return 0;
|
||||
}
|
||||
|
||||
llvm::OwningPtr<CXLoadedDiagnosticSetImpl>
|
||||
Diags(new CXLoadedDiagnosticSetImpl());
|
||||
|
||||
while (true) {
|
||||
unsigned BlockID = 0;
|
||||
StreamResult Res = readToNextRecordOrBlock(Stream, "Top-level",
|
||||
BlockID, true);
|
||||
switch (Res) {
|
||||
case Read_EndOfStream:
|
||||
return (CXDiagnosticSet) Diags.take();
|
||||
case Read_Failure:
|
||||
return 0;
|
||||
case Read_Record:
|
||||
llvm_unreachable("Top-level does not have records");
|
||||
return 0;
|
||||
case Read_BlockEnd:
|
||||
continue;
|
||||
case Read_BlockBegin:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (BlockID) {
|
||||
case serialized_diags::BLOCK_META:
|
||||
if (readMetaBlock(Stream))
|
||||
return 0;
|
||||
break;
|
||||
case serialized_diags::BLOCK_DIAG:
|
||||
if (readDiagnosticBlock(Stream, *Diags.get(), *Diags.get()))
|
||||
return 0;
|
||||
break;
|
||||
default:
|
||||
if (!Stream.SkipBlock()) {
|
||||
reportInvalidFile("Malformed block at top-level of diagnostics file");
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StreamResult DiagLoader::readToNextRecordOrBlock(llvm::BitstreamCursor &Stream,
|
||||
llvm::StringRef errorContext,
|
||||
unsigned &blockOrRecordID,
|
||||
const bool atTopLevel) {
|
||||
|
||||
blockOrRecordID = 0;
|
||||
|
||||
while (!Stream.AtEndOfStream()) {
|
||||
unsigned Code = Stream.ReadCode();
|
||||
|
||||
// Handle the top-level specially.
|
||||
if (atTopLevel) {
|
||||
if (Code == llvm::bitc::ENTER_SUBBLOCK) {
|
||||
unsigned BlockID = Stream.ReadSubBlockID();
|
||||
if (BlockID == llvm::bitc::BLOCKINFO_BLOCK_ID) {
|
||||
if (Stream.ReadBlockInfoBlock()) {
|
||||
reportInvalidFile("Malformed BlockInfoBlock in diagnostics file");
|
||||
return Read_Failure;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
blockOrRecordID = BlockID;
|
||||
return Read_BlockBegin;
|
||||
}
|
||||
reportInvalidFile("Only blocks can appear at the top of a "
|
||||
"diagnostic file");
|
||||
return Read_Failure;
|
||||
}
|
||||
|
||||
switch ((llvm::bitc::FixedAbbrevIDs)Code) {
|
||||
case llvm::bitc::ENTER_SUBBLOCK:
|
||||
blockOrRecordID = Stream.ReadSubBlockID();
|
||||
return Read_BlockBegin;
|
||||
|
||||
case llvm::bitc::END_BLOCK:
|
||||
if (Stream.ReadBlockEnd()) {
|
||||
reportInvalidFile("Cannot read end of block");
|
||||
return Read_Failure;
|
||||
}
|
||||
return Read_BlockEnd;
|
||||
|
||||
case llvm::bitc::DEFINE_ABBREV:
|
||||
Stream.ReadAbbrevRecord();
|
||||
continue;
|
||||
|
||||
case llvm::bitc::UNABBREV_RECORD:
|
||||
reportInvalidFile("Diagnostics file should have no unabbreviated "
|
||||
"records");
|
||||
return Read_Failure;
|
||||
|
||||
default:
|
||||
// We found a record.
|
||||
blockOrRecordID = Code;
|
||||
return Read_Record;
|
||||
}
|
||||
}
|
||||
|
||||
if (atTopLevel)
|
||||
return Read_EndOfStream;
|
||||
|
||||
reportInvalidFile(Twine("Premature end of diagnostics file within ").str() +
|
||||
errorContext.str());
|
||||
return Read_Failure;
|
||||
}
|
||||
|
||||
LoadResult DiagLoader::readMetaBlock(llvm::BitstreamCursor &Stream) {
|
||||
if (Stream.EnterSubBlock(clang::serialized_diags::BLOCK_META)) {
|
||||
reportInvalidFile("Malformed metadata block");
|
||||
return Failure;
|
||||
}
|
||||
|
||||
bool versionChecked = false;
|
||||
|
||||
while (true) {
|
||||
unsigned blockOrCode = 0;
|
||||
StreamResult Res = readToNextRecordOrBlock(Stream, "Metadata Block",
|
||||
blockOrCode);
|
||||
|
||||
switch(Res) {
|
||||
case Read_EndOfStream:
|
||||
llvm_unreachable("EndOfStream handled by readToNextRecordOrBlock");
|
||||
case Read_Failure:
|
||||
return Failure;
|
||||
case Read_Record:
|
||||
break;
|
||||
case Read_BlockBegin:
|
||||
if (Stream.SkipBlock()) {
|
||||
reportInvalidFile("Malformed metadata block");
|
||||
return Failure;
|
||||
}
|
||||
case Read_BlockEnd:
|
||||
if (!versionChecked) {
|
||||
reportInvalidFile("Diagnostics file does not contain version"
|
||||
" information");
|
||||
return Failure;
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
RecordData Record;
|
||||
const char *Blob;
|
||||
unsigned BlobLen;
|
||||
unsigned recordID = Stream.ReadRecord(blockOrCode, Record, &Blob, &BlobLen);
|
||||
|
||||
if (recordID == serialized_diags::RECORD_VERSION) {
|
||||
if (Record.size() < 1) {
|
||||
reportInvalidFile("malformed VERSION identifier in diagnostics file");
|
||||
return Failure;
|
||||
}
|
||||
if (Record[0] > MaxSupportedVersion) {
|
||||
reportInvalidFile("diagnosics file is a newer version than the one "
|
||||
"supported");
|
||||
return Failure;
|
||||
}
|
||||
versionChecked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LoadResult DiagLoader::readString(CXLoadedDiagnosticSetImpl &TopDiags,
|
||||
llvm::StringRef &RetStr,
|
||||
llvm::StringRef errorContext,
|
||||
RecordData &Record,
|
||||
const char *BlobStart,
|
||||
unsigned BlobLen) {
|
||||
|
||||
// Basic buffer overflow check.
|
||||
if (BlobLen > 65536) {
|
||||
reportInvalidFile(std::string("Out-of-bounds string in ") +
|
||||
std::string(errorContext));
|
||||
return Failure;
|
||||
}
|
||||
|
||||
if (Record.size() < 1 || BlobLen == 0) {
|
||||
reportInvalidFile(std::string("Corrupted ") + std::string(errorContext)
|
||||
+ std::string(" entry"));
|
||||
return Failure;
|
||||
}
|
||||
|
||||
RetStr = TopDiags.makeString(BlobStart, BlobLen);
|
||||
return Success;
|
||||
}
|
||||
|
||||
LoadResult DiagLoader::readString(CXLoadedDiagnosticSetImpl &TopDiags,
|
||||
Strings &strings,
|
||||
llvm::StringRef errorContext,
|
||||
RecordData &Record,
|
||||
const char *BlobStart,
|
||||
unsigned BlobLen) {
|
||||
llvm::StringRef RetStr;
|
||||
if (readString(TopDiags, RetStr, errorContext, Record, BlobStart, BlobLen))
|
||||
return Failure;
|
||||
strings[Record[0]] = RetStr;
|
||||
return Success;
|
||||
}
|
||||
|
||||
LoadResult DiagLoader::readLocation(CXLoadedDiagnosticSetImpl &TopDiags,
|
||||
RecordData &Record, unsigned &offset,
|
||||
CXLoadedDiagnostic::Location &Loc) {
|
||||
if (Record.size() < offset + 3) {
|
||||
reportInvalidFile("Corrupted source location");
|
||||
return Failure;
|
||||
}
|
||||
|
||||
unsigned fileID = Record[offset++];
|
||||
if (fileID == 0) {
|
||||
// Sentinel value.
|
||||
Loc.file = 0;
|
||||
Loc.line = 0;
|
||||
Loc.column = 0;
|
||||
Loc.offset = 0;
|
||||
return Success;
|
||||
}
|
||||
|
||||
const FileEntry *FE = TopDiags.Files[fileID];
|
||||
if (!FE) {
|
||||
reportInvalidFile("Corrupted file entry in source location");
|
||||
return Failure;
|
||||
}
|
||||
Loc.file = (void*) FE;
|
||||
Loc.line = Record[offset++];
|
||||
Loc.column = Record[offset++];
|
||||
Loc.offset = Record[offset++];
|
||||
return Success;
|
||||
}
|
||||
|
||||
LoadResult DiagLoader::readRange(CXLoadedDiagnosticSetImpl &TopDiags,
|
||||
RecordData &Record,
|
||||
unsigned int RecStartIdx,
|
||||
CXSourceRange &SR) {
|
||||
CXLoadedDiagnostic::Location *Start, *End;
|
||||
Start = TopDiags.Alloc.Allocate<CXLoadedDiagnostic::Location>();
|
||||
End = TopDiags.Alloc.Allocate<CXLoadedDiagnostic::Location>();
|
||||
|
||||
if (readLocation(TopDiags, Record, RecStartIdx, *Start))
|
||||
return Failure;
|
||||
if (readLocation(TopDiags, Record, RecStartIdx, *End))
|
||||
return Failure;
|
||||
|
||||
CXSourceLocation startLoc = makeLocation(Start);
|
||||
CXSourceLocation endLoc = makeLocation(End);
|
||||
SR = clang_getRange(startLoc, endLoc);
|
||||
return Success;
|
||||
}
|
||||
|
||||
LoadResult DiagLoader::readDiagnosticBlock(llvm::BitstreamCursor &Stream,
|
||||
CXDiagnosticSetImpl &Diags,
|
||||
CXLoadedDiagnosticSetImpl &TopDiags){
|
||||
|
||||
if (Stream.EnterSubBlock(clang::serialized_diags::BLOCK_DIAG)) {
|
||||
reportInvalidFile("malformed diagnostic block");
|
||||
return Failure;
|
||||
}
|
||||
|
||||
llvm::OwningPtr<CXLoadedDiagnostic> D(new CXLoadedDiagnostic());
|
||||
RecordData Record;
|
||||
|
||||
while (true) {
|
||||
unsigned blockOrCode = 0;
|
||||
StreamResult Res = readToNextRecordOrBlock(Stream, "Diagnostic Block",
|
||||
blockOrCode);
|
||||
switch (Res) {
|
||||
case Read_EndOfStream:
|
||||
llvm_unreachable("EndOfStream handled in readToNextRecordOrBlock");
|
||||
return Failure;
|
||||
case Read_Failure:
|
||||
return Failure;
|
||||
case Read_BlockBegin: {
|
||||
// The only blocks we care about are subdiagnostics.
|
||||
if (blockOrCode != serialized_diags::BLOCK_DIAG) {
|
||||
if (!Stream.SkipBlock()) {
|
||||
reportInvalidFile("Invalid subblock in Diagnostics block");
|
||||
return Failure;
|
||||
}
|
||||
} else if (readDiagnosticBlock(Stream, D->getChildDiagnostics(),
|
||||
TopDiags)) {
|
||||
return Failure;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
case Read_BlockEnd:
|
||||
Diags.appendDiagnostic(D.take());
|
||||
return Success;
|
||||
case Read_Record:
|
||||
break;
|
||||
}
|
||||
|
||||
// Read the record.
|
||||
Record.clear();
|
||||
const char *BlobStart = 0;
|
||||
unsigned BlobLen = 0;
|
||||
unsigned recID = Stream.ReadRecord(blockOrCode, Record,
|
||||
BlobStart, BlobLen);
|
||||
|
||||
if (recID < serialized_diags::RECORD_FIRST ||
|
||||
recID > serialized_diags::RECORD_LAST)
|
||||
continue;
|
||||
|
||||
switch ((serialized_diags::RecordIDs)recID) {
|
||||
case serialized_diags::RECORD_VERSION:
|
||||
continue;
|
||||
case serialized_diags::RECORD_CATEGORY:
|
||||
if (readString(TopDiags, TopDiags.Categories, "category", Record,
|
||||
BlobStart, BlobLen))
|
||||
return Failure;
|
||||
continue;
|
||||
|
||||
case serialized_diags::RECORD_DIAG_FLAG:
|
||||
if (readString(TopDiags, TopDiags.WarningFlags, "warning flag", Record,
|
||||
BlobStart, BlobLen))
|
||||
return Failure;
|
||||
continue;
|
||||
|
||||
case serialized_diags::RECORD_FILENAME: {
|
||||
if (readString(TopDiags, TopDiags.FileNames, "filename", Record,
|
||||
BlobStart, BlobLen))
|
||||
return Failure;
|
||||
|
||||
if (Record.size() < 3) {
|
||||
reportInvalidFile("Invalid file entry");
|
||||
return Failure;
|
||||
}
|
||||
|
||||
const FileEntry *FE =
|
||||
TopDiags.FakeFiles.getVirtualFile(TopDiags.FileNames[Record[0]],
|
||||
/* size */ Record[1],
|
||||
/* time */ Record[2]);
|
||||
|
||||
TopDiags.Files[Record[0]] = FE;
|
||||
continue;
|
||||
}
|
||||
|
||||
case serialized_diags::RECORD_SOURCE_RANGE: {
|
||||
CXSourceRange SR;
|
||||
if (readRange(TopDiags, Record, 0, SR))
|
||||
return Failure;
|
||||
D->Ranges.push_back(SR);
|
||||
continue;
|
||||
}
|
||||
|
||||
case serialized_diags::RECORD_FIXIT: {
|
||||
CXSourceRange SR;
|
||||
if (readRange(TopDiags, Record, 0, SR))
|
||||
return Failure;
|
||||
llvm::StringRef RetStr;
|
||||
if (readString(TopDiags, RetStr, "FIXIT", Record, BlobStart, BlobLen))
|
||||
return Failure;
|
||||
D->FixIts.push_back(std::make_pair(SR, createCXString(RetStr, false)));
|
||||
continue;
|
||||
}
|
||||
|
||||
case serialized_diags::RECORD_DIAG: {
|
||||
D->severity = Record[0];
|
||||
unsigned offset = 1;
|
||||
if (readLocation(TopDiags, Record, offset, D->DiagLoc))
|
||||
return Failure;
|
||||
D->category = Record[offset++];
|
||||
unsigned diagFlag = Record[offset++];
|
||||
D->DiagOption = diagFlag ? TopDiags.WarningFlags[diagFlag] : "";
|
||||
D->Spelling = TopDiags.makeString(BlobStart, BlobLen);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
CXDiagnosticSet clang_loadDiagnostics(const char *file,
|
||||
enum CXLoadDiag_Error *error,
|
||||
CXString *errorString) {
|
||||
DiagLoader L(error, errorString);
|
||||
return L.load(file);
|
||||
}
|
||||
} // end extern 'C'.
|
90
clang/tools/libclang/CXLoadedDiagnostic.h
Normal file
90
clang/tools/libclang/CXLoadedDiagnostic.h
Normal file
@ -0,0 +1,90 @@
|
||||
/*===-- CXLoadedDiagnostic.h - Handling of persisent diags ------*- C++ -*-===*\
|
||||
|* *|
|
||||
|* The LLVM Compiler Infrastructure *|
|
||||
|* *|
|
||||
|* This file is distributed under the University of Illinois Open Source *|
|
||||
|* License. See LICENSE.TXT for details. *|
|
||||
|* *|
|
||||
|*===----------------------------------------------------------------------===*|
|
||||
|* *|
|
||||
|* Implements handling of persisent diagnostics. *|
|
||||
|* *|
|
||||
\*===----------------------------------------------------------------------===*/
|
||||
|
||||
#ifndef LLVM_CLANG_CINDEX_LOADED_DIAGNOSTIC_H
|
||||
#define LLVM_CLANG_CINDEX_LOADED_DIAGNOSTIC_H
|
||||
|
||||
#include "CIndexDiagnostic.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "clang/Basic/LLVM.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace clang {
|
||||
class CXLoadedDiagnostic : public CXDiagnosticImpl {
|
||||
public:
|
||||
CXLoadedDiagnostic() : CXDiagnosticImpl(LoadedDiagnosticKind),
|
||||
severity(0), category(0) {}
|
||||
|
||||
virtual ~CXLoadedDiagnostic();
|
||||
|
||||
/// \brief Return the severity of the diagnostic.
|
||||
virtual CXDiagnosticSeverity getSeverity() const;
|
||||
|
||||
/// \brief Return the location of the diagnostic.
|
||||
virtual CXSourceLocation getLocation() const;
|
||||
|
||||
/// \brief Return the spelling of the diagnostic.
|
||||
virtual CXString getSpelling() const;
|
||||
|
||||
/// \brief Return the text for the diagnostic option.
|
||||
virtual CXString getDiagnosticOption(CXString *Disable) const;
|
||||
|
||||
/// \brief Return the category of the diagnostic.
|
||||
virtual unsigned getCategory() const;
|
||||
|
||||
/// \brief Return the number of source ranges for the diagnostic.
|
||||
virtual unsigned getNumRanges() const;
|
||||
|
||||
/// \brief Return the source ranges for the diagnostic.
|
||||
virtual CXSourceRange getRange(unsigned Range) const;
|
||||
|
||||
/// \brief Return the number of FixIts.
|
||||
virtual unsigned getNumFixIts() const;
|
||||
|
||||
/// \brief Return the FixIt information (source range and inserted text).
|
||||
virtual CXString getFixIt(unsigned FixIt,
|
||||
CXSourceRange *ReplacementRange) const;
|
||||
|
||||
static bool classof(const CXDiagnosticImpl *D) {
|
||||
return D->getKind() == LoadedDiagnosticKind;
|
||||
}
|
||||
|
||||
/// \brief Decode the CXSourceLocation into file, line, column, and offset.
|
||||
static void decodeLocation(CXSourceLocation location,
|
||||
CXFile *file,
|
||||
unsigned *line,
|
||||
unsigned *column,
|
||||
unsigned *offset);
|
||||
|
||||
struct Location {
|
||||
CXFile file;
|
||||
unsigned line;
|
||||
unsigned column;
|
||||
unsigned offset;
|
||||
|
||||
Location() : line(0), column(0), offset(0) {}
|
||||
};
|
||||
|
||||
Location DiagLoc;
|
||||
|
||||
std::vector<CXSourceRange> Ranges;
|
||||
std::vector<std::pair<CXSourceRange, CXString> > FixIts;
|
||||
llvm::StringRef Spelling;
|
||||
llvm::StringRef DiagOption;
|
||||
unsigned severity;
|
||||
unsigned category;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
@ -17,6 +17,7 @@
|
||||
#include "CXString.h"
|
||||
#include "CXSourceLocation.h"
|
||||
#include "CXTranslationUnit.h"
|
||||
#include "CXLoadedDiagnostic.h"
|
||||
|
||||
using namespace clang;
|
||||
using namespace clang::cxstring;
|
||||
@ -54,6 +55,13 @@ CXSourceRange clang_getNullRange() {
|
||||
}
|
||||
|
||||
CXSourceRange clang_getRange(CXSourceLocation begin, CXSourceLocation end) {
|
||||
if (!isASTUnitSourceLocation(begin)) {
|
||||
if (isASTUnitSourceLocation(end))
|
||||
return clang_getNullRange();
|
||||
CXSourceRange Result = { { begin.ptr_data[0], end.ptr_data[0] }, 0, 0 };
|
||||
return Result;
|
||||
}
|
||||
|
||||
if (begin.ptr_data[0] != end.ptr_data[0] ||
|
||||
begin.ptr_data[1] != end.ptr_data[1])
|
||||
return clang_getNullRange();
|
||||
@ -64,7 +72,6 @@ CXSourceRange clang_getRange(CXSourceLocation begin, CXSourceLocation end) {
|
||||
return Result;
|
||||
}
|
||||
|
||||
|
||||
unsigned clang_equalRanges(CXSourceRange range1, CXSourceRange range2) {
|
||||
return range1.ptr_data[0] == range2.ptr_data[0]
|
||||
&& range1.ptr_data[1] == range2.ptr_data[1]
|
||||
@ -78,12 +85,24 @@ int clang_Range_isNull(CXSourceRange range) {
|
||||
|
||||
|
||||
CXSourceLocation clang_getRangeStart(CXSourceRange range) {
|
||||
// Special decoding for CXSourceLocations for CXLoadedDiagnostics.
|
||||
if ((uintptr_t)range.ptr_data[0] & 0x1) {
|
||||
CXSourceLocation Result = { { range.ptr_data[0], 0 }, 0 };
|
||||
return Result;
|
||||
}
|
||||
|
||||
CXSourceLocation Result = { { range.ptr_data[0], range.ptr_data[1] },
|
||||
range.begin_int_data };
|
||||
return Result;
|
||||
}
|
||||
|
||||
CXSourceLocation clang_getRangeEnd(CXSourceRange range) {
|
||||
// Special decoding for CXSourceLocations for CXLoadedDiagnostics.
|
||||
if ((uintptr_t)range.ptr_data[0] & 0x1) {
|
||||
CXSourceLocation Result = { { range.ptr_data[1], 0 }, 0 };
|
||||
return Result;
|
||||
}
|
||||
|
||||
CXSourceLocation Result = { { range.ptr_data[0], range.ptr_data[1] },
|
||||
range.end_int_data };
|
||||
return Result;
|
||||
@ -182,41 +201,40 @@ void clang_getExpansionLocation(CXSourceLocation location,
|
||||
unsigned *column,
|
||||
unsigned *offset) {
|
||||
|
||||
if (isASTUnitSourceLocation(location)) {
|
||||
SourceLocation Loc = SourceLocation::getFromRawEncoding(location.int_data);
|
||||
if (!isASTUnitSourceLocation(location)) {
|
||||
CXLoadedDiagnostic::decodeLocation(location, file, line, column, offset);
|
||||
return;
|
||||
}
|
||||
|
||||
SourceLocation Loc = SourceLocation::getFromRawEncoding(location.int_data);
|
||||
|
||||
if (!location.ptr_data[0] || Loc.isInvalid()) {
|
||||
createNullLocation(file, line, column, offset);
|
||||
return;
|
||||
}
|
||||
|
||||
const SourceManager &SM =
|
||||
*static_cast<const SourceManager*>(location.ptr_data[0]);
|
||||
SourceLocation ExpansionLoc = SM.getExpansionLoc(Loc);
|
||||
|
||||
if (!location.ptr_data[0] || Loc.isInvalid()) {
|
||||
createNullLocation(file, line, column, offset);
|
||||
return;
|
||||
}
|
||||
|
||||
const SourceManager &SM =
|
||||
*static_cast<const SourceManager*>(location.ptr_data[0]);
|
||||
SourceLocation ExpansionLoc = SM.getExpansionLoc(Loc);
|
||||
|
||||
// Check that the FileID is invalid on the expansion location.
|
||||
// This can manifest in invalid code.
|
||||
FileID fileID = SM.getFileID(ExpansionLoc);
|
||||
bool Invalid = false;
|
||||
const SrcMgr::SLocEntry &sloc = SM.getSLocEntry(fileID, &Invalid);
|
||||
if (Invalid || !sloc.isFile()) {
|
||||
createNullLocation(file, line, column, offset);
|
||||
return;
|
||||
}
|
||||
|
||||
if (file)
|
||||
*file = (void *)SM.getFileEntryForSLocEntry(sloc);
|
||||
if (line)
|
||||
*line = SM.getExpansionLineNumber(ExpansionLoc);
|
||||
if (column)
|
||||
*column = SM.getExpansionColumnNumber(ExpansionLoc);
|
||||
if (offset)
|
||||
*offset = SM.getDecomposedLoc(ExpansionLoc).second;
|
||||
// Check that the FileID is invalid on the expansion location.
|
||||
// This can manifest in invalid code.
|
||||
FileID fileID = SM.getFileID(ExpansionLoc);
|
||||
bool Invalid = false;
|
||||
const SrcMgr::SLocEntry &sloc = SM.getSLocEntry(fileID, &Invalid);
|
||||
if (Invalid || !sloc.isFile()) {
|
||||
createNullLocation(file, line, column, offset);
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME:
|
||||
createNullLocation(file, line, column, offset);
|
||||
if (file)
|
||||
*file = (void *)SM.getFileEntryForSLocEntry(sloc);
|
||||
if (line)
|
||||
*line = SM.getExpansionLineNumber(ExpansionLoc);
|
||||
if (column)
|
||||
*column = SM.getExpansionColumnNumber(ExpansionLoc);
|
||||
if (offset)
|
||||
*offset = SM.getDecomposedLoc(ExpansionLoc).second;
|
||||
}
|
||||
|
||||
void clang_getPresumedLocation(CXSourceLocation location,
|
||||
@ -224,28 +242,29 @@ void clang_getPresumedLocation(CXSourceLocation location,
|
||||
unsigned *line,
|
||||
unsigned *column) {
|
||||
|
||||
if (isASTUnitSourceLocation(location)) {
|
||||
SourceLocation Loc = SourceLocation::getFromRawEncoding(location.int_data);
|
||||
|
||||
if (!location.ptr_data[0] || Loc.isInvalid())
|
||||
createNullLocation(filename, line, column);
|
||||
else {
|
||||
const SourceManager &SM =
|
||||
*static_cast<const SourceManager*>(location.ptr_data[0]);
|
||||
PresumedLoc PreLoc = SM.getPresumedLoc(Loc);
|
||||
|
||||
if (filename)
|
||||
*filename = createCXString(PreLoc.getFilename());
|
||||
if (line)
|
||||
*line = PreLoc.getLine();
|
||||
if (column)
|
||||
*column = PreLoc.getColumn();
|
||||
}
|
||||
if (!isASTUnitSourceLocation(location)) {
|
||||
// Other SourceLocation implementations do not support presumed locations
|
||||
// at this time.
|
||||
createNullLocation(filename, line, column);
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME:
|
||||
createNullLocation(filename, line, column);
|
||||
|
||||
SourceLocation Loc = SourceLocation::getFromRawEncoding(location.int_data);
|
||||
|
||||
if (!location.ptr_data[0] || Loc.isInvalid())
|
||||
createNullLocation(filename, line, column);
|
||||
else {
|
||||
const SourceManager &SM =
|
||||
*static_cast<const SourceManager*>(location.ptr_data[0]);
|
||||
PresumedLoc PreLoc = SM.getPresumedLoc(Loc);
|
||||
|
||||
if (filename)
|
||||
*filename = createCXString(PreLoc.getFilename());
|
||||
if (line)
|
||||
*line = PreLoc.getLine();
|
||||
if (column)
|
||||
*column = PreLoc.getColumn();
|
||||
}
|
||||
}
|
||||
|
||||
void clang_getInstantiationLocation(CXSourceLocation location,
|
||||
@ -263,44 +282,44 @@ void clang_getSpellingLocation(CXSourceLocation location,
|
||||
unsigned *column,
|
||||
unsigned *offset) {
|
||||
|
||||
if (isASTUnitSourceLocation(location)) {
|
||||
SourceLocation Loc = SourceLocation::getFromRawEncoding(location.int_data);
|
||||
|
||||
if (!location.ptr_data[0] || Loc.isInvalid())
|
||||
return createNullLocation(file, line, column, offset);
|
||||
|
||||
const SourceManager &SM =
|
||||
*static_cast<const SourceManager*>(location.ptr_data[0]);
|
||||
SourceLocation SpellLoc = Loc;
|
||||
if (SpellLoc.isMacroID()) {
|
||||
SourceLocation SimpleSpellingLoc = SM.getImmediateSpellingLoc(SpellLoc);
|
||||
if (SimpleSpellingLoc.isFileID() &&
|
||||
SM.getFileEntryForID(SM.getDecomposedLoc(SimpleSpellingLoc).first))
|
||||
SpellLoc = SimpleSpellingLoc;
|
||||
else
|
||||
SpellLoc = SM.getExpansionLoc(SpellLoc);
|
||||
}
|
||||
|
||||
std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(SpellLoc);
|
||||
FileID FID = LocInfo.first;
|
||||
unsigned FileOffset = LocInfo.second;
|
||||
|
||||
if (FID.isInvalid())
|
||||
return createNullLocation(file, line, column, offset);
|
||||
|
||||
if (file)
|
||||
*file = (void *)SM.getFileEntryForID(FID);
|
||||
if (line)
|
||||
*line = SM.getLineNumber(FID, FileOffset);
|
||||
if (column)
|
||||
*column = SM.getColumnNumber(FID, FileOffset);
|
||||
if (offset)
|
||||
*offset = FileOffset;
|
||||
if (!isASTUnitSourceLocation(location)) {
|
||||
CXLoadedDiagnostic::decodeLocation(location, file, line,
|
||||
column, offset);
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME:
|
||||
createNullLocation(file, line, column, offset);
|
||||
|
||||
SourceLocation Loc = SourceLocation::getFromRawEncoding(location.int_data);
|
||||
|
||||
if (!location.ptr_data[0] || Loc.isInvalid())
|
||||
return createNullLocation(file, line, column, offset);
|
||||
|
||||
const SourceManager &SM =
|
||||
*static_cast<const SourceManager*>(location.ptr_data[0]);
|
||||
SourceLocation SpellLoc = Loc;
|
||||
if (SpellLoc.isMacroID()) {
|
||||
SourceLocation SimpleSpellingLoc = SM.getImmediateSpellingLoc(SpellLoc);
|
||||
if (SimpleSpellingLoc.isFileID() &&
|
||||
SM.getFileEntryForID(SM.getDecomposedLoc(SimpleSpellingLoc).first))
|
||||
SpellLoc = SimpleSpellingLoc;
|
||||
else
|
||||
SpellLoc = SM.getExpansionLoc(SpellLoc);
|
||||
}
|
||||
|
||||
std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(SpellLoc);
|
||||
FileID FID = LocInfo.first;
|
||||
unsigned FileOffset = LocInfo.second;
|
||||
|
||||
if (FID.isInvalid())
|
||||
return createNullLocation(file, line, column, offset);
|
||||
|
||||
if (file)
|
||||
*file = (void *)SM.getFileEntryForID(FID);
|
||||
if (line)
|
||||
*line = SM.getLineNumber(FID, FileOffset);
|
||||
if (column)
|
||||
*column = SM.getColumnNumber(FID, FileOffset);
|
||||
if (offset)
|
||||
*offset = FileOffset;
|
||||
}
|
||||
|
||||
} // end extern "C"
|
||||
|
@ -28,9 +28,6 @@ using namespace clang;
|
||||
using namespace clang::cxloc;
|
||||
using namespace clang::cxstring;
|
||||
|
||||
// Needed for vtable of CXPersisetntDiagnostic.
|
||||
CXDiagnosticImpl::~CXDiagnosticImpl() {}
|
||||
|
||||
CXDiagnosticSeverity CXStoredDiagnostic::getSeverity() const {
|
||||
switch (Diag.getLevel()) {
|
||||
case DiagnosticsEngine::Ignored: return CXDiagnostic_Ignored;
|
||||
|
@ -18,6 +18,7 @@ extern "C" {
|
||||
struct CXTranslationUnitImpl {
|
||||
void *TUData;
|
||||
void *StringPool;
|
||||
void *Diagnostics;
|
||||
};
|
||||
}
|
||||
|
||||
@ -27,7 +28,7 @@ namespace clang {
|
||||
namespace cxtu {
|
||||
|
||||
CXTranslationUnitImpl *MakeCXTranslationUnit(ASTUnit *TU);
|
||||
|
||||
|
||||
class CXTUOwner {
|
||||
CXTranslationUnitImpl *TU;
|
||||
|
||||
|
@ -32,6 +32,7 @@ clang_disposeCXCursorSet
|
||||
clang_disposeCXTUResourceUsage
|
||||
clang_disposeCodeCompleteResults
|
||||
clang_disposeDiagnostic
|
||||
clang_disposeDiagnosticSet
|
||||
clang_disposeIndex
|
||||
clang_disposeOverriddenCursors
|
||||
clang_disposeString
|
||||
@ -53,6 +54,7 @@ clang_getCXTUResourceUsage
|
||||
clang_getCXXAccessSpecifier
|
||||
clang_getCanonicalCursor
|
||||
clang_getCanonicalType
|
||||
clang_getChildDiagnostics
|
||||
clang_getClangVersion
|
||||
clang_getCompletionAnnotation
|
||||
clang_getCompletionAvailability
|
||||
@ -86,6 +88,7 @@ clang_getDiagnostic
|
||||
clang_getDiagnosticCategory
|
||||
clang_getDiagnosticCategoryName
|
||||
clang_getDiagnosticFixIt
|
||||
clang_getDiagnosticInSet
|
||||
clang_getDiagnosticLocation
|
||||
clang_getDiagnosticNumFixIts
|
||||
clang_getDiagnosticNumRanges
|
||||
@ -108,6 +111,7 @@ clang_getNullLocation
|
||||
clang_getNullRange
|
||||
clang_getNumCompletionChunks
|
||||
clang_getNumDiagnostics
|
||||
clang_getNumDiagnosticsInSet
|
||||
clang_getNumOverloadedDecls
|
||||
clang_getOverloadedDecl
|
||||
clang_getOverriddenCursors
|
||||
@ -150,6 +154,7 @@ clang_isTranslationUnit
|
||||
clang_isUnexposed
|
||||
clang_isVirtualBase
|
||||
clang_isVolatileQualifiedType
|
||||
clang_loadDiagnostics
|
||||
clang_parseTranslationUnit
|
||||
clang_remap_dispose
|
||||
clang_remap_getFilenames
|
||||
|
Loading…
Reference in New Issue
Block a user