1f51a89d39
These were ignored by git accidentally. We want ALL OF THEM since they all came in the llvm/clang source distribution.
387 lines
15 KiB
C++
387 lines
15 KiB
C++
//===--- DiagnosticRenderer.cpp - Diagnostic Pretty-Printing --------------===//
|
|
//
|
|
// The LLVM Compiler Infrastructure
|
|
//
|
|
// This file is distributed under the University of Illinois Open Source
|
|
// License. See LICENSE.TXT for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "clang/Frontend/DiagnosticRenderer.h"
|
|
#include "clang/Basic/FileManager.h"
|
|
#include "clang/Basic/SourceManager.h"
|
|
#include "clang/Frontend/DiagnosticOptions.h"
|
|
#include "clang/Lex/Lexer.h"
|
|
#include "clang/Edit/EditedSource.h"
|
|
#include "clang/Edit/Commit.h"
|
|
#include "clang/Edit/EditsReceiver.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;
|
|
|
|
/// Look through spelling locations for a macro argument expansion, and
|
|
/// if found skip to it so that we can trace the argument rather than the macros
|
|
/// in which that argument is used. If no macro argument expansion is found,
|
|
/// don't skip anything and return the starting location.
|
|
static SourceLocation skipToMacroArgExpansion(const SourceManager &SM,
|
|
SourceLocation StartLoc) {
|
|
for (SourceLocation L = StartLoc; L.isMacroID();
|
|
L = SM.getImmediateSpellingLoc(L)) {
|
|
if (SM.isMacroArgExpansion(L))
|
|
return L;
|
|
}
|
|
|
|
// Otherwise just return initial location, there's nothing to skip.
|
|
return StartLoc;
|
|
}
|
|
|
|
/// Gets the location of the immediate macro caller, one level up the stack
|
|
/// toward the initial macro typed into the source.
|
|
static SourceLocation getImmediateMacroCallerLoc(const SourceManager &SM,
|
|
SourceLocation Loc) {
|
|
if (!Loc.isMacroID()) return Loc;
|
|
|
|
// When we have the location of (part of) an expanded parameter, its spelling
|
|
// location points to the argument as typed into the macro call, and
|
|
// therefore is used to locate the macro caller.
|
|
if (SM.isMacroArgExpansion(Loc))
|
|
return SM.getImmediateSpellingLoc(Loc);
|
|
|
|
// Otherwise, the caller of the macro is located where this macro is
|
|
// expanded (while the spelling is part of the macro definition).
|
|
return SM.getImmediateExpansionRange(Loc).first;
|
|
}
|
|
|
|
/// Gets the location of the immediate macro callee, one level down the stack
|
|
/// toward the leaf macro.
|
|
static SourceLocation getImmediateMacroCalleeLoc(const SourceManager &SM,
|
|
SourceLocation Loc) {
|
|
if (!Loc.isMacroID()) return Loc;
|
|
|
|
// When we have the location of (part of) an expanded parameter, its
|
|
// expansion location points to the unexpanded paramater reference within
|
|
// the macro definition (or callee).
|
|
if (SM.isMacroArgExpansion(Loc))
|
|
return SM.getImmediateExpansionRange(Loc).first;
|
|
|
|
// Otherwise, the callee of the macro is located where this location was
|
|
// spelled inside the macro definition.
|
|
return SM.getImmediateSpellingLoc(Loc);
|
|
}
|
|
|
|
/// \brief Retrieve the name of the immediate macro expansion.
|
|
///
|
|
/// This routine starts from a source location, and finds the name of the macro
|
|
/// responsible for its immediate expansion. It looks through any intervening
|
|
/// macro argument expansions to compute this. It returns a StringRef which
|
|
/// refers to the SourceManager-owned buffer of the source where that macro
|
|
/// name is spelled. Thus, the result shouldn't out-live that SourceManager.
|
|
///
|
|
/// This differs from Lexer::getImmediateMacroName in that any macro argument
|
|
/// location will result in the topmost function macro that accepted it.
|
|
/// e.g.
|
|
/// \code
|
|
/// MAC1( MAC2(foo) )
|
|
/// \endcode
|
|
/// for location of 'foo' token, this function will return "MAC1" while
|
|
/// Lexer::getImmediateMacroName will return "MAC2".
|
|
static StringRef getImmediateMacroName(SourceLocation Loc,
|
|
const SourceManager &SM,
|
|
const LangOptions &LangOpts) {
|
|
assert(Loc.isMacroID() && "Only reasonble to call this on macros");
|
|
// Walk past macro argument expanions.
|
|
while (SM.isMacroArgExpansion(Loc))
|
|
Loc = SM.getImmediateExpansionRange(Loc).first;
|
|
|
|
// Find the spelling location of the start of the non-argument expansion
|
|
// range. This is where the macro name was spelled in order to begin
|
|
// expanding this macro.
|
|
Loc = SM.getSpellingLoc(SM.getImmediateExpansionRange(Loc).first);
|
|
|
|
// Dig out the buffer where the macro name was spelled and the extents of the
|
|
// name so that we can render it into the expansion note.
|
|
std::pair<FileID, unsigned> ExpansionInfo = SM.getDecomposedLoc(Loc);
|
|
unsigned MacroTokenLength = Lexer::MeasureTokenLength(Loc, SM, LangOpts);
|
|
StringRef ExpansionBuffer = SM.getBufferData(ExpansionInfo.first);
|
|
return ExpansionBuffer.substr(ExpansionInfo.second, MacroTokenLength);
|
|
}
|
|
|
|
/// Get the presumed location of a diagnostic message. This computes the
|
|
/// presumed location for the top of any macro backtrace when present.
|
|
static PresumedLoc getDiagnosticPresumedLoc(const SourceManager &SM,
|
|
SourceLocation Loc) {
|
|
// This is a condensed form of the algorithm used by emitCaretDiagnostic to
|
|
// walk to the top of the macro call stack.
|
|
while (Loc.isMacroID()) {
|
|
Loc = skipToMacroArgExpansion(SM, Loc);
|
|
Loc = getImmediateMacroCallerLoc(SM, Loc);
|
|
}
|
|
|
|
return SM.getPresumedLoc(Loc);
|
|
}
|
|
|
|
DiagnosticRenderer::DiagnosticRenderer(const SourceManager &SM,
|
|
const LangOptions &LangOpts,
|
|
const DiagnosticOptions &DiagOpts)
|
|
: SM(SM), LangOpts(LangOpts), DiagOpts(DiagOpts), LastLevel() {}
|
|
|
|
DiagnosticRenderer::~DiagnosticRenderer() {}
|
|
|
|
namespace {
|
|
|
|
class FixitReceiver : public edit::EditsReceiver {
|
|
SmallVectorImpl<FixItHint> &MergedFixits;
|
|
|
|
public:
|
|
FixitReceiver(SmallVectorImpl<FixItHint> &MergedFixits)
|
|
: MergedFixits(MergedFixits) { }
|
|
virtual void insert(SourceLocation loc, StringRef text) {
|
|
MergedFixits.push_back(FixItHint::CreateInsertion(loc, text));
|
|
}
|
|
virtual void replace(CharSourceRange range, StringRef text) {
|
|
MergedFixits.push_back(FixItHint::CreateReplacement(range, text));
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
static void mergeFixits(ArrayRef<FixItHint> FixItHints,
|
|
const SourceManager &SM, const LangOptions &LangOpts,
|
|
SmallVectorImpl<FixItHint> &MergedFixits) {
|
|
edit::Commit commit(SM, LangOpts);
|
|
for (ArrayRef<FixItHint>::const_iterator
|
|
I = FixItHints.begin(), E = FixItHints.end(); I != E; ++I) {
|
|
const FixItHint &Hint = *I;
|
|
if (Hint.CodeToInsert.empty()) {
|
|
if (Hint.InsertFromRange.isValid())
|
|
commit.insertFromRange(Hint.RemoveRange.getBegin(),
|
|
Hint.InsertFromRange, /*afterToken=*/false,
|
|
Hint.BeforePreviousInsertions);
|
|
else
|
|
commit.remove(Hint.RemoveRange);
|
|
} else {
|
|
if (Hint.RemoveRange.isTokenRange() ||
|
|
Hint.RemoveRange.getBegin() != Hint.RemoveRange.getEnd())
|
|
commit.replace(Hint.RemoveRange, Hint.CodeToInsert);
|
|
else
|
|
commit.insert(Hint.RemoveRange.getBegin(), Hint.CodeToInsert,
|
|
/*afterToken=*/false, Hint.BeforePreviousInsertions);
|
|
}
|
|
}
|
|
|
|
edit::EditedSource Editor(SM, LangOpts);
|
|
if (Editor.commit(commit)) {
|
|
FixitReceiver Rec(MergedFixits);
|
|
Editor.applyRewrites(Rec);
|
|
}
|
|
}
|
|
|
|
void DiagnosticRenderer::emitDiagnostic(SourceLocation Loc,
|
|
DiagnosticsEngine::Level Level,
|
|
StringRef Message,
|
|
ArrayRef<CharSourceRange> Ranges,
|
|
ArrayRef<FixItHint> FixItHints,
|
|
DiagOrStoredDiag D) {
|
|
|
|
beginDiagnostic(D, Level);
|
|
|
|
PresumedLoc PLoc = getDiagnosticPresumedLoc(SM, Loc);
|
|
|
|
// First, if this diagnostic is not in the main file, print out the
|
|
// "included from" lines.
|
|
emitIncludeStack(PLoc.getIncludeLoc(), Level);
|
|
|
|
// Next, emit the actual diagnostic message.
|
|
emitDiagnosticMessage(Loc, PLoc, Level, Message, Ranges, D);
|
|
|
|
// Only recurse if we have a valid location.
|
|
if (Loc.isValid()) {
|
|
// Get the ranges into a local array we can hack on.
|
|
SmallVector<CharSourceRange, 20> MutableRanges(Ranges.begin(),
|
|
Ranges.end());
|
|
|
|
llvm::SmallVector<FixItHint, 8> MergedFixits;
|
|
if (!FixItHints.empty()) {
|
|
mergeFixits(FixItHints, SM, LangOpts, MergedFixits);
|
|
FixItHints = MergedFixits;
|
|
}
|
|
|
|
for (ArrayRef<FixItHint>::const_iterator I = FixItHints.begin(),
|
|
E = FixItHints.end();
|
|
I != E; ++I)
|
|
if (I->RemoveRange.isValid())
|
|
MutableRanges.push_back(I->RemoveRange);
|
|
|
|
unsigned MacroDepth = 0;
|
|
emitMacroExpansionsAndCarets(Loc, Level, MutableRanges, FixItHints,
|
|
MacroDepth);
|
|
}
|
|
|
|
LastLoc = Loc;
|
|
LastLevel = Level;
|
|
|
|
endDiagnostic(D, Level);
|
|
}
|
|
|
|
|
|
void DiagnosticRenderer::emitStoredDiagnostic(StoredDiagnostic &Diag) {
|
|
emitDiagnostic(Diag.getLocation(), Diag.getLevel(), Diag.getMessage(),
|
|
Diag.getRanges(), Diag.getFixIts(),
|
|
&Diag);
|
|
}
|
|
|
|
/// \brief Prints an include stack when appropriate for a particular
|
|
/// diagnostic level and location.
|
|
///
|
|
/// This routine handles all the logic of suppressing particular include
|
|
/// stacks (such as those for notes) and duplicate include stacks when
|
|
/// repeated warnings occur within the same file. It also handles the logic
|
|
/// of customizing the formatting and display of the include stack.
|
|
///
|
|
/// \param Level The diagnostic level of the message this stack pertains to.
|
|
/// \param Loc The include location of the current file (not the diagnostic
|
|
/// location).
|
|
void DiagnosticRenderer::emitIncludeStack(SourceLocation Loc,
|
|
DiagnosticsEngine::Level Level) {
|
|
// Skip redundant include stacks altogether.
|
|
if (LastIncludeLoc == Loc)
|
|
return;
|
|
LastIncludeLoc = Loc;
|
|
|
|
if (!DiagOpts.ShowNoteIncludeStack && Level == DiagnosticsEngine::Note)
|
|
return;
|
|
|
|
emitIncludeStackRecursively(Loc);
|
|
}
|
|
|
|
/// \brief Helper to recursivly walk up the include stack and print each layer
|
|
/// on the way back down.
|
|
void DiagnosticRenderer::emitIncludeStackRecursively(SourceLocation Loc) {
|
|
if (Loc.isInvalid())
|
|
return;
|
|
|
|
PresumedLoc PLoc = SM.getPresumedLoc(Loc);
|
|
if (PLoc.isInvalid())
|
|
return;
|
|
|
|
// Emit the other include frames first.
|
|
emitIncludeStackRecursively(PLoc.getIncludeLoc());
|
|
|
|
// Emit the inclusion text/note.
|
|
emitIncludeLocation(Loc, PLoc);
|
|
}
|
|
|
|
/// \brief Recursively emit notes for each macro expansion and caret
|
|
/// diagnostics where appropriate.
|
|
///
|
|
/// Walks up the macro expansion stack printing expansion notes, the code
|
|
/// snippet, caret, underlines and FixItHint display as appropriate at each
|
|
/// level.
|
|
///
|
|
/// \param Loc The location for this caret.
|
|
/// \param Level The diagnostic level currently being emitted.
|
|
/// \param Ranges The underlined ranges for this code snippet.
|
|
/// \param Hints The FixIt hints active for this diagnostic.
|
|
/// \param MacroSkipEnd The depth to stop skipping macro expansions.
|
|
/// \param OnMacroInst The current depth of the macro expansion stack.
|
|
void DiagnosticRenderer::emitMacroExpansionsAndCarets(
|
|
SourceLocation Loc,
|
|
DiagnosticsEngine::Level Level,
|
|
SmallVectorImpl<CharSourceRange>& Ranges,
|
|
ArrayRef<FixItHint> Hints,
|
|
unsigned &MacroDepth,
|
|
unsigned OnMacroInst)
|
|
{
|
|
assert(!Loc.isInvalid() && "must have a valid source location here");
|
|
|
|
// If this is a file source location, directly emit the source snippet and
|
|
// caret line. Also record the macro depth reached.
|
|
if (Loc.isFileID()) {
|
|
assert(MacroDepth == 0 && "We shouldn't hit a leaf node twice!");
|
|
MacroDepth = OnMacroInst;
|
|
emitCodeContext(Loc, Level, Ranges, Hints);
|
|
return;
|
|
}
|
|
// Otherwise recurse through each macro expansion layer.
|
|
|
|
// When processing macros, skip over the expansions leading up to
|
|
// a macro argument, and trace the argument's expansion stack instead.
|
|
Loc = skipToMacroArgExpansion(SM, Loc);
|
|
|
|
SourceLocation OneLevelUp = getImmediateMacroCallerLoc(SM, Loc);
|
|
|
|
// FIXME: Map ranges?
|
|
emitMacroExpansionsAndCarets(OneLevelUp, Level, Ranges, Hints, MacroDepth,
|
|
OnMacroInst + 1);
|
|
|
|
// Save the original location so we can find the spelling of the macro call.
|
|
SourceLocation MacroLoc = Loc;
|
|
|
|
// Map the location.
|
|
Loc = getImmediateMacroCalleeLoc(SM, Loc);
|
|
|
|
unsigned MacroSkipStart = 0, MacroSkipEnd = 0;
|
|
if (MacroDepth > DiagOpts.MacroBacktraceLimit &&
|
|
DiagOpts.MacroBacktraceLimit != 0) {
|
|
MacroSkipStart = DiagOpts.MacroBacktraceLimit / 2 +
|
|
DiagOpts.MacroBacktraceLimit % 2;
|
|
MacroSkipEnd = MacroDepth - DiagOpts.MacroBacktraceLimit / 2;
|
|
}
|
|
|
|
// Whether to suppress printing this macro expansion.
|
|
bool Suppressed = (OnMacroInst >= MacroSkipStart &&
|
|
OnMacroInst < MacroSkipEnd);
|
|
|
|
// Map the ranges.
|
|
for (SmallVectorImpl<CharSourceRange>::iterator I = Ranges.begin(),
|
|
E = Ranges.end();
|
|
I != E; ++I) {
|
|
SourceLocation Start = I->getBegin(), End = I->getEnd();
|
|
if (Start.isMacroID())
|
|
I->setBegin(getImmediateMacroCalleeLoc(SM, Start));
|
|
if (End.isMacroID())
|
|
I->setEnd(getImmediateMacroCalleeLoc(SM, End));
|
|
}
|
|
|
|
if (Suppressed) {
|
|
// Tell the user that we've skipped contexts.
|
|
if (OnMacroInst == MacroSkipStart) {
|
|
SmallString<200> MessageStorage;
|
|
llvm::raw_svector_ostream Message(MessageStorage);
|
|
Message << "(skipping " << (MacroSkipEnd - MacroSkipStart)
|
|
<< " expansions in backtrace; use -fmacro-backtrace-limit=0 to "
|
|
"see all)";
|
|
emitBasicNote(Message.str());
|
|
}
|
|
return;
|
|
}
|
|
|
|
SmallString<100> MessageStorage;
|
|
llvm::raw_svector_ostream Message(MessageStorage);
|
|
Message << "expanded from macro '"
|
|
<< getImmediateMacroName(MacroLoc, SM, LangOpts) << "'";
|
|
emitDiagnostic(SM.getSpellingLoc(Loc), DiagnosticsEngine::Note,
|
|
Message.str(),
|
|
Ranges, ArrayRef<FixItHint>());
|
|
}
|
|
|
|
DiagnosticNoteRenderer::~DiagnosticNoteRenderer() {}
|
|
|
|
void DiagnosticNoteRenderer::emitIncludeLocation(SourceLocation Loc,
|
|
PresumedLoc PLoc) {
|
|
// Generate a note indicating the include location.
|
|
SmallString<200> MessageStorage;
|
|
llvm::raw_svector_ostream Message(MessageStorage);
|
|
Message << "in file included from " << PLoc.getFilename() << ':'
|
|
<< PLoc.getLine() << ":";
|
|
emitNote(Loc, Message.str());
|
|
}
|
|
|
|
void DiagnosticNoteRenderer::emitBasicNote(StringRef Message) {
|
|
emitNote(SourceLocation(), Message);
|
|
}
|
|
|