diff --git a/include/llvm/FileCheck/FileCheck.h b/include/llvm/FileCheck/FileCheck.h index b44ab025694..6ed75e14ccb 100644 --- a/include/llvm/FileCheck/FileCheck.h +++ b/include/llvm/FileCheck/FileCheck.h @@ -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; diff --git a/lib/FileCheck/FileCheck.cpp b/lib/FileCheck/FileCheck.cpp index 462f5078866..2b596fdf1c5 100644 --- a/lib/FileCheck/FileCheck.cpp +++ b/lib/FileCheck/FileCheck.cpp @@ -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 Pattern::parseNumericVariableDefinition( StringRef &Expr, FileCheckPatternContext *Context, @@ -1212,22 +1213,19 @@ void Pattern::AddBackrefToRegEx(unsigned BackrefNum) { RegExStr += Backref; } -Expected 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(); - return Pos; + return MatchResult(Pos, /*MatchLen=*/FixedStr.size(), Error::success()); } // Regex match. @@ -1250,7 +1248,7 @@ Expected Pattern::match(StringRef Buffer, size_t &MatchLen, Expected 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 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 Pattern::match(StringRef Buffer, size_t &MatchLen, Expected 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 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 *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 *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 *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 *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 *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 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 *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 *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 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 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 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 || diff --git a/lib/FileCheck/FileCheckImpl.h b/lib/FileCheck/FileCheckImpl.h index 1966b0e7831..d4d891f7c1f 100644 --- a/lib/FileCheck/FileCheckImpl.h +++ b/lib/FileCheck/FileCheckImpl.h @@ -537,11 +537,13 @@ private: class ErrorDiagnostic : public ErrorInfo { 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( - 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 { +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(); + 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 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 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, diff --git a/test/FileCheck/match-time-error-propagation/invalid-excluded-pattern.txt b/test/FileCheck/match-time-error-propagation/invalid-excluded-pattern.txt new file mode 100644 index 00000000000..7150ae78907 --- /dev/null +++ b/test/FileCheck/match-time-error-propagation/invalid-excluded-pattern.txt @@ -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::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 diff --git a/test/FileCheck/match-time-error-propagation/invalid-expected-pattern.txt b/test/FileCheck/match-time-error-propagation/invalid-expected-pattern.txt new file mode 100644 index 00000000000..c066c625019 --- /dev/null +++ b/test/FileCheck/match-time-error-propagation/invalid-expected-pattern.txt @@ -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::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 diff --git a/test/FileCheck/match-time-error-propagation/matched-excluded-pattern.txt b/test/FileCheck/match-time-error-propagation/matched-excluded-pattern.txt new file mode 100644 index 00000000000..32ffbf2e8f2 --- /dev/null +++ b/test/FileCheck/match-time-error-propagation/matched-excluded-pattern.txt @@ -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::1:1: note: found here + ERR-NEXT:123 abc 10{{0*}} + ERR-NEXT:^~~~~~~~~{{~*}} + ERR-NEXT::1:1: note: with "122+1" equal to "123" + ERR-NEXT:123 abc 10{{0*}} + ERR-NEXT:^ + ERR-NEXT::1:5: note: captured var "STR" + ERR-NEXT:123 abc 10{{0*}} + ERR-NEXT: ^~~ + ERR-NEXT::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 diff --git a/test/FileCheck/match-time-error-propagation/matched-expected-pattern.txt b/test/FileCheck/match-time-error-propagation/matched-expected-pattern.txt new file mode 100644 index 00000000000..d376f73bfdc --- /dev/null +++ b/test/FileCheck/match-time-error-propagation/matched-expected-pattern.txt @@ -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::1:1: note: found here + ERR-NEXT:123 abc 10{{0*}} + ERR-NEXT:^~~~~~~~~{{~*}} + ERR-NEXT::1:1: note: with "122+1" equal to "123" + ERR-NEXT:123 abc 10{{0*}} + ERR-NEXT:^ + ERR-NEXT::1:5: note: captured var "STR" + ERR-NEXT:123 abc 10{{0*}} + ERR-NEXT: ^~~ + ERR-NEXT::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 diff --git a/unittests/FileCheck/FileCheckTest.cpp b/unittests/FileCheck/FileCheckTest.cpp index 3ed51433943..299fbb8a798 100644 --- a/unittests/FileCheck/FileCheckTest.cpp +++ b/unittests/FileCheck/FileCheckTest.cpp @@ -1027,8 +1027,10 @@ public: Expected 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. diff --git a/utils/FileCheck/FileCheck.cpp b/utils/FileCheck/FileCheck.cpp index ebee55b3599..668dd984440 100644 --- a/utils/FileCheck/FileCheck.cpp +++ b/utils/FileCheck/FileCheck.cpp @@ -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;