llvm-project/clang/lib/Frontend/VerifyDiagnosticsClient.cpp
Chandler Carruth 1aef0c5621 Switch the VerifyDiagnosticsClient to use PresumedLocs now that they
exist. Cheat and do this by adding some wrappers around the PresumedLoc
machinery that directly return the line and column number.

llvm-svn: 126281
2011-02-23 00:47:48 +00:00

514 lines
16 KiB
C++

//===--- VerifyDiagnosticsClient.cpp - Verifying Diagnostic Client --------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This is a concrete diagnostic client, which buffers the diagnostic messages.
//
//===----------------------------------------------------------------------===//
#include "clang/Frontend/VerifyDiagnosticsClient.h"
#include "clang/Frontend/FrontendDiagnostic.h"
#include "clang/Frontend/TextDiagnosticBuffer.h"
#include "clang/Lex/Preprocessor.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/Regex.h"
#include "llvm/Support/raw_ostream.h"
using namespace clang;
VerifyDiagnosticsClient::VerifyDiagnosticsClient(Diagnostic &_Diags,
DiagnosticClient *_Primary)
: Diags(_Diags), PrimaryClient(_Primary),
Buffer(new TextDiagnosticBuffer()), CurrentPreprocessor(0) {
}
VerifyDiagnosticsClient::~VerifyDiagnosticsClient() {
CheckDiagnostics();
}
// DiagnosticClient interface.
void VerifyDiagnosticsClient::BeginSourceFile(const LangOptions &LangOpts,
const Preprocessor *PP) {
// FIXME: Const hack, we screw up the preprocessor but in practice its ok
// because it doesn't get reused. It would be better if we could make a copy
// though.
CurrentPreprocessor = const_cast<Preprocessor*>(PP);
PrimaryClient->BeginSourceFile(LangOpts, PP);
}
void VerifyDiagnosticsClient::EndSourceFile() {
CheckDiagnostics();
PrimaryClient->EndSourceFile();
CurrentPreprocessor = 0;
}
void VerifyDiagnosticsClient::HandleDiagnostic(Diagnostic::Level DiagLevel,
const DiagnosticInfo &Info) {
// Send the diagnostic to the buffer, we will check it once we reach the end
// of the source file (or are destructed).
Buffer->HandleDiagnostic(DiagLevel, Info);
}
//===----------------------------------------------------------------------===//
// Checking diagnostics implementation.
//===----------------------------------------------------------------------===//
typedef TextDiagnosticBuffer::DiagList DiagList;
typedef TextDiagnosticBuffer::const_iterator const_diag_iterator;
namespace {
/// Directive - Abstract class representing a parsed verify directive.
///
class Directive {
public:
static Directive* Create(bool RegexKind, const SourceLocation &Location,
const std::string &Text, unsigned Count);
public:
SourceLocation Location;
const std::string Text;
unsigned Count;
virtual ~Directive() { }
// Returns true if directive text is valid.
// Otherwise returns false and populates E.
virtual bool isValid(std::string &Error) = 0;
// Returns true on match.
virtual bool Match(const std::string &S) = 0;
protected:
Directive(const SourceLocation &Location, const std::string &Text,
unsigned Count)
: Location(Location), Text(Text), Count(Count) { }
private:
Directive(const Directive&); // DO NOT IMPLEMENT
void operator=(const Directive&); // DO NOT IMPLEMENT
};
/// StandardDirective - Directive with string matching.
///
class StandardDirective : public Directive {
public:
StandardDirective(const SourceLocation &Location, const std::string &Text,
unsigned Count)
: Directive(Location, Text, Count) { }
virtual bool isValid(std::string &Error) {
// all strings are considered valid; even empty ones
return true;
}
virtual bool Match(const std::string &S) {
return S.find(Text) != std::string::npos ||
Text.find(S) != std::string::npos;
}
};
/// RegexDirective - Directive with regular-expression matching.
///
class RegexDirective : public Directive {
public:
RegexDirective(const SourceLocation &Location, const std::string &Text,
unsigned Count)
: Directive(Location, Text, Count), Regex(Text) { }
virtual bool isValid(std::string &Error) {
if (Regex.isValid(Error))
return true;
return false;
}
virtual bool Match(const std::string &S) {
return Regex.match(S);
}
private:
llvm::Regex Regex;
};
typedef std::vector<Directive*> DirectiveList;
/// ExpectedData - owns directive objects and deletes on destructor.
///
struct ExpectedData {
DirectiveList Errors;
DirectiveList Warnings;
DirectiveList Notes;
~ExpectedData() {
DirectiveList* Lists[] = { &Errors, &Warnings, &Notes, 0 };
for (DirectiveList **PL = Lists; *PL; ++PL) {
DirectiveList * const L = *PL;
for (DirectiveList::iterator I = L->begin(), E = L->end(); I != E; ++I)
delete *I;
}
}
};
class ParseHelper
{
public:
ParseHelper(const char *Begin, const char *End)
: Begin(Begin), End(End), C(Begin), P(Begin), PEnd(NULL) { }
// Return true if string literal is next.
bool Next(llvm::StringRef S) {
P = C;
PEnd = C + S.size();
if (PEnd > End)
return false;
return !memcmp(P, S.data(), S.size());
}
// Return true if number is next.
// Output N only if number is next.
bool Next(unsigned &N) {
unsigned TMP = 0;
P = C;
for (; P < End && P[0] >= '0' && P[0] <= '9'; ++P) {
TMP *= 10;
TMP += P[0] - '0';
}
if (P == C)
return false;
PEnd = P;
N = TMP;
return true;
}
// Return true if string literal is found.
// When true, P marks begin-position of S in content.
bool Search(llvm::StringRef S) {
P = std::search(C, End, S.begin(), S.end());
PEnd = P + S.size();
return P != End;
}
// Advance 1-past previous next/search.
// Behavior is undefined if previous next/search failed.
bool Advance() {
C = PEnd;
return C < End;
}
// Skip zero or more whitespace.
void SkipWhitespace() {
for (; C < End && isspace(*C); ++C)
;
}
// Return true if EOF reached.
bool Done() {
return !(C < End);
}
const char * const Begin; // beginning of expected content
const char * const End; // end of expected content (1-past)
const char *C; // position of next char in content
const char *P;
private:
const char *PEnd; // previous next/search subject end (1-past)
};
} // namespace anonymous
/// ParseDirective - Go through the comment and see if it indicates expected
/// diagnostics. If so, then put them in the appropriate directive list.
///
static void ParseDirective(const char *CommentStart, unsigned CommentLen,
ExpectedData &ED, Preprocessor &PP,
SourceLocation Pos) {
// A single comment may contain multiple directives.
for (ParseHelper PH(CommentStart, CommentStart+CommentLen); !PH.Done();) {
// search for token: expected
if (!PH.Search("expected"))
break;
PH.Advance();
// next token: -
if (!PH.Next("-"))
continue;
PH.Advance();
// next token: { error | warning | note }
DirectiveList* DL = NULL;
if (PH.Next("error"))
DL = &ED.Errors;
else if (PH.Next("warning"))
DL = &ED.Warnings;
else if (PH.Next("note"))
DL = &ED.Notes;
else
continue;
PH.Advance();
// default directive kind
bool RegexKind = false;
const char* KindStr = "string";
// next optional token: -
if (PH.Next("-re")) {
PH.Advance();
RegexKind = true;
KindStr = "regex";
}
// skip optional whitespace
PH.SkipWhitespace();
// next optional token: positive integer
unsigned Count = 1;
if (PH.Next(Count))
PH.Advance();
// skip optional whitespace
PH.SkipWhitespace();
// next token: {{
if (!PH.Next("{{")) {
PP.Diag(Pos.getFileLocWithOffset(PH.C-PH.Begin),
diag::err_verify_missing_start) << KindStr;
continue;
}
PH.Advance();
const char* const ContentBegin = PH.C; // mark content begin
// search for token: }}
if (!PH.Search("}}")) {
PP.Diag(Pos.getFileLocWithOffset(PH.C-PH.Begin),
diag::err_verify_missing_end) << KindStr;
continue;
}
const char* const ContentEnd = PH.P; // mark content end
PH.Advance();
// build directive text; convert \n to newlines
std::string Text;
llvm::StringRef NewlineStr = "\\n";
llvm::StringRef Content(ContentBegin, ContentEnd-ContentBegin);
size_t CPos = 0;
size_t FPos;
while ((FPos = Content.find(NewlineStr, CPos)) != llvm::StringRef::npos) {
Text += Content.substr(CPos, FPos-CPos);
Text += '\n';
CPos = FPos + NewlineStr.size();
}
if (Text.empty())
Text.assign(ContentBegin, ContentEnd);
// construct new directive
Directive *D = Directive::Create(RegexKind, Pos, Text, Count);
std::string Error;
if (D->isValid(Error))
DL->push_back(D);
else {
PP.Diag(Pos.getFileLocWithOffset(ContentBegin-PH.Begin),
diag::err_verify_invalid_content)
<< KindStr << Error;
}
}
}
/// FindExpectedDiags - Lex the main source file to find all of the
// expected errors and warnings.
static void FindExpectedDiags(Preprocessor &PP, ExpectedData &ED) {
// Create a raw lexer to pull all the comments out of the main file. We don't
// want to look in #include'd headers for expected-error strings.
SourceManager &SM = PP.getSourceManager();
FileID FID = SM.getMainFileID();
if (SM.getMainFileID().isInvalid())
return;
// Create a lexer to lex all the tokens of the main file in raw mode.
const llvm::MemoryBuffer *FromFile = SM.getBuffer(FID);
Lexer RawLex(FID, FromFile, SM, PP.getLangOptions());
// Return comments as tokens, this is how we find expected diagnostics.
RawLex.SetCommentRetentionState(true);
Token Tok;
Tok.setKind(tok::comment);
while (Tok.isNot(tok::eof)) {
RawLex.Lex(Tok);
if (!Tok.is(tok::comment)) continue;
std::string Comment = PP.getSpelling(Tok);
if (Comment.empty()) continue;
// Find all expected errors/warnings/notes.
ParseDirective(&Comment[0], Comment.size(), ED, PP, Tok.getLocation());
};
}
/// PrintProblem - This takes a diagnostic map of the delta between expected and
/// seen diagnostics. If there's anything in it, then something unexpected
/// happened. Print the map out in a nice format and return "true". If the map
/// is empty and we're not going to print things, then return "false".
///
static unsigned PrintProblem(Diagnostic &Diags, SourceManager *SourceMgr,
const_diag_iterator diag_begin,
const_diag_iterator diag_end,
const char *Kind, bool Expected) {
if (diag_begin == diag_end) return 0;
llvm::SmallString<256> Fmt;
llvm::raw_svector_ostream OS(Fmt);
for (const_diag_iterator I = diag_begin, E = diag_end; I != E; ++I) {
if (I->first.isInvalid() || !SourceMgr)
OS << "\n (frontend)";
else
OS << "\n Line " << SourceMgr->getPresumedLineNumber(I->first);
OS << ": " << I->second;
}
Diags.Report(diag::err_verify_inconsistent_diags)
<< Kind << !Expected << OS.str();
return std::distance(diag_begin, diag_end);
}
static unsigned PrintProblem(Diagnostic &Diags, SourceManager *SourceMgr,
DirectiveList &DL, const char *Kind,
bool Expected) {
if (DL.empty())
return 0;
llvm::SmallString<256> Fmt;
llvm::raw_svector_ostream OS(Fmt);
for (DirectiveList::iterator I = DL.begin(), E = DL.end(); I != E; ++I) {
Directive& D = **I;
if (D.Location.isInvalid() || !SourceMgr)
OS << "\n (frontend)";
else
OS << "\n Line " << SourceMgr->getPresumedLineNumber(D.Location);
OS << ": " << D.Text;
}
Diags.Report(diag::err_verify_inconsistent_diags)
<< Kind << !Expected << OS.str();
return DL.size();
}
/// CheckLists - Compare expected to seen diagnostic lists and return the
/// the difference between them.
///
static unsigned CheckLists(Diagnostic &Diags, SourceManager &SourceMgr,
const char *Label,
DirectiveList &Left,
const_diag_iterator d2_begin,
const_diag_iterator d2_end) {
DirectiveList LeftOnly;
DiagList Right(d2_begin, d2_end);
for (DirectiveList::iterator I = Left.begin(), E = Left.end(); I != E; ++I) {
Directive& D = **I;
unsigned LineNo1 = SourceMgr.getPresumedLineNumber(D.Location);
for (unsigned i = 0; i < D.Count; ++i) {
DiagList::iterator II, IE;
for (II = Right.begin(), IE = Right.end(); II != IE; ++II) {
unsigned LineNo2 = SourceMgr.getPresumedLineNumber(II->first);
if (LineNo1 != LineNo2)
continue;
const std::string &RightText = II->second;
if (D.Match(RightText))
break;
}
if (II == IE) {
// Not found.
LeftOnly.push_back(*I);
} else {
// Found. The same cannot be found twice.
Right.erase(II);
}
}
}
// Now all that's left in Right are those that were not matched.
return (PrintProblem(Diags, &SourceMgr, LeftOnly, Label, true) +
PrintProblem(Diags, &SourceMgr, Right.begin(), Right.end(),
Label, false));
}
/// CheckResults - This compares the expected results to those that
/// were actually reported. It emits any discrepencies. Return "true" if there
/// were problems. Return "false" otherwise.
///
static unsigned CheckResults(Diagnostic &Diags, SourceManager &SourceMgr,
const TextDiagnosticBuffer &Buffer,
ExpectedData &ED) {
// We want to capture the delta between what was expected and what was
// seen.
//
// Expected \ Seen - set expected but not seen
// Seen \ Expected - set seen but not expected
unsigned NumProblems = 0;
// See if there are error mismatches.
NumProblems += CheckLists(Diags, SourceMgr, "error", ED.Errors,
Buffer.err_begin(), Buffer.err_end());
// See if there are warning mismatches.
NumProblems += CheckLists(Diags, SourceMgr, "warning", ED.Warnings,
Buffer.warn_begin(), Buffer.warn_end());
// See if there are note mismatches.
NumProblems += CheckLists(Diags, SourceMgr, "note", ED.Notes,
Buffer.note_begin(), Buffer.note_end());
return NumProblems;
}
void VerifyDiagnosticsClient::CheckDiagnostics() {
ExpectedData ED;
// Ensure any diagnostics go to the primary client.
DiagnosticClient *CurClient = Diags.takeClient();
Diags.setClient(PrimaryClient.get());
// If we have a preprocessor, scan the source for expected diagnostic
// markers. If not then any diagnostics are unexpected.
if (CurrentPreprocessor) {
FindExpectedDiags(*CurrentPreprocessor, ED);
// Check that the expected diagnostics occurred.
NumErrors += CheckResults(Diags, CurrentPreprocessor->getSourceManager(),
*Buffer, ED);
} else {
NumErrors += (PrintProblem(Diags, 0,
Buffer->err_begin(), Buffer->err_end(),
"error", false) +
PrintProblem(Diags, 0,
Buffer->warn_begin(), Buffer->warn_end(),
"warn", false) +
PrintProblem(Diags, 0,
Buffer->note_begin(), Buffer->note_end(),
"note", false));
}
Diags.takeClient();
Diags.setClient(CurClient);
// Reset the buffer, we have processed all the diagnostics in it.
Buffer.reset(new TextDiagnosticBuffer());
}
Directive* Directive::Create(bool RegexKind, const SourceLocation &Location,
const std::string &Text, unsigned Count) {
if (RegexKind)
return new RegexDirective(Location, Text, Count);
return new StandardDirective(Location, Text, Count);
}