in lib/Frontend/DiagnosticVerifier.cpp [308:911]
DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
using llvm::SMLoc;
const SourceLoc BufferStartLoc = SM.getLocForBufferStart(BufferID);
CharSourceRange EntireRange = SM.getRangeForBuffer(BufferID);
StringRef InputFile = SM.extractText(EntireRange);
StringRef BufferName = SM.getIdentifierForBuffer(BufferID);
// Queue up all of the diagnostics, allowing us to sort them and emit them in
// file order.
std::vector<llvm::SMDiagnostic> Errors;
unsigned PrevExpectedContinuationLine = 0;
std::vector<ExpectedDiagnosticInfo> ExpectedDiagnostics;
auto addError = [&](const char *Loc, const Twine &message,
ArrayRef<llvm::SMFixIt> FixIts = {}) {
auto loc = SourceLoc(SMLoc::getFromPointer(Loc));
auto diag = SM.GetMessage(loc, llvm::SourceMgr::DK_Error, message,
{}, FixIts);
Errors.push_back(diag);
};
// Scan the memory buffer looking for expected-note/warning/error.
for (size_t Match = InputFile.find("expected-");
Match != StringRef::npos; Match = InputFile.find("expected-", Match+1)) {
// Process this potential match. If we fail to process it, just move on to
// the next match.
StringRef MatchStart = InputFile.substr(Match);
const char *DiagnosticLoc = MatchStart.data();
DiagnosticKind ExpectedClassification;
if (MatchStart.startswith("expected-note")) {
ExpectedClassification = DiagnosticKind::Note;
MatchStart = MatchStart.substr(strlen("expected-note"));
} else if (MatchStart.startswith("expected-warning")) {
ExpectedClassification = DiagnosticKind::Warning;
MatchStart = MatchStart.substr(strlen("expected-warning"));
} else if (MatchStart.startswith("expected-error")) {
ExpectedClassification = DiagnosticKind::Error;
MatchStart = MatchStart.substr(strlen("expected-error"));
} else if (MatchStart.startswith("expected-remark")) {
ExpectedClassification = DiagnosticKind::Remark;
MatchStart = MatchStart.substr(strlen("expected-remark"));
} else
continue;
// Skip any whitespace before the {{.
MatchStart = MatchStart.substr(MatchStart.find_first_not_of(" \t"));
size_t TextStartIdx = MatchStart.find("{{");
if (TextStartIdx == StringRef::npos) {
addError(MatchStart.data(),
"expected {{ in expected-warning/note/error line");
continue;
}
ExpectedDiagnosticInfo Expected(DiagnosticLoc, ExpectedClassification);
int LineOffset = 0;
if (TextStartIdx > 0 && MatchStart[0] == '@') {
if (MatchStart[1] != '+' && MatchStart[1] != '-' &&
MatchStart[1] != ':') {
addError(MatchStart.data(),
"expected '+'/'-' for line offset, or ':' for column");
continue;
}
StringRef Offs;
if (MatchStart[1] == '+')
Offs = MatchStart.slice(2, TextStartIdx).rtrim();
else
Offs = MatchStart.slice(1, TextStartIdx).rtrim();
size_t SpaceIndex = Offs.find(' ');
if (SpaceIndex != StringRef::npos && SpaceIndex < TextStartIdx) {
size_t Delta = Offs.size() - SpaceIndex;
MatchStart = MatchStart.substr(TextStartIdx - Delta);
TextStartIdx = Delta;
Offs = Offs.slice(0, SpaceIndex);
} else {
MatchStart = MatchStart.substr(TextStartIdx);
TextStartIdx = 0;
}
size_t ColonIndex = Offs.find(':');
// Check whether a line offset was provided
if (ColonIndex != 0) {
StringRef LineOffs = Offs.slice(0, ColonIndex);
if (LineOffs.getAsInteger(10, LineOffset)) {
addError(MatchStart.data(), "expected line offset before '{{'");
continue;
}
}
// Check whether a column was provided
if (ColonIndex != StringRef::npos) {
Offs = Offs.slice(ColonIndex + 1, Offs.size());
int Column = 0;
if (Offs.getAsInteger(10, Column)) {
addError(MatchStart.data(), "expected column before '{{'");
continue;
}
Expected.ColumnNo = Column;
}
}
unsigned Count = 1;
if (TextStartIdx > 0) {
StringRef CountStr = MatchStart.substr(0, TextStartIdx).trim();
if (CountStr == "*") {
Expected.mayAppear = true;
} else {
if (CountStr.getAsInteger(10, Count)) {
addError(MatchStart.data(), "expected match count before '{{'");
continue;
}
if (Count == 0) {
addError(MatchStart.data(),
"expected positive match count before '{{'");
continue;
}
}
// Resync up to the '{{'.
MatchStart = MatchStart.substr(TextStartIdx);
}
size_t End = MatchStart.find("}}");
if (End == StringRef::npos) {
addError(MatchStart.data(),
"didn't find '}}' to match '{{' in expected-warning/note/error line");
continue;
}
llvm::SmallString<256> Buf;
Expected.MessageRange = MatchStart.slice(2, End);
Expected.MessageStr =
Lexer::getEncodedStringSegment(Expected.MessageRange, Buf).str();
if (PrevExpectedContinuationLine)
Expected.LineNo = PrevExpectedContinuationLine;
else
Expected.LineNo = SM.getPresumedLineAndColumnForLoc(
BufferStartLoc.getAdvancedLoc(MatchStart.data() -
InputFile.data()),
BufferID)
.first;
Expected.LineNo += LineOffset;
// Check if the next expected diagnostic should be in the same line.
StringRef AfterEnd = MatchStart.substr(End + strlen("}}"));
AfterEnd = AfterEnd.substr(AfterEnd.find_first_not_of(" \t"));
if (AfterEnd.startswith("\\"))
PrevExpectedContinuationLine = Expected.LineNo;
else
PrevExpectedContinuationLine = 0;
// Scan for fix-its: {{10-14=replacement text}}
StringRef ExtraChecks = MatchStart.substr(End+2).ltrim(" \t");
while (ExtraChecks.startswith("{{")) {
// First make sure we have a closing "}}".
size_t EndIndex = ExtraChecks.find("}}");
if (EndIndex == StringRef::npos) {
addError(ExtraChecks.data(),
"didn't find '}}' to match '{{' in diagnostic verification");
break;
}
// Allow for close braces to appear in the replacement text.
while (EndIndex + 2 < ExtraChecks.size() &&
ExtraChecks[EndIndex + 2] == '}')
++EndIndex;
const char *OpenLoc = ExtraChecks.data(); // Beginning of opening '{{'.
const char *CloseLoc =
ExtraChecks.data() + EndIndex + 2; // End of closing '}}'.
StringRef CheckStr = ExtraChecks.slice(2, EndIndex);
// Check for matching a later "}}" on a different line.
if (CheckStr.find_first_of("\r\n") != StringRef::npos) {
addError(ExtraChecks.data(), "didn't find '}}' to match '{{' in "
"diagnostic verification");
break;
}
// Prepare for the next round of checks.
ExtraChecks = ExtraChecks.substr(EndIndex + 2).ltrim();
// If this check starts with 'educational-notes=', check for one or more
// educational notes instead of a fix-it.
if (CheckStr.startswith(educationalNotesSpecifier)) {
if (Expected.EducationalNotes.hasValue()) {
addError(CheckStr.data(),
"each verified diagnostic may only have one "
"{{educational-notes=<#notes#>}} declaration");
continue;
}
StringRef NotesStr = CheckStr.substr(
educationalNotesSpecifier.size()); // Trim 'educational-notes='.
llvm::SmallVector<StringRef, 1> names;
// Note names are comma-separated.
std::pair<StringRef, StringRef> split;
do {
split = NotesStr.split(',');
names.push_back(split.first);
NotesStr = split.second;
} while (!NotesStr.empty());
Expected.EducationalNotes.emplace(OpenLoc, CloseLoc, names);
continue;
}
// This wasn't an educational notes specifier, so it must be a fix-it.
// Special case for specifying no fixits should appear.
if (CheckStr == fixitExpectationNoneString) {
if (Expected.noneMarkerStartLoc) {
addError(CheckStr.data() - 2,
Twine("A second {{") + fixitExpectationNoneString +
"}} was found. It may only appear once in an expectation.");
break;
}
Expected.noneMarkerStartLoc = CheckStr.data() - 2;
continue;
}
if (Expected.noneMarkerStartLoc) {
addError(Expected.noneMarkerStartLoc, Twine("{{") +
fixitExpectationNoneString +
"}} must be at the end.");
break;
}
// Parse the pieces of the fix-it.
size_t MinusLoc = CheckStr.find('-');
if (MinusLoc == StringRef::npos) {
addError(CheckStr.data(), "expected '-' in fix-it verification");
continue;
}
StringRef StartColStr = CheckStr.slice(0, MinusLoc);
StringRef AfterMinus = CheckStr.substr(MinusLoc + 1);
size_t EqualLoc = AfterMinus.find('=');
if (EqualLoc == StringRef::npos) {
addError(AfterMinus.data(),
"expected '=' after '-' in fix-it verification");
continue;
}
StringRef EndColStr = AfterMinus.slice(0, EqualLoc);
StringRef AfterEqual = AfterMinus.substr(EqualLoc+1);
ExpectedFixIt FixIt;
FixIt.StartLoc = OpenLoc;
FixIt.EndLoc = CloseLoc;
if (StartColStr.getAsInteger(10, FixIt.StartCol)) {
addError(StartColStr.data(),
"invalid column number in fix-it verification");
continue;
}
if (EndColStr.getAsInteger(10, FixIt.EndCol)) {
addError(EndColStr.data(),
"invalid column number in fix-it verification");
continue;
}
// Translate literal "\\n" into '\n', inefficiently.
StringRef fixItText = AfterEqual.slice(0, EndIndex);
for (const char *current = fixItText.begin(), *end = fixItText.end();
current != end; /* in loop */) {
if (*current == '\\' && current + 1 < end) {
if (current[1] == 'n') {
FixIt.Text += '\n';
current += 2;
} else { // Handle \}, \\, etc.
FixIt.Text += current[1];
current += 2;
}
} else {
FixIt.Text += *current++;
}
}
Expected.Fixits.push_back(FixIt);
}
Expected.ExpectedEnd = ExtraChecks.data();
// Don't include trailing whitespace in the expected-foo{{}} range.
while (isspace(Expected.ExpectedEnd[-1]))
--Expected.ExpectedEnd;
// Add the diagnostic the expected number of times.
for (; Count; --Count)
ExpectedDiagnostics.push_back(Expected);
}
// Make sure all the expected diagnostics appeared.
std::reverse(ExpectedDiagnostics.begin(), ExpectedDiagnostics.end());
for (unsigned i = ExpectedDiagnostics.size(); i != 0; ) {
--i;
auto &expected = ExpectedDiagnostics[i];
// Check to see if we had this expected diagnostic.
auto FoundDiagnosticIter =
findDiagnostic(CapturedDiagnostics, expected, BufferName);
if (FoundDiagnosticIter == CapturedDiagnostics.end()) {
// Diagnostic didn't exist. If this is a 'mayAppear' diagnostic, then
// we're ok. Otherwise, leave it in the list.
if (expected.mayAppear)
ExpectedDiagnostics.erase(ExpectedDiagnostics.begin()+i);
continue;
}
auto &FoundDiagnostic = *FoundDiagnosticIter;
const char *missedFixitLoc = nullptr;
// Verify that any expected fix-its are present in the diagnostic.
for (auto fixit : expected.Fixits) {
// If we found it, we're ok.
if (!checkForFixIt(fixit, FoundDiagnostic, InputFile)) {
missedFixitLoc = fixit.StartLoc;
break;
}
}
const bool isUnexpectedFixitsSeen =
expected.Fixits.size() < FoundDiagnostic.FixIts.size();
struct ActualFixitsPhrase {
std::string phrase;
std::string actualFixits;
};
auto makeActualFixitsPhrase =
[&](ArrayRef<DiagnosticInfo::FixIt> actualFixits)
-> ActualFixitsPhrase {
std::string actualFixitsStr = renderFixits(actualFixits, InputFile);
return ActualFixitsPhrase{(Twine("actual fix-it") +
(actualFixits.size() >= 2 ? "s" : "") +
" seen: " + actualFixitsStr).str(),
actualFixitsStr};
};
auto emitFixItsError = [&](const char *location, const Twine &message,
const char *replStartLoc, const char *replEndLoc,
const std::string &replStr) {
llvm::SMFixIt fix(llvm::SMRange(SMLoc::getFromPointer(replStartLoc),
SMLoc::getFromPointer(replEndLoc)),
replStr);
addError(location, message, fix);
};
// If we have any expected fixits that didn't get matched, then they are
// wrong. Replace the failed fixit with what actually happened.
if (missedFixitLoc) {
// If we had an incorrect expected fixit, render it and produce a fixit
// of our own.
assert(!expected.Fixits.empty() &&
"some fix-its should be expected here");
const char *replStartLoc = expected.Fixits.front().StartLoc;
const char *replEndLoc = expected.Fixits.back().EndLoc;
std::string message = "expected fix-it not seen";
std::string actualFixits;
if (FoundDiagnostic.FixIts.empty()) {
/// If actual fix-its is empty,
/// eat a space before first marker.
/// For example,
///
/// @code
/// expected-error {{message}} {{1-2=aa}}
/// ~~~~~~~~~~~
/// ^ remove
/// @endcode
if (replStartLoc[-1] == ' ') {
--replStartLoc;
}
} else {
auto phrase = makeActualFixitsPhrase(FoundDiagnostic.FixIts);
actualFixits = phrase.actualFixits;
message += "; " + phrase.phrase;
}
emitFixItsError(missedFixitLoc, message, replStartLoc, replEndLoc,
actualFixits);
} else if (expected.noExtraFixitsMayAppear() && isUnexpectedFixitsSeen) {
// If unexpected fixit were produced, add a fixit to add them in.
assert(!FoundDiagnostic.FixIts.empty() &&
"some fix-its should be produced here");
assert(expected.noneMarkerStartLoc && "none marker location is null");
const char *replStartLoc = nullptr, *replEndLoc = nullptr;
std::string message;
if (expected.Fixits.empty()) {
message = "expected no fix-its";
replStartLoc = expected.noneMarkerStartLoc;
replEndLoc = expected.noneMarkerStartLoc;
} else {
message = "unexpected fix-it seen";
replStartLoc = expected.Fixits.front().StartLoc;
replEndLoc = expected.Fixits.back().EndLoc;
}
auto phrase = makeActualFixitsPhrase(FoundDiagnostic.FixIts);
std::string actualFixits = phrase.actualFixits;
message += "; " + phrase.phrase;
if (replStartLoc == replEndLoc) {
/// If no fix-its was expected and range of replacement is empty,
/// insert space after new last marker.
/// For example:
///
/// @code
/// expected-error {{message}} {{none}}
/// ^
/// insert `{{1-2=aa}} `
/// @endcode
actualFixits += " ";
}
emitFixItsError(expected.noneMarkerStartLoc, message, replStartLoc,
replEndLoc, actualFixits);
}
if (auto expectedNotes = expected.EducationalNotes) {
// Verify educational notes
for (auto &foundName : FoundDiagnostic.EducationalNotes) {
llvm::erase_if(expectedNotes->Names,
[&](StringRef item) { return item.equals(foundName); });
}
if (!expectedNotes->Names.empty()) {
if (FoundDiagnostic.EducationalNotes.empty()) {
addError(expectedNotes->StartLoc,
"expected educational note(s) not seen");
} else {
// If we had an incorrect expected note, render it and produce a fixit
// of our own.
auto actual =
renderEducationalNotes(FoundDiagnostic.EducationalNotes);
auto replStartLoc = SMLoc::getFromPointer(expectedNotes->StartLoc);
auto replEndLoc = SMLoc::getFromPointer(expectedNotes->EndLoc);
llvm::SMFixIt fix(llvm::SMRange(replStartLoc, replEndLoc), actual);
addError(expectedNotes->StartLoc,
"expected educational note(s) not seen; actual educational "
"note(s): " + actual, fix);
}
}
}
// Actually remove the diagnostic from the list, so we don't match it
// again. We do have to do this after checking fix-its, though, because
// the diagnostic owns its fix-its.
CapturedDiagnostics.erase(FoundDiagnosticIter);
// We found the diagnostic, so remove it... unless we allow an arbitrary
// number of diagnostics, in which case we want to reprocess this.
if (expected.mayAppear)
++i;
else
ExpectedDiagnostics.erase(ExpectedDiagnostics.begin()+i);
}
// Check to see if we have any incorrect diagnostics. If so, diagnose them as
// such.
auto expectedDiagIter = ExpectedDiagnostics.begin();
while (expectedDiagIter != ExpectedDiagnostics.end()) {
// Check to see if any found diagnostics have the right line and
// classification, but the wrong text.
auto I = CapturedDiagnostics.begin();
for (auto E = CapturedDiagnostics.end(); I != E; ++I) {
// Verify the file and line of the diagnostic.
if (I->Line != expectedDiagIter->LineNo || I->FileName != BufferName ||
I->Classification != expectedDiagIter->Classification)
continue;
// Otherwise, we found it, break out.
break;
}
if (I == CapturedDiagnostics.end()) {
++expectedDiagIter;
continue;
}
if (I->Message.find(expectedDiagIter->MessageStr) == StringRef::npos) {
auto StartLoc =
SMLoc::getFromPointer(expectedDiagIter->MessageRange.begin());
auto EndLoc = SMLoc::getFromPointer(expectedDiagIter->MessageRange.end());
llvm::SMFixIt fixIt(llvm::SMRange{StartLoc, EndLoc}, I->Message);
addError(expectedDiagIter->MessageRange.begin(),
"incorrect message found", fixIt);
} else if (I->Column != *expectedDiagIter->ColumnNo) {
// The difference must be only in the column
addError(expectedDiagIter->MessageRange.begin(),
llvm::formatv("message found at column {0} but was expected to "
"appear at column {1}",
I->Column, *expectedDiagIter->ColumnNo));
} else {
llvm_unreachable("unhandled difference from expected diagnostic");
}
CapturedDiagnostics.erase(I);
expectedDiagIter = ExpectedDiagnostics.erase(expectedDiagIter);
}
// Diagnose expected diagnostics that didn't appear.
std::reverse(ExpectedDiagnostics.begin(), ExpectedDiagnostics.end());
for (auto const &expected : ExpectedDiagnostics) {
std::string message = "expected "+getDiagKindString(expected.Classification)
+ " not produced";
// Get the range of the expected-foo{{}} diagnostic specifier.
auto StartLoc = expected.ExpectedStart;
auto EndLoc = expected.ExpectedEnd;
// A very common case if for the specifier to be the last thing on the line.
// In this case, eat any trailing whitespace.
while (isspace(*EndLoc) && *EndLoc != '\n' && *EndLoc != '\r')
++EndLoc;
// If we found the end of the line, we can do great things. Otherwise,
// avoid nuking whitespace that might be zapped through other means.
if (*EndLoc != '\n' && *EndLoc != '\r') {
EndLoc = expected.ExpectedEnd;
} else {
// If we hit the end of line, then zap whitespace leading up to it.
auto FileStart = InputFile.data();
while (StartLoc-1 != FileStart && isspace(StartLoc[-1]) &&
StartLoc[-1] != '\n' && StartLoc[-1] != '\r')
--StartLoc;
// If we got to the end of the line, and the thing before this diagnostic
// is a "//" then we can remove it too.
if (StartLoc-2 >= FileStart && StartLoc[-1] == '/' && StartLoc[-2] == '/')
StartLoc -= 2;
// Perform another round of general whitespace nuking to cleanup
// whitespace before the //.
while (StartLoc-1 != FileStart && isspace(StartLoc[-1]) &&
StartLoc[-1] != '\n' && StartLoc[-1] != '\r')
--StartLoc;
// If we found a \n, then we can nuke the entire line.
if (StartLoc-1 != FileStart &&
(StartLoc[-1] == '\n' || StartLoc[-1] == '\r'))
--StartLoc;
}
// Remove the expected-foo{{}} as a fixit.
llvm::SMFixIt fixIt(llvm::SMRange{
SMLoc::getFromPointer(StartLoc),
SMLoc::getFromPointer(EndLoc)
}, "");
addError(expected.ExpectedStart, message, fixIt);
}
// Verify that there are no diagnostics (in MemoryBuffer) left in the list.
bool HadUnexpectedDiag = false;
auto CapturedDiagIter = CapturedDiagnostics.begin();
while (CapturedDiagIter != CapturedDiagnostics.end()) {
if (CapturedDiagIter->FileName != BufferName) {
++CapturedDiagIter;
continue;
}
HadUnexpectedDiag = true;
std::string Message =
("unexpected " + getDiagKindString(CapturedDiagIter->Classification) +
" produced: " + CapturedDiagIter->Message)
.str();
addError(getRawLoc(CapturedDiagIter->Loc).getPointer(), Message);
CapturedDiagIter = CapturedDiagnostics.erase(CapturedDiagIter);
}
// Sort the diagnostics by their address in the memory buffer as the primary
// key. This ensures that an "unexpected diagnostic" and
// "expected diagnostic" in the same place are emitted next to each other.
std::sort(Errors.begin(), Errors.end(),
[&](const llvm::SMDiagnostic &lhs,
const llvm::SMDiagnostic &rhs) -> bool {
return lhs.getLoc().getPointer() < rhs.getLoc().getPointer();
});
// Emit all of the queue'd up errors.
for (auto Err : Errors)
SM.getLLVMSourceMgr().PrintMessage(llvm::errs(), Err);
// If auto-apply fixits is on, rewrite the original source file.
if (AutoApplyFixes)
autoApplyFixes(SM, BufferID, Errors);
return Result{!Errors.empty(), HadUnexpectedDiag};
}