TextDiagnostic.cpp [plain text]
#include "clang/Frontend/TextDiagnostic.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Frontend/DiagnosticOptions.h"
#include "clang/Lex/Lexer.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/ADT/SmallString.h"
#include <algorithm>
using namespace clang;
static const enum raw_ostream::Colors noteColor =
raw_ostream::BLACK;
static const enum raw_ostream::Colors fixitColor =
raw_ostream::GREEN;
static const enum raw_ostream::Colors caretColor =
raw_ostream::GREEN;
static const enum raw_ostream::Colors warningColor =
raw_ostream::MAGENTA;
static const enum raw_ostream::Colors errorColor = raw_ostream::RED;
static const enum raw_ostream::Colors fatalColor = raw_ostream::RED;
static const enum raw_ostream::Colors savedColor =
raw_ostream::SAVEDCOLOR;
const unsigned WordWrapIndentation = 6;
static void selectInterestingSourceRegion(std::string &SourceLine,
std::string &CaretLine,
std::string &FixItInsertionLine,
unsigned EndOfCaretToken,
unsigned Columns) {
unsigned MaxSize = std::max(SourceLine.size(),
std::max(CaretLine.size(),
FixItInsertionLine.size()));
if (MaxSize > SourceLine.size())
SourceLine.resize(MaxSize, ' ');
if (MaxSize > CaretLine.size())
CaretLine.resize(MaxSize, ' ');
if (!FixItInsertionLine.empty() && MaxSize > FixItInsertionLine.size())
FixItInsertionLine.resize(MaxSize, ' ');
unsigned CaretStart = 0, CaretEnd = CaretLine.size();
for (; CaretStart != CaretEnd; ++CaretStart)
if (!isspace(CaretLine[CaretStart]))
break;
for (; CaretEnd != CaretStart; --CaretEnd)
if (!isspace(CaretLine[CaretEnd - 1]))
break;
if (CaretEnd < EndOfCaretToken)
CaretEnd = EndOfCaretToken;
if (!FixItInsertionLine.empty()) {
unsigned FixItStart = 0, FixItEnd = FixItInsertionLine.size();
for (; FixItStart != FixItEnd; ++FixItStart)
if (!isspace(FixItInsertionLine[FixItStart]))
break;
for (; FixItEnd != FixItStart; --FixItEnd)
if (!isspace(FixItInsertionLine[FixItEnd - 1]))
break;
if (FixItStart < CaretStart)
CaretStart = FixItStart;
if (FixItEnd > CaretEnd)
CaretEnd = FixItEnd;
}
if (Columns > 3 && CaretEnd < Columns - 3)
CaretStart = 0;
unsigned TargetColumns = Columns;
if (TargetColumns > 8)
TargetColumns -= 8; unsigned SourceLength = SourceLine.size();
while ((CaretEnd - CaretStart) < TargetColumns) {
bool ExpandedRegion = false;
if (CaretStart == 1)
CaretStart = 0;
else if (CaretStart > 1) {
unsigned NewStart = CaretStart - 1;
while (NewStart && isspace(SourceLine[NewStart]))
--NewStart;
while (NewStart && !isspace(SourceLine[NewStart]))
--NewStart;
if (NewStart)
++NewStart;
if (CaretEnd - NewStart <= TargetColumns) {
CaretStart = NewStart;
ExpandedRegion = true;
}
}
if (CaretEnd != SourceLength) {
assert(CaretEnd < SourceLength && "Unexpected caret position!");
unsigned NewEnd = CaretEnd;
while (NewEnd != SourceLength && isspace(SourceLine[NewEnd - 1]))
++NewEnd;
while (NewEnd != SourceLength && !isspace(SourceLine[NewEnd - 1]))
++NewEnd;
if (NewEnd - CaretStart <= TargetColumns) {
CaretEnd = NewEnd;
ExpandedRegion = true;
}
}
if (!ExpandedRegion)
break;
}
if (CaretEnd < SourceLine.size())
SourceLine.replace(CaretEnd, std::string::npos, "...");
if (CaretEnd < CaretLine.size())
CaretLine.erase(CaretEnd, std::string::npos);
if (FixItInsertionLine.size() > CaretEnd)
FixItInsertionLine.erase(CaretEnd, std::string::npos);
if (CaretStart > 2) {
SourceLine.replace(0, CaretStart, " ...");
CaretLine.replace(0, CaretStart, " ");
if (FixItInsertionLine.size() >= CaretStart)
FixItInsertionLine.replace(0, CaretStart, " ");
}
}
static unsigned skipWhitespace(unsigned Idx, StringRef Str, unsigned Length) {
while (Idx < Length && isspace(Str[Idx]))
++Idx;
return Idx;
}
static inline char findMatchingPunctuation(char c) {
switch (c) {
case '\'': return '\'';
case '`': return '\'';
case '"': return '"';
case '(': return ')';
case '[': return ']';
case '{': return '}';
default: break;
}
return 0;
}
static unsigned findEndOfWord(unsigned Start, StringRef Str,
unsigned Length, unsigned Column,
unsigned Columns) {
assert(Start < Str.size() && "Invalid start position!");
unsigned End = Start + 1;
if (End == Str.size())
return End;
char EndPunct = findMatchingPunctuation(Str[Start]);
if (!EndPunct) {
while (End < Length && !isspace(Str[End]))
++End;
return End;
}
SmallString<16> PunctuationEndStack;
PunctuationEndStack.push_back(EndPunct);
while (End < Length && !PunctuationEndStack.empty()) {
if (Str[End] == PunctuationEndStack.back())
PunctuationEndStack.pop_back();
else if (char SubEndPunct = findMatchingPunctuation(Str[End]))
PunctuationEndStack.push_back(SubEndPunct);
++End;
}
while (End < Length && !isspace(Str[End]))
++End;
unsigned PunctWordLength = End - Start;
if ( Column + PunctWordLength <= Columns ||
PunctWordLength < Columns/3)
return End;
return findEndOfWord(Start + 1, Str, Length, Column + 1, Columns);
}
static bool printWordWrapped(raw_ostream &OS, StringRef Str,
unsigned Columns,
unsigned Column = 0,
unsigned Indentation = WordWrapIndentation) {
const unsigned Length = std::min(Str.find('\n'), Str.size());
SmallString<16> IndentStr;
IndentStr.assign(Indentation, ' ');
bool Wrapped = false;
for (unsigned WordStart = 0, WordEnd; WordStart < Length;
WordStart = WordEnd) {
WordStart = skipWhitespace(WordStart, Str, Length);
if (WordStart == Length)
break;
WordEnd = findEndOfWord(WordStart, Str, Length, Column, Columns);
unsigned WordLength = WordEnd - WordStart;
if (Column + WordLength < Columns) {
if (WordStart) {
OS << ' ';
Column += 1;
}
OS << Str.substr(WordStart, WordLength);
Column += WordLength;
continue;
}
OS << '\n';
OS.write(&IndentStr[0], Indentation);
OS << Str.substr(WordStart, WordLength);
Column = Indentation + WordLength;
Wrapped = true;
}
OS << Str.substr(Length);
return Wrapped;
}
TextDiagnostic::TextDiagnostic(raw_ostream &OS,
const SourceManager &SM,
const LangOptions &LangOpts,
const DiagnosticOptions &DiagOpts)
: DiagnosticRenderer(SM, LangOpts, DiagOpts), OS(OS) {}
TextDiagnostic::~TextDiagnostic() {}
void
TextDiagnostic::emitDiagnosticMessage(SourceLocation Loc,
PresumedLoc PLoc,
DiagnosticsEngine::Level Level,
StringRef Message,
ArrayRef<clang::CharSourceRange> Ranges,
DiagOrStoredDiag D) {
uint64_t StartOfLocationInfo = OS.tell();
emitDiagnosticLoc(Loc, PLoc, Level, Ranges);
if (DiagOpts.ShowColors)
OS.resetColor();
printDiagnosticLevel(OS, Level, DiagOpts.ShowColors);
printDiagnosticMessage(OS, Level, Message,
OS.tell() - StartOfLocationInfo,
DiagOpts.MessageLength, DiagOpts.ShowColors);
}
void
TextDiagnostic::printDiagnosticLevel(raw_ostream &OS,
DiagnosticsEngine::Level Level,
bool ShowColors) {
if (ShowColors) {
switch (Level) {
case DiagnosticsEngine::Ignored:
llvm_unreachable("Invalid diagnostic type");
case DiagnosticsEngine::Note: OS.changeColor(noteColor, true); break;
case DiagnosticsEngine::Warning: OS.changeColor(warningColor, true); break;
case DiagnosticsEngine::Error: OS.changeColor(errorColor, true); break;
case DiagnosticsEngine::Fatal: OS.changeColor(fatalColor, true); break;
}
}
switch (Level) {
case DiagnosticsEngine::Ignored:
llvm_unreachable("Invalid diagnostic type");
case DiagnosticsEngine::Note: OS << "note: "; break;
case DiagnosticsEngine::Warning: OS << "warning: "; break;
case DiagnosticsEngine::Error: OS << "error: "; break;
case DiagnosticsEngine::Fatal: OS << "fatal error: "; break;
}
if (ShowColors)
OS.resetColor();
}
void
TextDiagnostic::printDiagnosticMessage(raw_ostream &OS,
DiagnosticsEngine::Level Level,
StringRef Message,
unsigned CurrentColumn, unsigned Columns,
bool ShowColors) {
if (ShowColors) {
switch (Level) {
case DiagnosticsEngine::Warning: OS.changeColor(savedColor, true); break;
case DiagnosticsEngine::Error: OS.changeColor(savedColor, true); break;
case DiagnosticsEngine::Fatal: OS.changeColor(savedColor, true); break;
default: break; }
}
if (Columns)
printWordWrapped(OS, Message, Columns, CurrentColumn);
else
OS << Message;
if (ShowColors)
OS.resetColor();
OS << '\n';
}
void TextDiagnostic::emitDiagnosticLoc(SourceLocation Loc, PresumedLoc PLoc,
DiagnosticsEngine::Level Level,
ArrayRef<CharSourceRange> Ranges) {
if (PLoc.isInvalid()) {
FileID FID = SM.getFileID(Loc);
if (!FID.isInvalid()) {
const FileEntry* FE = SM.getFileEntryForID(FID);
if (FE && FE->getName()) {
OS << FE->getName();
if (FE->getDevice() == 0 && FE->getInode() == 0
&& FE->getFileMode() == 0) {
OS << " (in PCH)";
}
OS << ": ";
}
}
return;
}
unsigned LineNo = PLoc.getLine();
if (!DiagOpts.ShowLocation)
return;
if (DiagOpts.ShowColors)
OS.changeColor(savedColor, true);
OS << PLoc.getFilename();
switch (DiagOpts.Format) {
case DiagnosticOptions::Clang: OS << ':' << LineNo; break;
case DiagnosticOptions::Msvc: OS << '(' << LineNo; break;
case DiagnosticOptions::Vi: OS << " +" << LineNo; break;
}
if (DiagOpts.ShowColumn)
if (unsigned ColNo = PLoc.getColumn()) {
if (DiagOpts.Format == DiagnosticOptions::Msvc) {
OS << ',';
ColNo--;
} else
OS << ':';
OS << ColNo;
}
switch (DiagOpts.Format) {
case DiagnosticOptions::Clang:
case DiagnosticOptions::Vi: OS << ':'; break;
case DiagnosticOptions::Msvc: OS << ") : "; break;
}
if (DiagOpts.ShowSourceRanges && !Ranges.empty()) {
FileID CaretFileID =
SM.getFileID(SM.getExpansionLoc(Loc));
bool PrintedRange = false;
for (ArrayRef<CharSourceRange>::const_iterator RI = Ranges.begin(),
RE = Ranges.end();
RI != RE; ++RI) {
if (!RI->isValid()) continue;
SourceLocation B = SM.getExpansionLoc(RI->getBegin());
SourceLocation E = SM.getExpansionLoc(RI->getEnd());
if (B == E && RI->getEnd().isMacroID())
E = SM.getExpansionRange(RI->getEnd()).second;
std::pair<FileID, unsigned> BInfo = SM.getDecomposedLoc(B);
std::pair<FileID, unsigned> EInfo = SM.getDecomposedLoc(E);
if (BInfo.first != CaretFileID || EInfo.first != CaretFileID)
continue;
unsigned TokSize = 0;
if (RI->isTokenRange())
TokSize = Lexer::MeasureTokenLength(E, SM, LangOpts);
OS << '{' << SM.getLineNumber(BInfo.first, BInfo.second) << ':'
<< SM.getColumnNumber(BInfo.first, BInfo.second) << '-'
<< SM.getLineNumber(EInfo.first, EInfo.second) << ':'
<< (SM.getColumnNumber(EInfo.first, EInfo.second)+TokSize)
<< '}';
PrintedRange = true;
}
if (PrintedRange)
OS << ':';
}
OS << ' ';
}
void TextDiagnostic::emitBasicNote(StringRef Message) {
OS << "note: " << Message << "\n";
}
void TextDiagnostic::emitIncludeLocation(SourceLocation Loc,
PresumedLoc PLoc) {
if (DiagOpts.ShowLocation)
OS << "In file included from " << PLoc.getFilename() << ':'
<< PLoc.getLine() << ":\n";
else
OS << "In included file:\n";
}
void TextDiagnostic::emitSnippetAndCaret(
SourceLocation Loc, DiagnosticsEngine::Level Level,
SmallVectorImpl<CharSourceRange>& Ranges,
ArrayRef<FixItHint> Hints) {
assert(!Loc.isInvalid() && "must have a valid source location here");
assert(Loc.isFileID() && "must have a file location here");
if (!DiagOpts.ShowCarets)
return;
if (Loc == LastLoc && Ranges.empty() && Hints.empty() &&
(LastLevel != DiagnosticsEngine::Note || Level == LastLevel))
return;
std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc);
FileID FID = LocInfo.first;
unsigned FileOffset = LocInfo.second;
bool Invalid = false;
const char *BufStart = SM.getBufferData(FID, &Invalid).data();
if (Invalid)
return;
unsigned LineNo = SM.getLineNumber(FID, FileOffset);
unsigned ColNo = SM.getColumnNumber(FID, FileOffset);
unsigned CaretEndColNo
= ColNo + Lexer::MeasureTokenLength(Loc, SM, LangOpts);
const char *TokPtr = BufStart+FileOffset;
const char *LineStart = TokPtr-ColNo+1;
const char *LineEnd = TokPtr;
while (*LineEnd != '\n' && *LineEnd != '\r' && *LineEnd != '\0')
++LineEnd;
CaretEndColNo = std::min(CaretEndColNo, unsigned(LineEnd - LineStart));
std::string SourceLine(LineStart, LineEnd);
std::string CaretLine(LineEnd-LineStart, ' ');
for (SmallVectorImpl<CharSourceRange>::iterator I = Ranges.begin(),
E = Ranges.end();
I != E; ++I)
highlightRange(*I, LineNo, FID, SourceLine, CaretLine);
if (ColNo-1 < CaretLine.size())
CaretLine[ColNo-1] = '^';
else
CaretLine.push_back('^');
expandTabs(SourceLine, CaretLine);
if (DiagOpts.ShowSourceRanges) {
SourceLine = ' ' + SourceLine;
CaretLine = ' ' + CaretLine;
}
std::string FixItInsertionLine = buildFixItInsertionLine(LineNo,
LineStart, LineEnd,
Hints);
unsigned Columns = DiagOpts.MessageLength;
if (Columns && SourceLine.size() > Columns)
selectInterestingSourceRegion(SourceLine, CaretLine, FixItInsertionLine,
CaretEndColNo, Columns);
while (CaretLine[CaretLine.size()-1] == ' ')
CaretLine.erase(CaretLine.end()-1);
OS << SourceLine << '\n';
if (DiagOpts.ShowColors)
OS.changeColor(caretColor, true);
OS << CaretLine << '\n';
if (DiagOpts.ShowColors)
OS.resetColor();
if (!FixItInsertionLine.empty()) {
if (DiagOpts.ShowColors)
OS.changeColor(fixitColor, false);
if (DiagOpts.ShowSourceRanges)
OS << ' ';
OS << FixItInsertionLine << '\n';
if (DiagOpts.ShowColors)
OS.resetColor();
}
emitParseableFixits(Hints);
}
void TextDiagnostic::highlightRange(const CharSourceRange &R,
unsigned LineNo, FileID FID,
const std::string &SourceLine,
std::string &CaretLine) {
assert(CaretLine.size() == SourceLine.size() &&
"Expect a correspondence between source and caret line!");
if (!R.isValid()) return;
SourceLocation Begin = SM.getExpansionLoc(R.getBegin());
SourceLocation End = SM.getExpansionLoc(R.getEnd());
if (Begin == End && R.getEnd().isMacroID())
End = SM.getExpansionRange(R.getEnd()).second;
unsigned StartLineNo = SM.getExpansionLineNumber(Begin);
if (StartLineNo > LineNo || SM.getFileID(Begin) != FID)
return;
unsigned EndLineNo = SM.getExpansionLineNumber(End);
if (EndLineNo < LineNo || SM.getFileID(End) != FID)
return;
unsigned StartColNo = 0;
if (StartLineNo == LineNo) {
StartColNo = SM.getExpansionColumnNumber(Begin);
if (StartColNo) --StartColNo; }
unsigned EndColNo = CaretLine.size();
if (EndLineNo == LineNo) {
EndColNo = SM.getExpansionColumnNumber(End);
if (EndColNo) {
--EndColNo;
if (R.isTokenRange())
EndColNo += Lexer::MeasureTokenLength(End, SM, LangOpts);
} else {
EndColNo = CaretLine.size();
}
}
assert(StartColNo <= EndColNo && "Invalid range!");
if (R.isTokenRange()) {
while (StartColNo < SourceLine.size() &&
(SourceLine[StartColNo] == ' ' || SourceLine[StartColNo] == '\t'))
++StartColNo;
if (EndColNo > SourceLine.size())
EndColNo = SourceLine.size();
while (EndColNo-1 &&
(SourceLine[EndColNo-1] == ' ' || SourceLine[EndColNo-1] == '\t'))
--EndColNo;
assert(StartColNo <= EndColNo && "Trying to highlight whitespace??");
}
for (unsigned i = StartColNo; i < EndColNo; ++i)
CaretLine[i] = '~';
}
std::string TextDiagnostic::buildFixItInsertionLine(unsigned LineNo,
const char *LineStart,
const char *LineEnd,
ArrayRef<FixItHint> Hints) {
std::string FixItInsertionLine;
if (Hints.empty() || !DiagOpts.ShowFixits)
return FixItInsertionLine;
for (ArrayRef<FixItHint>::iterator I = Hints.begin(), E = Hints.end();
I != E; ++I) {
if (!I->CodeToInsert.empty()) {
std::pair<FileID, unsigned> HintLocInfo
= SM.getDecomposedExpansionLoc(I->RemoveRange.getBegin());
if (LineNo == SM.getLineNumber(HintLocInfo.first, HintLocInfo.second)) {
unsigned HintColNo
= SM.getColumnNumber(HintLocInfo.first, HintLocInfo.second);
unsigned LastColumnModified
= HintColNo - 1 + I->CodeToInsert.size();
if (LastColumnModified > FixItInsertionLine.size())
FixItInsertionLine.resize(LastColumnModified, ' ');
std::copy(I->CodeToInsert.begin(), I->CodeToInsert.end(),
FixItInsertionLine.begin() + HintColNo - 1);
} else {
FixItInsertionLine.clear();
break;
}
}
}
if (FixItInsertionLine.empty())
return FixItInsertionLine;
unsigned FixItPos = 0;
unsigned LinePos = 0;
unsigned TabExpandedCol = 0;
unsigned LineLength = LineEnd - LineStart;
while (FixItPos < FixItInsertionLine.size() && LinePos < LineLength) {
while (FixItPos < FixItInsertionLine.size() &&
FixItInsertionLine[FixItPos] == ' ')
++FixItPos;
unsigned CharDistance = FixItPos - TabExpandedCol;
for (unsigned I = 0; I < CharDistance; ++I, ++LinePos)
if (LinePos >= LineLength || LineStart[LinePos] != '\t')
++TabExpandedCol;
else
TabExpandedCol =
(TabExpandedCol/DiagOpts.TabStop + 1) * DiagOpts.TabStop;
FixItInsertionLine.insert(FixItPos, TabExpandedCol-FixItPos, ' ');
FixItPos = TabExpandedCol;
while (FixItPos < FixItInsertionLine.size() &&
FixItInsertionLine[FixItPos] != ' ')
++FixItPos;
}
return FixItInsertionLine;
}
void TextDiagnostic::expandTabs(std::string &SourceLine,
std::string &CaretLine) {
for (unsigned i = 0; i != SourceLine.size(); ++i) {
if (SourceLine[i] != '\t') continue;
SourceLine[i] = ' ';
unsigned TabStop = DiagOpts.TabStop;
assert(0 < TabStop && TabStop <= DiagnosticOptions::MaxTabStop &&
"Invalid -ftabstop value");
unsigned NumSpaces = ((i+TabStop)/TabStop * TabStop) - (i+1);
assert(NumSpaces < TabStop && "Invalid computation of space amt");
SourceLine.insert(i+1, NumSpaces, ' ');
CaretLine.insert(i+1, NumSpaces, CaretLine[i] == '~' ? '~' : ' ');
}
}
void TextDiagnostic::emitParseableFixits(ArrayRef<FixItHint> Hints) {
if (!DiagOpts.ShowParseableFixits)
return;
for (ArrayRef<FixItHint>::iterator I = Hints.begin(), E = Hints.end();
I != E; ++I) {
if (I->RemoveRange.isInvalid() ||
I->RemoveRange.getBegin().isMacroID() ||
I->RemoveRange.getEnd().isMacroID())
return;
}
for (ArrayRef<FixItHint>::iterator I = Hints.begin(), E = Hints.end();
I != E; ++I) {
SourceLocation BLoc = I->RemoveRange.getBegin();
SourceLocation ELoc = I->RemoveRange.getEnd();
std::pair<FileID, unsigned> BInfo = SM.getDecomposedLoc(BLoc);
std::pair<FileID, unsigned> EInfo = SM.getDecomposedLoc(ELoc);
if (I->RemoveRange.isTokenRange())
EInfo.second += Lexer::MeasureTokenLength(ELoc, SM, LangOpts);
PresumedLoc PLoc = SM.getPresumedLoc(BLoc);
if (PLoc.isInvalid())
break;
OS << "fix-it:\"";
OS.write_escaped(PLoc.getFilename());
OS << "\":{" << SM.getLineNumber(BInfo.first, BInfo.second)
<< ':' << SM.getColumnNumber(BInfo.first, BInfo.second)
<< '-' << SM.getLineNumber(EInfo.first, EInfo.second)
<< ':' << SM.getColumnNumber(EInfo.first, EInfo.second)
<< "}:\"";
OS.write_escaped(I->CodeToInsert);
OS << "\"\n";
}
}