1
0
mirror of https://github.com/RPCS3/llvm-mirror.git synced 2024-11-25 04:02:41 +01:00

[FileCheck] Fix numeric error propagation

A more general name might be match-time error propagation.  That is,
it's conceivable we'll one day have non-numeric errors that require
the handling fixed by this patch.

Without this patch, FileCheck behaves as follows:

```
$ cat check
CHECK-NOT: [[#0x8000000000000000+0x8000000000000000]]

$ FileCheck -vv -dump-input=never check < input
check:1:54: remark: implicit EOF: expected string found in input
CHECK-NOT: [[#0x8000000000000000+0x8000000000000000]]
                                                     ^
<stdin>:2:1: note: found here

^
check:1:15: error: unable to substitute variable or numeric expression: overflow error
CHECK-NOT: [[#0x8000000000000000+0x8000000000000000]]
              ^
$ echo $?
0
```

Notice that the exit status is 0 even though there's an error.
Moreover, FileCheck doesn't print the error diagnostic unless both
`-dump-input=never` and `-vv` are specified.

The same problem occurs when `CHECK-NOT` does have a match but a
capture fails due to overflow: exit status is 0, and no diagnostic is
printed unless both `-dump-input=never` and `-vv` are specified.  The
usefulness of capturing from `CHECK-NOT` is questionable, but this
case should certainly produce an error.

With this patch, FileCheck always includes the error diagnostic and
has non-zero exit status for the above examples.  It's conceivable
that this change will cause some existing tests to fail, but my
assumption is that they should fail.  Moreover, with nearly every
project enabled, this patch didn't produce additional `check-all`
failures for me.

This patch also extends input dumps to include such numeric error
diagnostics for both expected and excluded patterns.

As noted in fixmes in some of the tests added by this patch, this
patch worsens an existing issue with redundant diagnostics.  I'll fix
that bug in a subsequent patch.

Reviewed By: thopre, jhenderson

Differential Revision: https://reviews.llvm.org/D98086
This commit is contained in:
Joel E. Denny 2021-03-17 14:13:57 -04:00
parent 1bc9df98e9
commit 20f8c79d86
9 changed files with 566 additions and 151 deletions

View File

@ -118,8 +118,6 @@ struct FileCheckDiag {
/// depending on whether the pattern must have or must not have a match in
/// order for the directive to succeed. For example, a CHECK directive's
/// pattern is expected, and a CHECK-NOT directive's pattern is excluded.
/// All match result types whose names end with "Excluded" are for excluded
/// patterns, and all others are for expected patterns.
///
/// There might be more than one match result for a single pattern. For
/// example, there might be several discarded matches
@ -136,18 +134,29 @@ struct FileCheckDiag {
MatchFoundButWrongLine,
/// Indicates a discarded match for an expected pattern.
MatchFoundButDiscarded,
/// Indicates an error while processing a match after the match was found
/// for an expected or excluded pattern. The error is specified by \c Note,
/// to which it should be appropriate to prepend "error: " later. The full
/// match itself should be recorded in a preceding diagnostic of a different
/// \c MatchFound match type.
MatchFoundErrorNote,
/// Indicates no match for an excluded pattern.
MatchNoneAndExcluded,
/// Indicates no match for an expected pattern, but this might follow good
/// matches when multiple matches are expected for the pattern, or it might
/// follow discarded matches for the pattern.
MatchNoneButExpected,
/// Indicates no match due to an expected or excluded pattern that has
/// proven to be invalid at match time. The exact problems are usually
/// reported in subsequent diagnostics of the same match type but with
/// \c Note set.
MatchNoneForInvalidPattern,
/// Indicates a fuzzy match that serves as a suggestion for the next
/// intended match for an expected pattern with too few or no good matches.
MatchFuzzy,
} MatchTy;
/// The search range if MatchTy is MatchNoneAndExcluded or
/// MatchNoneButExpected, or the match range otherwise.
/// The search range if MatchTy starts with MatchNone, or the match range
/// otherwise.
unsigned InputStartLine;
unsigned InputStartCol;
unsigned InputEndLine;

View File

@ -476,6 +476,7 @@ char OverflowError::ID = 0;
char UndefVarError::ID = 0;
char ErrorDiagnostic::ID = 0;
char NotFoundError::ID = 0;
char ErrorReported::ID = 0;
Expected<NumericVariable *> Pattern::parseNumericVariableDefinition(
StringRef &Expr, FileCheckPatternContext *Context,
@ -1212,22 +1213,19 @@ void Pattern::AddBackrefToRegEx(unsigned BackrefNum) {
RegExStr += Backref;
}
Expected<size_t> Pattern::match(StringRef Buffer, size_t &MatchLen,
const SourceMgr &SM) const {
Pattern::MatchResult Pattern::match(StringRef Buffer,
const SourceMgr &SM) const {
// If this is the EOF pattern, match it immediately.
if (CheckTy == Check::CheckEOF) {
MatchLen = 0;
return Buffer.size();
}
if (CheckTy == Check::CheckEOF)
return MatchResult(Buffer.size(), 0, Error::success());
// If this is a fixed string pattern, just match it now.
if (!FixedStr.empty()) {
MatchLen = FixedStr.size();
size_t Pos =
IgnoreCase ? Buffer.find_lower(FixedStr) : Buffer.find(FixedStr);
if (Pos == StringRef::npos)
return make_error<NotFoundError>();
return Pos;
return MatchResult(Pos, /*MatchLen=*/FixedStr.size(), Error::success());
}
// Regex match.
@ -1250,7 +1248,7 @@ Expected<size_t> Pattern::match(StringRef Buffer, size_t &MatchLen,
Expected<std::string> Value = Substitution->getResult();
if (!Value) {
// Convert to an ErrorDiagnostic to get location information. This is
// done here rather than PrintNoMatch since now we know which
// done here rather than printMatch/printNoMatch since now we know which
// substitution block caused the overflow.
Error Err =
handleErrors(Value.takeError(), [&](const OverflowError &E) {
@ -1289,6 +1287,14 @@ Expected<size_t> Pattern::match(StringRef Buffer, size_t &MatchLen,
MatchInfo[VariableDef.second];
}
// Like CHECK-NEXT, CHECK-EMPTY's match range is considered to start after
// the required preceding newline, which is consumed by the pattern in the
// case of CHECK-EMPTY but not CHECK-NEXT.
size_t MatchStartSkip = CheckTy == Check::CheckEmpty;
Match TheMatch;
TheMatch.Pos = FullMatch.data() - Buffer.data() + MatchStartSkip;
TheMatch.Len = FullMatch.size() - MatchStartSkip;
// If this defines any numeric variables, remember their values.
for (const auto &NumericVariableDef : NumericVariableDefs) {
const NumericVariableMatch &NumericVariableMatch =
@ -1303,16 +1309,11 @@ Expected<size_t> Pattern::match(StringRef Buffer, size_t &MatchLen,
Expected<ExpressionValue> Value =
Format.valueFromStringRepr(MatchedValue, SM);
if (!Value)
return Value.takeError();
return MatchResult(TheMatch, Value.takeError());
DefinedNumericVariable->setValue(*Value, MatchedValue);
}
// Like CHECK-NEXT, CHECK-EMPTY's match range is considered to start after
// the required preceding newline, which is consumed by the pattern in the
// case of CHECK-EMPTY but not CHECK-NEXT.
size_t MatchStartSkip = CheckTy == Check::CheckEmpty;
MatchLen = FullMatch.size() - MatchStartSkip;
return FullMatch.data() - Buffer.data() + MatchStartSkip;
return MatchResult(TheMatch, Error::success());
}
unsigned Pattern::computeMatchDistance(StringRef Buffer) const {
@ -1349,7 +1350,7 @@ void Pattern::printSubstitutions(const SourceMgr &SM, StringRef Buffer,
bool UndefSeen = false;
handleAllErrors(
MatchedValue.takeError(), [](const NotFoundError &E) {},
// Handled in PrintNoMatch().
// Handled in printMatch and printNoMatch().
[](const ErrorDiagnostic &E) {},
// Handled in match().
[](const OverflowError &E) {},
@ -1404,11 +1405,12 @@ void Pattern::printVariableDefs(const SourceMgr &SM,
for (const auto &VariableDef : NumericVariableDefs) {
VarCapture VC;
VC.Name = VariableDef.getKey();
StringRef StrValue = VariableDef.getValue()
.DefinedNumericVariable->getStringValue()
.getValue();
SMLoc Start = SMLoc::getFromPointer(StrValue.data());
SMLoc End = SMLoc::getFromPointer(StrValue.data() + StrValue.size());
Optional<StringRef> StrValue =
VariableDef.getValue().DefinedNumericVariable->getStringValue();
if (!StrValue)
continue;
SMLoc Start = SMLoc::getFromPointer(StrValue->data());
SMLoc End = SMLoc::getFromPointer(StrValue->data() + StrValue->size());
VC.Range = SMRange(Start, End);
VarCaptures.push_back(VC);
}
@ -2036,123 +2038,179 @@ bool FileCheck::readCheckFile(
return false;
}
static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM,
StringRef Prefix, SMLoc Loc, const Pattern &Pat,
int MatchedCount, StringRef Buffer, size_t MatchPos,
size_t MatchLen, const FileCheckRequest &Req,
std::vector<FileCheckDiag> *Diags) {
/// Returns either (1) \c ErrorSuccess if there was no error or (2)
/// \c ErrorReported if an error was reported, such as an unexpected match.
static Error printMatch(bool ExpectedMatch, const SourceMgr &SM,
StringRef Prefix, SMLoc Loc, const Pattern &Pat,
int MatchedCount, StringRef Buffer,
Pattern::MatchResult MatchResult,
const FileCheckRequest &Req,
std::vector<FileCheckDiag> *Diags) {
// Suppress some verbosity if there's no error.
bool HasError = !ExpectedMatch || MatchResult.TheError;
bool PrintDiag = true;
if (ExpectedMatch) {
if (!HasError) {
if (!Req.Verbose)
return;
return ErrorReported::reportedOrSuccess(HasError);
if (!Req.VerboseVerbose && Pat.getCheckTy() == Check::CheckEOF)
return;
return ErrorReported::reportedOrSuccess(HasError);
// Due to their verbosity, we don't print verbose diagnostics here if we're
// gathering them for a different rendering, but we always print other
// diagnostics.
// gathering them for Diags to be rendered elsewhere, but we always print
// other diagnostics.
PrintDiag = !Diags;
}
// Add "found" diagnostic, substitutions, and variable definitions to Diags.
FileCheckDiag::MatchType MatchTy = ExpectedMatch
? FileCheckDiag::MatchFoundAndExpected
: FileCheckDiag::MatchFoundButExcluded;
SMRange MatchRange = ProcessMatchResult(MatchTy, SM, Loc, Pat.getCheckTy(),
Buffer, MatchPos, MatchLen, Diags);
Buffer, MatchResult.TheMatch->Pos,
MatchResult.TheMatch->Len, Diags);
if (Diags) {
Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, Diags);
Pat.printVariableDefs(SM, MatchTy, Diags);
}
if (!PrintDiag)
return;
if (!PrintDiag) {
assert(!HasError && "expected to report more diagnostics for error");
return ErrorReported::reportedOrSuccess(HasError);
}
// Print the match.
std::string Message = formatv("{0}: {1} string found in input",
Pat.getCheckTy().getDescription(Prefix),
(ExpectedMatch ? "expected" : "excluded"))
.str();
if (Pat.getCount() > 1)
Message += formatv(" ({0} out of {1})", MatchedCount, Pat.getCount()).str();
SM.PrintMessage(
Loc, ExpectedMatch ? SourceMgr::DK_Remark : SourceMgr::DK_Error, Message);
SM.PrintMessage(MatchRange.Start, SourceMgr::DK_Note, "found here",
{MatchRange});
// Print additional information, which can be useful even if there are errors.
Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, nullptr);
Pat.printVariableDefs(SM, MatchTy, nullptr);
// Print errors and add them to Diags. We report these errors after the match
// itself because we found them after the match. If we had found them before
// the match, we'd be in printNoMatch.
handleAllErrors(std::move(MatchResult.TheError),
[&](const ErrorDiagnostic &E) {
E.log(errs());
if (Diags) {
Diags->emplace_back(SM, Pat.getCheckTy(), Loc,
FileCheckDiag::MatchFoundErrorNote,
E.getRange(), E.getMessage().str());
}
});
return ErrorReported::reportedOrSuccess(HasError);
}
static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM,
const FileCheckString &CheckStr, int MatchedCount,
StringRef Buffer, size_t MatchPos, size_t MatchLen,
FileCheckRequest &Req,
std::vector<FileCheckDiag> *Diags) {
PrintMatch(ExpectedMatch, SM, CheckStr.Prefix, CheckStr.Loc, CheckStr.Pat,
MatchedCount, Buffer, MatchPos, MatchLen, Req, Diags);
}
static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM,
StringRef Prefix, SMLoc Loc, const Pattern &Pat,
int MatchedCount, StringRef Buffer,
bool VerboseVerbose, std::vector<FileCheckDiag> *Diags,
Error MatchErrors) {
assert(MatchErrors && "Called on successful match");
bool PrintDiag = true;
if (!ExpectedMatch) {
if (!VerboseVerbose) {
consumeError(std::move(MatchErrors));
return;
}
// Due to their verbosity, we don't print verbose diagnostics here if we're
// gathering them for a different rendering, but we always print other
// diagnostics.
PrintDiag = !Diags;
}
/// Returns either (1) \c ErrorSuccess if there was no error, or (2)
/// \c ErrorReported if an error was reported, such as an expected match not
/// found.
static Error printNoMatch(bool ExpectedMatch, const SourceMgr &SM,
StringRef Prefix, SMLoc Loc, const Pattern &Pat,
int MatchedCount, StringRef Buffer, Error MatchError,
bool VerboseVerbose,
std::vector<FileCheckDiag> *Diags) {
// Print any pattern errors, and record them to be added to Diags later.
bool HasError = ExpectedMatch;
bool HasPatternError = false;
FileCheckDiag::MatchType MatchTy = ExpectedMatch
? FileCheckDiag::MatchNoneButExpected
: FileCheckDiag::MatchNoneAndExcluded;
SMRange SearchRange = ProcessMatchResult(MatchTy, SM, Loc, Pat.getCheckTy(),
Buffer, 0, Buffer.size(), Diags);
if (Diags)
Pat.printSubstitutions(SM, Buffer, SearchRange, MatchTy, Diags);
if (!PrintDiag) {
consumeError(std::move(MatchErrors));
return;
SmallVector<std::string, 4> ErrorMsgs;
handleAllErrors(
std::move(MatchError),
[&](const ErrorDiagnostic &E) {
HasError = HasPatternError = true;
MatchTy = FileCheckDiag::MatchNoneForInvalidPattern;
E.log(errs());
if (Diags)
ErrorMsgs.push_back(E.getMessage().str());
},
// UndefVarError is reported in printSubstitutions below.
// FIXME: It probably should be handled as a pattern error and actually
// change the exit status to 1, even if !ExpectedMatch. To do so, we
// could stop calling printSubstitutions and actually report the error
// here as we do ErrorDiagnostic above.
[](const UndefVarError &E) {},
// NotFoundError is why printNoMatch was invoked.
[](const NotFoundError &E) {});
// Suppress some verbosity if there's no error.
bool PrintDiag = true;
if (!HasError) {
if (!VerboseVerbose)
return ErrorReported::reportedOrSuccess(HasError);
// Due to their verbosity, we don't print verbose diagnostics here if we're
// gathering them for Diags to be rendered elsewhere, but we always print
// other diagnostics.
PrintDiag = !Diags;
}
MatchErrors = handleErrors(std::move(MatchErrors),
[](const ErrorDiagnostic &E) { E.log(errs()); });
// Add "not found" diagnostic, substitutions, and pattern errors to Diags.
//
// We handle Diags a little differently than the errors we print directly:
// we add the "not found" diagnostic to Diags even if there are pattern
// errors. The reason is that we need to attach pattern errors as notes
// somewhere in the input, and the input search range from the "not found"
// diagnostic is all we have to anchor them.
SMRange SearchRange = ProcessMatchResult(MatchTy, SM, Loc, Pat.getCheckTy(),
Buffer, 0, Buffer.size(), Diags);
if (Diags) {
SMRange NoteRange = SMRange(SearchRange.Start, SearchRange.Start);
for (StringRef ErrorMsg : ErrorMsgs)
Diags->emplace_back(SM, Pat.getCheckTy(), Loc, MatchTy, NoteRange,
ErrorMsg);
Pat.printSubstitutions(SM, Buffer, SearchRange, MatchTy, Diags);
}
if (!PrintDiag) {
assert(!HasError && "expected to report more diagnostics for error");
return ErrorReported::reportedOrSuccess(HasError);
}
// No problem matching the string per se.
if (!MatchErrors)
return;
consumeError(std::move(MatchErrors));
// Print "not found" diagnostic, except that's implied if we already printed a
// pattern error.
if (!HasPatternError) {
std::string Message = formatv("{0}: {1} string not found in input",
Pat.getCheckTy().getDescription(Prefix),
(ExpectedMatch ? "expected" : "excluded"))
.str();
if (Pat.getCount() > 1)
Message +=
formatv(" ({0} out of {1})", MatchedCount, Pat.getCount()).str();
SM.PrintMessage(Loc,
ExpectedMatch ? SourceMgr::DK_Error : SourceMgr::DK_Remark,
Message);
SM.PrintMessage(SearchRange.Start, SourceMgr::DK_Note,
"scanning from here");
}
// Print "not found" diagnostic.
std::string Message = formatv("{0}: {1} string not found in input",
Pat.getCheckTy().getDescription(Prefix),
(ExpectedMatch ? "expected" : "excluded"))
.str();
if (Pat.getCount() > 1)
Message += formatv(" ({0} out of {1})", MatchedCount, Pat.getCount()).str();
SM.PrintMessage(
Loc, ExpectedMatch ? SourceMgr::DK_Error : SourceMgr::DK_Remark, Message);
// Print the "scanning from here" line.
SM.PrintMessage(SearchRange.Start, SourceMgr::DK_Note, "scanning from here");
// Allow the pattern to print additional information if desired.
// Print additional information, which can be useful even after a pattern
// error.
Pat.printSubstitutions(SM, Buffer, SearchRange, MatchTy, nullptr);
if (ExpectedMatch)
Pat.printFuzzyMatch(SM, Buffer, Diags);
return ErrorReported::reportedOrSuccess(HasError);
}
static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM,
const FileCheckString &CheckStr, int MatchedCount,
StringRef Buffer, bool VerboseVerbose,
std::vector<FileCheckDiag> *Diags, Error MatchErrors) {
PrintNoMatch(ExpectedMatch, SM, CheckStr.Prefix, CheckStr.Loc, CheckStr.Pat,
MatchedCount, Buffer, VerboseVerbose, Diags,
std::move(MatchErrors));
/// Returns either (1) \c ErrorSuccess if there was no error, or (2)
/// \c ErrorReported if an error was reported.
static Error reportMatchResult(bool ExpectedMatch, const SourceMgr &SM,
StringRef Prefix, SMLoc Loc, const Pattern &Pat,
int MatchedCount, StringRef Buffer,
Pattern::MatchResult MatchResult,
const FileCheckRequest &Req,
std::vector<FileCheckDiag> *Diags) {
if (MatchResult.TheMatch)
return printMatch(ExpectedMatch, SM, Prefix, Loc, Pat, MatchedCount, Buffer,
std::move(MatchResult), Req, Diags);
return printNoMatch(ExpectedMatch, SM, Prefix, Loc, Pat, MatchedCount, Buffer,
std::move(MatchResult.TheError), Req.VerboseVerbose,
Diags);
}
/// Counts the number of newlines in the specified range.
@ -2204,24 +2262,23 @@ size_t FileCheckString::Check(const SourceMgr &SM, StringRef Buffer,
assert(Pat.getCount() != 0 && "pattern count can not be zero");
for (int i = 1; i <= Pat.getCount(); i++) {
StringRef MatchBuffer = Buffer.substr(LastMatchEnd);
size_t CurrentMatchLen;
// get a match at current start point
Expected<size_t> MatchResult = Pat.match(MatchBuffer, CurrentMatchLen, SM);
Pattern::MatchResult MatchResult = Pat.match(MatchBuffer, SM);
// report
if (!MatchResult) {
PrintNoMatch(true, SM, *this, i, MatchBuffer, Req.VerboseVerbose, Diags,
MatchResult.takeError());
if (Error Err = reportMatchResult(/*ExpectedMatch=*/true, SM, Prefix, Loc,
Pat, i, MatchBuffer,
std::move(MatchResult), Req, Diags)) {
cantFail(handleErrors(std::move(Err), [&](const ErrorReported &E) {}));
return StringRef::npos;
}
size_t MatchPos = *MatchResult;
PrintMatch(true, SM, *this, i, MatchBuffer, MatchPos, CurrentMatchLen, Req,
Diags);
size_t MatchPos = MatchResult.TheMatch->Pos;
if (i == 1)
FirstMatchPos = LastPos + MatchPos;
// move start point after the match
LastMatchEnd += MatchPos + CurrentMatchLen;
LastMatchEnd += MatchPos + MatchResult.TheMatch->Len;
}
// Full match len counts from first match pos.
MatchLen = LastMatchEnd - FirstMatchPos;
@ -2328,22 +2385,15 @@ bool FileCheckString::CheckNot(const SourceMgr &SM, StringRef Buffer,
bool DirectiveFail = false;
for (const Pattern *Pat : NotStrings) {
assert((Pat->getCheckTy() == Check::CheckNot) && "Expect CHECK-NOT!");
size_t MatchLen = 0;
Expected<size_t> MatchResult = Pat->match(Buffer, MatchLen, SM);
if (!MatchResult) {
PrintNoMatch(false, SM, Prefix, Pat->getLoc(), *Pat, 1, Buffer,
Req.VerboseVerbose, Diags, MatchResult.takeError());
Pattern::MatchResult MatchResult = Pat->match(Buffer, SM);
if (Error Err = reportMatchResult(/*ExpectedMatch=*/false, SM, Prefix,
Pat->getLoc(), *Pat, 1, Buffer,
std::move(MatchResult), Req, Diags)) {
cantFail(handleErrors(std::move(Err), [&](const ErrorReported &E) {}));
DirectiveFail = true;
continue;
}
size_t Pos = *MatchResult;
PrintMatch(false, SM, Prefix, Pat->getLoc(), *Pat, 1, Buffer, Pos, MatchLen,
Req, Diags);
DirectiveFail = true;
}
return DirectiveFail;
}
@ -2389,20 +2439,22 @@ size_t FileCheckString::CheckDag(const SourceMgr &SM, StringRef Buffer,
// CHECK-DAG group.
for (auto MI = MatchRanges.begin(), ME = MatchRanges.end(); true; ++MI) {
StringRef MatchBuffer = Buffer.substr(MatchPos);
Expected<size_t> MatchResult = Pat.match(MatchBuffer, MatchLen, SM);
Pattern::MatchResult MatchResult = Pat.match(MatchBuffer, SM);
// With a group of CHECK-DAGs, a single mismatching means the match on
// that group of CHECK-DAGs fails immediately.
if (!MatchResult) {
PrintNoMatch(true, SM, Prefix, Pat.getLoc(), Pat, 1, MatchBuffer,
Req.VerboseVerbose, Diags, MatchResult.takeError());
return StringRef::npos;
if (MatchResult.TheError || Req.VerboseVerbose) {
if (Error Err = reportMatchResult(/*ExpectedMatch=*/true, SM, Prefix,
Pat.getLoc(), Pat, 1, MatchBuffer,
std::move(MatchResult), Req, Diags)) {
cantFail(
handleErrors(std::move(Err), [&](const ErrorReported &E) {}));
return StringRef::npos;
}
}
size_t MatchPosBuf = *MatchResult;
// Re-calc it as the offset relative to the start of the original string.
MatchPos += MatchPosBuf;
if (Req.VerboseVerbose)
PrintMatch(true, SM, Prefix, Pat.getLoc(), Pat, 1, Buffer, MatchPos,
MatchLen, Req, Diags);
MatchLen = MatchResult.TheMatch->Len;
// Re-calc it as the offset relative to the start of the original
// string.
MatchPos += MatchResult.TheMatch->Pos;
MatchRange M{MatchPos, MatchPos + MatchLen};
if (Req.AllowDeprecatedDagOverlap) {
// We don't need to track all matches in this mode, so we just maintain
@ -2453,8 +2505,10 @@ size_t FileCheckString::CheckDag(const SourceMgr &SM, StringRef Buffer,
MatchPos = MI->End;
}
if (!Req.VerboseVerbose)
PrintMatch(true, SM, Prefix, Pat.getLoc(), Pat, 1, Buffer, MatchPos,
MatchLen, Req, Diags);
cantFail(printMatch(
/*ExpectedMatch=*/true, SM, Prefix, Pat.getLoc(), Pat, 1, Buffer,
Pattern::MatchResult(MatchPos, MatchLen, Error::success()), Req,
Diags));
// Handle the end of a CHECK-DAG group.
if (std::next(PatItr) == PatEnd ||

View File

@ -537,11 +537,13 @@ private:
class ErrorDiagnostic : public ErrorInfo<ErrorDiagnostic> {
private:
SMDiagnostic Diagnostic;
SMRange Range;
public:
static char ID;
ErrorDiagnostic(SMDiagnostic &&Diag) : Diagnostic(Diag) {}
ErrorDiagnostic(SMDiagnostic &&Diag, SMRange Range)
: Diagnostic(Diag), Range(Range) {}
std::error_code convertToErrorCode() const override {
return inconvertibleErrorCode();
@ -550,13 +552,19 @@ public:
/// Print diagnostic associated with this error when printing the error.
void log(raw_ostream &OS) const override { Diagnostic.print(nullptr, OS); }
static Error get(const SourceMgr &SM, SMLoc Loc, const Twine &ErrMsg) {
StringRef getMessage() const { return Diagnostic.getMessage(); }
SMRange getRange() const { return Range; }
static Error get(const SourceMgr &SM, SMLoc Loc, const Twine &ErrMsg,
SMRange Range = None) {
return make_error<ErrorDiagnostic>(
SM.GetMessage(Loc, SourceMgr::DK_Error, ErrMsg));
SM.GetMessage(Loc, SourceMgr::DK_Error, ErrMsg), Range);
}
static Error get(const SourceMgr &SM, StringRef Buffer, const Twine &ErrMsg) {
return get(SM, SMLoc::getFromPointer(Buffer.data()), ErrMsg);
SMLoc Start = SMLoc::getFromPointer(Buffer.data());
SMLoc End = SMLoc::getFromPointer(Buffer.data() + Buffer.size());
return get(SM, Start, ErrMsg, SMRange(Start, End));
}
};
@ -574,6 +582,36 @@ public:
}
};
/// An error that has already been reported.
///
/// This class is designed to support a function whose callers may need to know
/// whether the function encountered and reported an error but never need to
/// know the nature of that error. For example, the function has a return type
/// of \c Error and always returns either \c ErrorReported or \c ErrorSuccess.
/// That interface is similar to that of a function returning bool to indicate
/// an error except, in the former case, (1) there is no confusion over polarity
/// and (2) the caller must either check the result or explicitly ignore it with
/// a call like \c consumeError.
class ErrorReported final : public ErrorInfo<ErrorReported> {
public:
static char ID;
std::error_code convertToErrorCode() const override {
return inconvertibleErrorCode();
}
/// Print diagnostic associated with this error when printing the error.
void log(raw_ostream &OS) const override {
OS << "error previously reported";
}
static inline Error reportedOrSuccess(bool HasErrorReported) {
if (HasErrorReported)
return make_error<ErrorReported>();
return Error::success();
}
};
class Pattern {
SMLoc PatternLoc;
@ -694,11 +732,22 @@ public:
/// \returns true in case of an error, false otherwise.
bool parsePattern(StringRef PatternStr, StringRef Prefix, SourceMgr &SM,
const FileCheckRequest &Req);
/// Matches the pattern string against the input buffer \p Buffer
struct Match {
size_t Pos;
size_t Len;
};
struct MatchResult {
Optional<Match> TheMatch;
Error TheError;
MatchResult(size_t MatchPos, size_t MatchLen, Error E)
: TheMatch(Match{MatchPos, MatchLen}), TheError(std::move(E)) {}
MatchResult(Match M, Error E) : TheMatch(M), TheError(std::move(E)) {}
MatchResult(Error E) : TheError(std::move(E)) {}
};
/// Matches the pattern string against the input buffer \p Buffer.
///
/// \returns the position that is matched or an error indicating why matching
/// failed. If there is a match, updates \p MatchLen with the size of the
/// matched string.
/// \returns either (1) an error resulting in no match or (2) a match possibly
/// with an error encountered while processing the match.
///
/// The GlobalVariableTable StringMap in the FileCheckPatternContext class
/// instance provides the current values of FileCheck string variables and is
@ -706,8 +755,7 @@ public:
/// GlobalNumericVariableTable StringMap in the same class provides the
/// current values of FileCheck numeric variables and is updated if this
/// match defines new numeric values.
Expected<size_t> match(StringRef Buffer, size_t &MatchLen,
const SourceMgr &SM) const;
MatchResult match(StringRef Buffer, const SourceMgr &SM) const;
/// Prints the value of successful substitutions or the name of the undefined
/// string or numeric variables preventing a successful substitution.
void printSubstitutions(const SourceMgr &SM, StringRef Buffer,

View File

@ -0,0 +1,75 @@
; Check handling of match-time diagnostics for invalid patterns (e.g.,
; substitution overflow) in the case of excluded patterns (e.g., CHECK-NOT).
;
; At one time, FileCheck's exit status was zero for this case. Moreover, it
; printed the error diagnostic only if -vv was specified and input dumps were
; disabled. Test every combination as the logic is hard to get right.
;
; FIXME: We shouldn't have: (1) the blank note at the end of the trace, and
; (2) the redundant error in the middle of the dump. These will be fixed in a
; subsequent patch.
RUN: echo > %t.chk \
RUN: 'CHECK-NOT: [[#0x8000000000000000+0x8000000000000000]] [[UNDEFVAR]]'
RUN: echo > %t.in '10000000000000000'
ERR-NOT:{{.}}
ERR-VV:{{.*}}: remark: implicit EOF: expected string found in input
ERR-VV-NEXT:CHECK-NOT: {{.*}}
ERR-VV-NEXT:{{ *}}^
ERR-VV-NEXT:{{.*}}: note: found here
ERR-VV-EMPTY:
ERR-VV-NEXT:^
ERR-NOT:{{.}}
ERR:{{.*}}: error: unable to substitute variable or numeric expression: overflow error
ERR-NEXT:CHECK-NOT: {{.*}}
ERR-NEXT:{{ *}}^
ERR-NEXT:{{.*}}: note:
ERR-NEXT:10000000000000000
ERR-NEXT:^
ERR-NEXT:<stdin>:1:1: note: uses undefined variable(s): "UNDEFVAR"
ERR-NEXT:10000000000000000
ERR-NEXT:^
ERR-NOT:{{error|note|remark}}:
DUMP:<<<<<<
DUMP-NEXT: 1: 10000000000000000
DUMP-NEXT:not:1'0 X~~~~~~~~~~~~~~~~~ error: match failed for invalid pattern
DUMP-NEXT:not:1'1 unable to substitute variable or numeric expression: overflow error
DUMP-NEXT:not:1'2 X error: match failed for invalid pattern
DUMP-NEXT:not:1'3 uses undefined variable(s): "UNDEFVAR"
DUMP-VV-NEXT: 2:
DUMP-VV-NEXT:eof:1 ^
DUMP-NEXT:>>>>>>
;--------------------------------------------------
; Check -dump-input=never cases.
;--------------------------------------------------
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=never %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=never -v %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=never -vv %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR,ERR-VV
;--------------------------------------------------
; Check -dump-input=fail cases.
;--------------------------------------------------
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=fail %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR,DUMP
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=fail -v %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR,DUMP
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=fail -vv %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR,DUMP,DUMP-VV

View File

@ -0,0 +1,62 @@
; Check handling of match-time diagnostics for invalid patterns (e.g.,
; substitution overflow) in the case of expected patterns (e.g., CHECK).
;
; FIXME: We shouldn't have: (1) the blank note at the end of the trace, and
; (2) the redundant error in the middle of the dump. These will be fixed in a
; subsequent patch.
RUN: echo > %t.chk \
RUN: 'CHECK: [[#0x8000000000000000+0x8000000000000000]] [[UNDEFVAR]]'
RUN: echo > %t.in '10000000000000000'
ERR-NOT:{{.}}
ERR:{{.*}}: error: unable to substitute variable or numeric expression: overflow error
ERR-NEXT:CHECK: {{.*}}
ERR-NEXT:{{ *}}^
ERR-NEXT:{{.*}}: note:
ERR-NEXT:10000000000000000
ERR-NEXT:^
ERR-NEXT:<stdin>:1:1: note: uses undefined variable(s): "UNDEFVAR"
ERR-NEXT:10000000000000000
ERR-NEXT:^
ERR-NOT:{{error|note|remark}}:
DUMP:<<<<<<
DUMP-NEXT: 1: 10000000000000000
DUMP-NEXT:check:1'0 X~~~~~~~~~~~~~~~~~ error: match failed for invalid pattern
DUMP-NEXT:check:1'1 unable to substitute variable or numeric expression: overflow error
DUMP-NEXT:check:1'2 X error: match failed for invalid pattern
DUMP-NEXT:check:1'3 uses undefined variable(s): "UNDEFVAR"
DUMP-NEXT:>>>>>>
;--------------------------------------------------
; Check -dump-input=never cases.
;--------------------------------------------------
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=never %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=never -v %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=never -vv %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR
;--------------------------------------------------
; Check -dump-input=fail cases.
;--------------------------------------------------
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=fail %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR,DUMP
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=fail -v %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR,DUMP
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=fail -vv %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR,DUMP

View File

@ -0,0 +1,88 @@
; Check handling of diagnostics for problematic matches (e.g., variable capture
; overflow) in the case of excluded patterns (e.g., CHECK-NOT).
;
; At one time, FileCheck's exit status for the following example was zero even
; though the excluded pattern does match (it's just capturing that fails).
; Moreover, it printed the error diagnostic only if -vv was specified and input
; dumps were disabled. Test every combination as the logic is hard to get
; right.
;
; TODO: Capturing from an excluded pattern probably shouldn't be permitted
; because it seems useless: it's captured only if the pattern matches, but then
; FileCheck fails. The helpfulness of reporting overflow from that capture is
; perhaps questionable then, but it doesn't seem harmful either. Anyway, the
; goal of this test is simply to exercise the error propagation mechanism for a
; matched excluded pattern. In the future, if we have a more interesting error
; to exercise in that case, we should instead use it in this test, and then we
; might want to think more about where that error should be presented in the
; list of diagnostics.
RUN: echo > %t.chk 'CHECK-NOT: [[#122+1]] [[STR:abc]] [[#NUM:]]'
RUN: echo > %t.in '123 abc 1000000000000000000000000000000000000000000000000000'
ERR-NOT:{{.}}
ERR-VV:{{.*}}: remark: implicit EOF: expected string found in input
ERR-VV-NEXT:CHECK-NOT: {{.*}}
ERR-VV-NEXT:{{ *}}^
ERR-VV-NEXT:{{.*}}: note: found here
ERR-VV-EMPTY:
ERR-VV-NEXT:^
ERR-NOT:{{.}}
ERR:{{.*}}: error: CHECK-NOT: excluded string found in input
ERR-NEXT:CHECK-NOT: {{.*}}
ERR-NEXT:{{ *}}^
ERR-NEXT:<stdin>:1:1: note: found here
ERR-NEXT:123 abc 10{{0*}}
ERR-NEXT:^~~~~~~~~{{~*}}
ERR-NEXT:<stdin>:1:1: note: with "122+1" equal to "123"
ERR-NEXT:123 abc 10{{0*}}
ERR-NEXT:^
ERR-NEXT:<stdin>:1:5: note: captured var "STR"
ERR-NEXT:123 abc 10{{0*}}
ERR-NEXT: ^~~
ERR-NEXT:<stdin>:1:9: error: unable to represent numeric value
ERR-NEXT:123 abc 10{{0*}}
ERR-NEXT: ^
ERR-NOT:{{error|note|remark}}:
DUMP:<<<<<<
DUMP-NEXT: 1: 123 abc 10{{0*}}
DUMP-NEXT:not:1'0 !~~~~~~~~~{{~*}} error: no match expected
DUMP-NEXT:not:1'1 with "122+1" equal to "123"
DUMP-NEXT:not:1'2 !~~ captured var "STR"
DUMP-NEXT:not:1'3 !~{{~*}} error: unable to represent numeric value
DUMP-VV-NEXT: 2:
DUMP-VV-NEXT:eof:1 ^
DUMP-NEXT:>>>>>>
;--------------------------------------------------
; Check -dump-input=never cases.
;--------------------------------------------------
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=never %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=never -v %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=never -vv %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR,ERR-VV
;--------------------------------------------------
; Check -dump-input=fail cases.
;--------------------------------------------------
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=fail %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR,DUMP
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=fail -v %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR,DUMP
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=fail -vv %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR,DUMP,DUMP-VV

View File

@ -0,0 +1,63 @@
; Check handling of diagnostics for problematic matches (e.g., variable capture
; overflow) in the case of expected patterns (e.g., CHECK).
RUN: echo > %t.chk 'CHECK: [[#122+1]] [[STR:abc]] [[#NUM:]]'
RUN: echo > %t.in '123 abc 1000000000000000000000000000000000000000000000000000'
ERR-NOT:{{.}}
ERR:{{.*}}: remark: CHECK: expected string found in input
ERR-NEXT:CHECK: {{.*}}
ERR-NEXT:{{ *}}^
ERR-NEXT:<stdin>:1:1: note: found here
ERR-NEXT:123 abc 10{{0*}}
ERR-NEXT:^~~~~~~~~{{~*}}
ERR-NEXT:<stdin>:1:1: note: with "122+1" equal to "123"
ERR-NEXT:123 abc 10{{0*}}
ERR-NEXT:^
ERR-NEXT:<stdin>:1:5: note: captured var "STR"
ERR-NEXT:123 abc 10{{0*}}
ERR-NEXT: ^~~
ERR-NEXT:<stdin>:1:9: error: unable to represent numeric value
ERR-NEXT:123 abc 10{{0*}}
ERR-NEXT: ^
ERR-NOT:{{error|note|remark}}:
DUMP:<<<<<<
DUMP-NEXT: 1: 123 abc 10{{0*}}
DUMP-NEXT:check:1'0 ^~~~~~~~~~{{~*}}
DUMP-NEXT:check:1'1 with "122+1" equal to "123"
DUMP-NEXT:check:1'2 ^~~ captured var "STR"
DUMP-NEXT:check:1'3 !~{{~*}} error: unable to represent numeric value
DUMP-NEXT:>>>>>>
;--------------------------------------------------
; Check -dump-input=never cases.
;--------------------------------------------------
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=never %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=never -v %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=never -vv %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR
;--------------------------------------------------
; Check -dump-input=fail cases.
;--------------------------------------------------
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=fail %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR,DUMP
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=fail -v %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR,DUMP
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck -dump-input=fail -vv %t.chk < %t.in 2>&1 \
RUN: | FileCheck %s -match-full-lines -check-prefixes=ERR,DUMP

View File

@ -1027,8 +1027,10 @@ public:
Expected<size_t> match(StringRef Buffer) {
StringRef BufferRef = bufferize(SM, Buffer);
size_t MatchLen;
return P.match(BufferRef, MatchLen, SM);
Pattern::MatchResult Res = P.match(BufferRef, SM);
if (Res.TheError)
return std::move(Res.TheError);
return Res.TheMatch->Pos;
}
void printVariableDefs(FileCheckDiag::MatchType MatchTy,
@ -1640,8 +1642,8 @@ TEST_F(FileCheckTest, FileCheckContext) {
FileCheckRequest Req;
Cxt.createLineVariable();
ASSERT_FALSE(P.parsePattern("[[@LINE]]", "CHECK", SM, Req));
size_t MatchLen;
ASSERT_THAT_EXPECTED(P.match("1", MatchLen, SM), Succeeded());
Pattern::MatchResult Res = P.match("1", SM);
ASSERT_THAT_ERROR(std::move(Res.TheError), Succeeded());
#ifndef NDEBUG
// Recreating @LINE pseudo numeric variable fails.

View File

@ -212,11 +212,20 @@ static MarkerStyle GetMarker(FileCheckDiag::MatchType MatchTy) {
case FileCheckDiag::MatchFoundButDiscarded:
return MarkerStyle('!', raw_ostream::CYAN,
"discard: overlaps earlier match");
case FileCheckDiag::MatchFoundErrorNote:
// Note should always be overridden within the FileCheckDiag.
return MarkerStyle('!', raw_ostream::RED,
"error: unknown error after match",
/*FiltersAsError=*/true);
case FileCheckDiag::MatchNoneAndExcluded:
return MarkerStyle('X', raw_ostream::GREEN);
case FileCheckDiag::MatchNoneButExpected:
return MarkerStyle('X', raw_ostream::RED, "error: no match found",
/*FiltersAsError=*/true);
case FileCheckDiag::MatchNoneForInvalidPattern:
return MarkerStyle('X', raw_ostream::RED,
"error: match failed for invalid pattern",
/*FiltersAsError=*/true);
case FileCheckDiag::MatchFuzzy:
return MarkerStyle('?', raw_ostream::MAGENTA, "possible intended match",
/*FiltersAsError=*/true);
@ -421,6 +430,11 @@ BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID,
DiagItr->InputStartCol == DiagItr->InputEndCol)
A.Marker.Lead = ' ';
}
if (DiagItr->MatchTy == FileCheckDiag::MatchFoundErrorNote) {
assert(!DiagItr->Note.empty() &&
"expected custom note for MatchFoundErrorNote");
A.Marker.Note = "error: " + A.Marker.Note;
}
A.FoundAndExpectedMatch =
DiagItr->MatchTy == FileCheckDiag::MatchFoundAndExpected;