mirror of
https://github.com/intel/llvm.git
synced 2026-01-26 21:53:12 +08:00
push some source location information down through the compiler,
into ContentCache::getBuffer. This allows it to produce diagnostics on the broken #include line instead of without a location. llvm-svn: 101939
This commit is contained in:
@@ -78,8 +78,13 @@ namespace SrcMgr {
|
||||
/// \param Diag Object through which diagnostics will be emitted it the
|
||||
/// buffer cannot be retrieved.
|
||||
///
|
||||
/// \param Loc If specified, is the location that invalid file diagnostics
|
||||
/// will be emitted at.
|
||||
///
|
||||
/// \param Invalid If non-NULL, will be set \c true if an error occurred.
|
||||
const llvm::MemoryBuffer *getBuffer(Diagnostic &Diag,
|
||||
const llvm::MemoryBuffer *getBuffer(Diagnostic &Diag,
|
||||
const SourceManager &SM,
|
||||
SourceLocation Loc = SourceLocation(),
|
||||
bool *Invalid = 0) const;
|
||||
|
||||
/// getSize - Returns the size of the content encapsulated by this
|
||||
@@ -447,11 +452,17 @@ public:
|
||||
/// getBuffer - Return the buffer for the specified FileID. If there is an
|
||||
/// error opening this buffer the first time, this manufactures a temporary
|
||||
/// buffer and returns a non-empty error string.
|
||||
const llvm::MemoryBuffer *getBuffer(FileID FID, bool *Invalid = 0) const {
|
||||
return getSLocEntry(FID).getFile().getContentCache()->getBuffer(Diag,
|
||||
Invalid);
|
||||
const llvm::MemoryBuffer *getBuffer(FileID FID, SourceLocation Loc,
|
||||
bool *Invalid = 0) const {
|
||||
return getSLocEntry(FID).getFile().getContentCache()
|
||||
->getBuffer(Diag, *this, Loc, Invalid);
|
||||
}
|
||||
|
||||
const llvm::MemoryBuffer *getBuffer(FileID FID, bool *Invalid = 0) const {
|
||||
return getSLocEntry(FID).getFile().getContentCache()
|
||||
->getBuffer(Diag, *this, SourceLocation(), Invalid);
|
||||
}
|
||||
|
||||
/// getFileEntryForID - Returns the FileEntry record for the provided FileID.
|
||||
const FileEntry *getFileEntryForID(FileID FID) const {
|
||||
return getSLocEntry(FID).getFile().getContentCache()->Entry;
|
||||
|
||||
@@ -366,17 +366,17 @@ public:
|
||||
|
||||
/// EnterMainSourceFile - Enter the specified FileID as the main source file,
|
||||
/// which implicitly adds the builtin defines etc.
|
||||
bool EnterMainSourceFile();
|
||||
void EnterMainSourceFile();
|
||||
|
||||
/// EndSourceFile - Inform the preprocessor callbacks that processing is
|
||||
/// complete.
|
||||
void EndSourceFile();
|
||||
|
||||
/// EnterSourceFile - Add a source file to the top of the include stack and
|
||||
/// start lexing tokens from it instead of the current buffer. Return true
|
||||
/// and fill in ErrorStr with the error information on failure.
|
||||
bool EnterSourceFile(FileID CurFileID, const DirectoryLookup *Dir,
|
||||
std::string &ErrorStr);
|
||||
/// start lexing tokens from it instead of the current buffer. Emit an error
|
||||
/// and don't enter the file on error.
|
||||
void EnterSourceFile(FileID CurFileID, const DirectoryLookup *Dir,
|
||||
SourceLocation Loc);
|
||||
|
||||
/// EnterMacro - Add a Macro to the top of the include stack and start lexing
|
||||
/// tokens from it instead of the current buffer. Args specifies the
|
||||
|
||||
@@ -3090,7 +3090,7 @@ FileID ASTImporter::Import(FileID FromID) {
|
||||
FromSLoc.getFile().getFileCharacteristic());
|
||||
} else {
|
||||
// FIXME: We want to re-use the existing MemoryBuffer!
|
||||
const llvm::MemoryBuffer *FromBuf = Cache->getBuffer(getDiags());
|
||||
const llvm::MemoryBuffer *FromBuf = Cache->getBuffer(getDiags(), FromSM);
|
||||
llvm::MemoryBuffer *ToBuf
|
||||
= llvm::MemoryBuffer::getMemBufferCopy(FromBuf->getBuffer(),
|
||||
FromBuf->getBufferIdentifier());
|
||||
|
||||
@@ -60,12 +60,17 @@ void ContentCache::replaceBuffer(const llvm::MemoryBuffer *B) {
|
||||
}
|
||||
|
||||
const llvm::MemoryBuffer *ContentCache::getBuffer(Diagnostic &Diag,
|
||||
const SourceManager &sm,
|
||||
SourceLocation Loc,
|
||||
bool *Invalid) const {
|
||||
if (Invalid)
|
||||
*Invalid = false;
|
||||
|
||||
// Lazily create the Buffer for ContentCaches that wrap files.
|
||||
if (!Buffer.getPointer() && Entry) {
|
||||
// FIXME:
|
||||
SourceManager &SM = const_cast<SourceManager &>(sm);
|
||||
|
||||
std::string ErrorStr;
|
||||
struct stat FileInfo;
|
||||
Buffer.setPointer(MemoryBuffer::getFile(Entry->getName(), &ErrorStr,
|
||||
@@ -94,7 +99,7 @@ const llvm::MemoryBuffer *ContentCache::getBuffer(Diagnostic &Diag,
|
||||
Diag.SetDelayedDiagnostic(diag::err_cannot_open_file,
|
||||
Entry->getName(), ErrorStr);
|
||||
else
|
||||
Diag.Report(diag::err_cannot_open_file)
|
||||
Diag.Report(FullSourceLoc(Loc, SM), diag::err_cannot_open_file)
|
||||
<< Entry->getName() << ErrorStr;
|
||||
|
||||
Buffer.setInt(true);
|
||||
@@ -114,7 +119,8 @@ const llvm::MemoryBuffer *ContentCache::getBuffer(Diagnostic &Diag,
|
||||
Diag.SetDelayedDiagnostic(diag::err_file_modified,
|
||||
Entry->getName());
|
||||
else
|
||||
Diag.Report(diag::err_file_modified) << Entry->getName();
|
||||
Diag.Report(FullSourceLoc(Loc, SM), diag::err_file_modified)
|
||||
<< Entry->getName();
|
||||
|
||||
Buffer.setInt(true);
|
||||
#endif
|
||||
@@ -150,7 +156,8 @@ const llvm::MemoryBuffer *ContentCache::getBuffer(Diagnostic &Diag,
|
||||
BOM = "BOCU-1";
|
||||
|
||||
if (BOM) {
|
||||
Diag.Report(diag::err_unsupported_bom) << BOM << Entry->getName();
|
||||
Diag.Report(FullSourceLoc(Loc, SM), diag::err_unsupported_bom)
|
||||
<< BOM << Entry->getName();
|
||||
Buffer.setInt(1);
|
||||
}
|
||||
}
|
||||
@@ -513,7 +520,7 @@ SourceManager::getMemoryBufferForFile(const FileEntry *File,
|
||||
bool *Invalid) {
|
||||
const SrcMgr::ContentCache *IR = getOrCreateContentCache(File);
|
||||
assert(IR && "getOrCreateContentCache() cannot return NULL");
|
||||
return IR->getBuffer(Diag, Invalid);
|
||||
return IR->getBuffer(Diag, *this, SourceLocation(), Invalid);
|
||||
}
|
||||
|
||||
bool SourceManager::overrideFileContents(const FileEntry *SourceFile,
|
||||
@@ -760,8 +767,8 @@ const char *SourceManager::getCharacterData(SourceLocation SL,
|
||||
// Note that calling 'getBuffer()' may lazily page in a source file.
|
||||
bool CharDataInvalid = false;
|
||||
const llvm::MemoryBuffer *Buffer
|
||||
= getSLocEntry(LocInfo.first).getFile().getContentCache()->getBuffer(Diag,
|
||||
&CharDataInvalid);
|
||||
= getSLocEntry(LocInfo.first).getFile().getContentCache()
|
||||
->getBuffer(Diag, *this, SourceLocation(), &CharDataInvalid);
|
||||
if (Invalid)
|
||||
*Invalid = CharDataInvalid;
|
||||
return Buffer->getBufferStart() + (CharDataInvalid? 0 : LocInfo.second);
|
||||
@@ -800,14 +807,16 @@ unsigned SourceManager::getInstantiationColumnNumber(SourceLocation Loc,
|
||||
return getColumnNumber(LocInfo.first, LocInfo.second, Invalid);
|
||||
}
|
||||
|
||||
static DISABLE_INLINE void ComputeLineNumbers(Diagnostic &Diag,
|
||||
ContentCache* FI,
|
||||
llvm::BumpPtrAllocator &Alloc,
|
||||
bool &Invalid);
|
||||
static void ComputeLineNumbers(Diagnostic &Diag, ContentCache* FI,
|
||||
llvm::BumpPtrAllocator &Alloc, bool &Invalid) {
|
||||
static DISABLE_INLINE void
|
||||
ComputeLineNumbers(Diagnostic &Diag, ContentCache *FI,
|
||||
llvm::BumpPtrAllocator &Alloc,
|
||||
const SourceManager &SM, bool &Invalid);
|
||||
static void ComputeLineNumbers(Diagnostic &Diag, ContentCache *FI,
|
||||
llvm::BumpPtrAllocator &Alloc,
|
||||
const SourceManager &SM, bool &Invalid) {
|
||||
// Note that calling 'getBuffer()' may lazily page in the file.
|
||||
const MemoryBuffer *Buffer = FI->getBuffer(Diag, &Invalid);
|
||||
const MemoryBuffer *Buffer = FI->getBuffer(Diag, SM, SourceLocation(),
|
||||
&Invalid);
|
||||
if (Invalid)
|
||||
return;
|
||||
|
||||
@@ -868,7 +877,7 @@ unsigned SourceManager::getLineNumber(FileID FID, unsigned FilePos,
|
||||
/// SourceLineCache for it on demand.
|
||||
if (Content->SourceLineCache == 0) {
|
||||
bool MyInvalid = false;
|
||||
ComputeLineNumbers(Diag, Content, ContentCacheAlloc, MyInvalid);
|
||||
ComputeLineNumbers(Diag, Content, ContentCacheAlloc, *this, MyInvalid);
|
||||
if (Invalid)
|
||||
*Invalid = MyInvalid;
|
||||
if (MyInvalid)
|
||||
@@ -1034,8 +1043,11 @@ PresumedLoc SourceManager::getPresumedLoc(SourceLocation Loc) const {
|
||||
// To get the source name, first consult the FileEntry (if one exists)
|
||||
// before the MemBuffer as this will avoid unnecessarily paging in the
|
||||
// MemBuffer.
|
||||
const char *Filename =
|
||||
C->Entry ? C->Entry->getName() : C->getBuffer(Diag)->getBufferIdentifier();
|
||||
const char *Filename;
|
||||
if (C->Entry)
|
||||
Filename = C->Entry->getName();
|
||||
else
|
||||
Filename = C->getBuffer(Diag, *this)->getBufferIdentifier();
|
||||
unsigned LineNo = getLineNumber(LocInfo.first, LocInfo.second);
|
||||
unsigned ColNo = getColumnNumber(LocInfo.first, LocInfo.second);
|
||||
SourceLocation IncludeLoc = FI.getIncludeLoc();
|
||||
@@ -1093,7 +1105,7 @@ SourceLocation SourceManager::getLocation(const FileEntry *SourceFile,
|
||||
/// SourceLineCache for it on demand.
|
||||
if (Content->SourceLineCache == 0) {
|
||||
bool MyInvalid = false;
|
||||
ComputeLineNumbers(Diag, Content, ContentCacheAlloc, MyInvalid);
|
||||
ComputeLineNumbers(Diag, Content, ContentCacheAlloc, *this, MyInvalid);
|
||||
if (MyInvalid)
|
||||
return SourceLocation();
|
||||
}
|
||||
@@ -1125,15 +1137,15 @@ SourceLocation SourceManager::getLocation(const FileEntry *SourceFile,
|
||||
return SourceLocation();
|
||||
|
||||
if (Line > Content->NumLines) {
|
||||
unsigned Size = Content->getBuffer(Diag)->getBufferSize();
|
||||
unsigned Size = Content->getBuffer(Diag, *this)->getBufferSize();
|
||||
if (Size > 0)
|
||||
--Size;
|
||||
return getLocForStartOfFile(FirstFID).getFileLocWithOffset(Size);
|
||||
}
|
||||
|
||||
unsigned FilePos = Content->SourceLineCache[Line - 1];
|
||||
const char *Buf = Content->getBuffer(Diag)->getBufferStart() + FilePos;
|
||||
unsigned BufLength = Content->getBuffer(Diag)->getBufferEnd() - Buf;
|
||||
const char *Buf = Content->getBuffer(Diag, *this)->getBufferStart() + FilePos;
|
||||
unsigned BufLength = Content->getBuffer(Diag, *this)->getBufferEnd() - Buf;
|
||||
unsigned i = 0;
|
||||
|
||||
// Check that the given column is valid.
|
||||
|
||||
@@ -475,7 +475,7 @@ void PTHWriter::GeneratePTH(const std::string &MainFile) {
|
||||
if (!P.isAbsolute())
|
||||
continue;
|
||||
|
||||
const llvm::MemoryBuffer *B = C.getBuffer(PP.getDiagnostics());
|
||||
const llvm::MemoryBuffer *B = C.getBuffer(PP.getDiagnostics(), SM);
|
||||
if (!B) continue;
|
||||
|
||||
FileID FID = SM.createFileID(FE, SourceLocation(), SrcMgr::C_User);
|
||||
@@ -550,8 +550,7 @@ void clang::CacheTokens(Preprocessor &PP, llvm::raw_fd_ostream* OS) {
|
||||
// Lex through the entire file. This will populate SourceManager with
|
||||
// all of the header information.
|
||||
Token Tok;
|
||||
if (PP.EnterMainSourceFile())
|
||||
return;
|
||||
PP.EnterMainSourceFile();
|
||||
do { PP.Lex(Tok); } while (Tok.isNot(tok::eof));
|
||||
|
||||
// Generate the PTH file.
|
||||
|
||||
@@ -236,8 +236,7 @@ void DumpTokensAction::ExecuteAction() {
|
||||
Preprocessor &PP = getCompilerInstance().getPreprocessor();
|
||||
// Start preprocessing the specified input file.
|
||||
Token Tok;
|
||||
if (PP.EnterMainSourceFile())
|
||||
return;
|
||||
PP.EnterMainSourceFile();
|
||||
do {
|
||||
PP.Lex(Tok);
|
||||
PP.DumpToken(Tok, true);
|
||||
@@ -265,8 +264,7 @@ void ParseOnlyAction::ExecuteAction() {
|
||||
llvm::OwningPtr<Action> PA(new MinimalAction(PP));
|
||||
|
||||
Parser P(PP, *PA);
|
||||
if (PP.EnterMainSourceFile())
|
||||
return;
|
||||
PP.EnterMainSourceFile();
|
||||
P.ParseTranslationUnit();
|
||||
}
|
||||
|
||||
@@ -275,8 +273,7 @@ void PreprocessOnlyAction::ExecuteAction() {
|
||||
|
||||
Token Tok;
|
||||
// Start parsing the specified input file.
|
||||
if (PP.EnterMainSourceFile())
|
||||
return;
|
||||
PP.EnterMainSourceFile();
|
||||
do {
|
||||
PP.Lex(Tok);
|
||||
} while (Tok.isNot(tok::eof));
|
||||
@@ -291,8 +288,7 @@ void PrintParseAction::ExecuteAction() {
|
||||
llvm::OwningPtr<Action> PA(CreatePrintParserActionsAction(PP, OS));
|
||||
|
||||
Parser P(PP, *PA);
|
||||
if (PP.EnterMainSourceFile())
|
||||
return;
|
||||
PP.EnterMainSourceFile();
|
||||
P.ParseTranslationUnit();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//===--- PCHWriter.h - Precompiled Headers Writer ---------------*- C++ -*-===//
|
||||
//===--- PCHWriter.cpp - Precompiled Headers Writer -----------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
@@ -1104,7 +1104,7 @@ void PCHWriter::WriteSourceManagerBlock(SourceManager &SourceMgr,
|
||||
// that is required by llvm::MemoryBuffer::getMemBuffer (on
|
||||
// the reader side).
|
||||
const llvm::MemoryBuffer *Buffer
|
||||
= Content->getBuffer(PP.getDiagnostics());
|
||||
= Content->getBuffer(PP.getDiagnostics(), PP.getSourceManager());
|
||||
const char *Name = Buffer->getBufferIdentifier();
|
||||
Stream.EmitRecordWithBlob(SLocBufferAbbrv, Record,
|
||||
llvm::StringRef(Name, strlen(Name) + 1));
|
||||
|
||||
@@ -456,8 +456,7 @@ static int MacroIDCompare(const void* a, const void* b) {
|
||||
static void DoPrintMacros(Preprocessor &PP, llvm::raw_ostream *OS) {
|
||||
// -dM mode just scans and ignores all tokens in the files, then dumps out
|
||||
// the macro table at the end.
|
||||
if (PP.EnterMainSourceFile())
|
||||
return;
|
||||
PP.EnterMainSourceFile();
|
||||
|
||||
Token Tok;
|
||||
do PP.Lex(Tok);
|
||||
@@ -502,8 +501,7 @@ void clang::DoPrintPreprocessedInput(Preprocessor &PP, llvm::raw_ostream *OS,
|
||||
PP.addPPCallbacks(Callbacks);
|
||||
|
||||
// After we have configured the preprocessor, enter the main file.
|
||||
if (PP.EnterMainSourceFile())
|
||||
return;
|
||||
PP.EnterMainSourceFile();
|
||||
|
||||
// Consume all of the tokens that come from the predefines buffer. Those
|
||||
// should not be emitted into the output and are guaranteed to be at the
|
||||
|
||||
@@ -101,8 +101,7 @@ void clang::RewriteMacrosInInput(Preprocessor &PP, llvm::raw_ostream *OS) {
|
||||
|
||||
|
||||
// Get the first preprocessing token.
|
||||
if (PP.EnterMainSourceFile())
|
||||
return;
|
||||
PP.EnterMainSourceFile();
|
||||
Token PPTok;
|
||||
PP.Lex(PPTok);
|
||||
|
||||
|
||||
@@ -1098,9 +1098,8 @@ void Preprocessor::HandleIncludeDirective(Token &IncludeTok,
|
||||
// Ask HeaderInfo if we should enter this #include file. If not, #including
|
||||
// this file will have no effect.
|
||||
if (!HeaderInfo.ShouldEnterIncludeFile(File, isImport)) {
|
||||
if (Callbacks) {
|
||||
if (Callbacks)
|
||||
Callbacks->FileSkipped(*File, FilenameTok, FileCharacter);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1113,10 +1112,7 @@ void Preprocessor::HandleIncludeDirective(Token &IncludeTok,
|
||||
}
|
||||
|
||||
// Finally, if all is good, enter the new file!
|
||||
std::string ErrorStr;
|
||||
if (EnterSourceFile(FID, CurDir, ErrorStr))
|
||||
Diag(FilenameTok, diag::err_pp_error_opening_file)
|
||||
<< std::string(SourceMgr.getFileEntryForID(FID)->getName()) << ErrorStr;
|
||||
EnterSourceFile(FID, CurDir, FilenameTok.getLocation());
|
||||
}
|
||||
|
||||
/// HandleIncludeNextDirective - Implements #include_next.
|
||||
|
||||
@@ -64,8 +64,8 @@ PreprocessorLexer *Preprocessor::getCurrentFileLexer() const {
|
||||
|
||||
/// EnterSourceFile - Add a source file to the top of the include stack and
|
||||
/// start lexing tokens from it instead of the current buffer.
|
||||
bool Preprocessor::EnterSourceFile(FileID FID, const DirectoryLookup *CurDir,
|
||||
std::string &ErrorStr) {
|
||||
void Preprocessor::EnterSourceFile(FileID FID, const DirectoryLookup *CurDir,
|
||||
SourceLocation Loc) {
|
||||
assert(CurTokenLexer == 0 && "Cannot #include a file inside a macro!");
|
||||
++NumEnteredSourceFiles;
|
||||
|
||||
@@ -75,19 +75,23 @@ bool Preprocessor::EnterSourceFile(FileID FID, const DirectoryLookup *CurDir,
|
||||
if (PTH) {
|
||||
if (PTHLexer *PL = PTH->CreateLexer(FID)) {
|
||||
EnterSourceFileWithPTH(PL, CurDir);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the MemoryBuffer for this FID, if it fails, we fail.
|
||||
bool Invalid = false;
|
||||
const llvm::MemoryBuffer *InputFile = getSourceManager().getBuffer(FID,
|
||||
&Invalid);
|
||||
if (Invalid)
|
||||
return true;
|
||||
const llvm::MemoryBuffer *InputFile =
|
||||
getSourceManager().getBuffer(FID, Loc, &Invalid);
|
||||
if (Invalid) {
|
||||
SourceLocation FileStart = SourceMgr.getLocForStartOfFile(FID);
|
||||
Diag(Loc, diag::err_pp_error_opening_file)
|
||||
<< std::string(SourceMgr.getBufferName(FileStart)) << "";
|
||||
return;
|
||||
}
|
||||
|
||||
EnterSourceFileWithLexer(new Lexer(FID, InputFile, *this), CurDir);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
/// EnterSourceFileWithLexer - Add a source file to the top of the include stack
|
||||
|
||||
@@ -490,7 +490,7 @@ SourceLocation Preprocessor::getLocForEndOfToken(SourceLocation Loc,
|
||||
|
||||
/// EnterMainSourceFile - Enter the specified FileID as the main source file,
|
||||
/// which implicitly adds the builtin defines etc.
|
||||
bool Preprocessor::EnterMainSourceFile() {
|
||||
void Preprocessor::EnterMainSourceFile() {
|
||||
// We do not allow the preprocessor to reenter the main file. Doing so will
|
||||
// cause FileID's to accumulate information from both runs (e.g. #line
|
||||
// information) and predefined macros aren't guaranteed to be set properly.
|
||||
@@ -498,9 +498,7 @@ bool Preprocessor::EnterMainSourceFile() {
|
||||
FileID MainFileID = SourceMgr.getMainFileID();
|
||||
|
||||
// Enter the main file source buffer.
|
||||
std::string ErrorStr;
|
||||
if (EnterSourceFile(MainFileID, 0, ErrorStr))
|
||||
return true;
|
||||
EnterSourceFile(MainFileID, 0, SourceLocation());
|
||||
|
||||
// Tell the header info that the main file was entered. If the file is later
|
||||
// #imported, it won't be re-entered.
|
||||
@@ -515,7 +513,7 @@ bool Preprocessor::EnterMainSourceFile() {
|
||||
assert(!FID.isInvalid() && "Could not create FileID for predefines?");
|
||||
|
||||
// Start parsing the predefines.
|
||||
return EnterSourceFile(FID, 0, ErrorStr);
|
||||
EnterSourceFile(FID, 0, SourceLocation());
|
||||
}
|
||||
|
||||
void Preprocessor::EndSourceFile() {
|
||||
|
||||
@@ -64,8 +64,7 @@ void clang::ParseAST(Preprocessor &PP, ASTConsumer *Consumer,
|
||||
|
||||
Sema S(PP, Ctx, *Consumer, CompleteTranslationUnit, CompletionConsumer);
|
||||
Parser P(PP, S);
|
||||
if (PP.EnterMainSourceFile())
|
||||
return;
|
||||
PP.EnterMainSourceFile();
|
||||
|
||||
// Initialize the parser.
|
||||
P.Initialize();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// RUN: not %clang -xc %s.txt -fsyntax-only 2>&1 | grep 'UTF-16 (LE) byte order mark detected'
|
||||
// RUN: not %clang %s -fsyntax-only -verify
|
||||
// rdar://7876588
|
||||
|
||||
// This test verifies that clang gives a decent error for UTF-16 source files.
|
||||
// This test verifies that clang gives a decent error for UTF-16 source files.
|
||||
|
||||
#include "utf-16.c.txt" // expected-error {{UTF-16 (LE) byte order mark detected}}
|
||||
|
||||
Reference in New Issue
Block a user