diff --git a/llvm/include/llvm/Support/FileCheck.h b/llvm/include/llvm/Support/FileCheck.h --- a/llvm/include/llvm/Support/FileCheck.h +++ b/llvm/include/llvm/Support/FileCheck.h @@ -103,6 +103,8 @@ /// (MatchFoundAndExpected) or a failure to match (MatchNoneButExpected), /// and there might be a fuzzy match (MatchFuzzy) after the latter. enum MatchType { + /// Indicates an error during match. + MatchError, /// Indicates a good match for an expected pattern. MatchFoundAndExpected, /// Indicates a match for an excluded pattern. diff --git a/llvm/lib/Support/FileCheck.cpp b/llvm/lib/Support/FileCheck.cpp --- a/llvm/lib/Support/FileCheck.cpp +++ b/llvm/lib/Support/FileCheck.cpp @@ -1210,20 +1210,22 @@ // Substitute all string variables and expressions whose values are only // now known. Use of string variables defined on the same line are handled // by back-references. + Error Errs = Error::success(); for (const auto &Substitution : Substitutions) { // Substitute and check for failure (e.g. use of undefined variable). 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 printNoMatch since now we know which // substitution block caused the overflow. - Error Err = + Errs = joinErrors( + std::move(Errs), handleErrors(Value.takeError(), [&](const OverflowError &E) { return ErrorDiagnostic::get(SM, Substitution->getFromString(), "unable to substitute variable or " "numeric expression: overflow error"); - }); - return std::move(Err); + })); + continue; } // Plop it into the regex at the adjusted offset. @@ -1231,6 +1233,8 @@ Value->begin(), Value->end()); InsertOffset += Value->size(); } + if (Errs) + return std::move(Errs); // Match the newly constructed regex. RegExToMatch = TmpStr; @@ -1306,32 +1310,13 @@ for (const auto &Substitution : Substitutions) { SmallString<256> Msg; raw_svector_ostream OS(Msg); - Expected MatchedValue = Substitution->getResult(); - - // Substitution failed or is not known at match time, print the undefined - // variables it uses. - if (!MatchedValue) { - bool UndefSeen = false; - handleAllErrors( - MatchedValue.takeError(), [](const NotFoundError &E) {}, - // Handled in PrintNoMatch(). - [](const ErrorDiagnostic &E) {}, - // Handled in match(). - [](const OverflowError &E) {}, - [&](const UndefVarError &E) { - if (!UndefSeen) { - OS << "uses undefined variable(s):"; - UndefSeen = true; - } - OS << " "; - E.log(OS); - }); - } else { - // Substitution succeeded. Print substituted value. - OS << "with \""; - OS.write_escaped(Substitution->getFromString()) << "\" equal to \""; - OS.write_escaped(*MatchedValue) << "\""; - } + + std::string MatchedValue = + cantFail(Substitution->getResult(), + "printSubstitutions called with substitution failure"); + OS << "with \""; + OS.write_escaped(Substitution->getFromString()) << "\" equal to \""; + OS.write_escaped(MatchedValue) << "\""; // We report only the start of the match/search range to suggest we are // reporting the substitutions as set at the start of the match/search. @@ -2007,18 +1992,16 @@ MatchedCount, Buffer, MatchPos, MatchLen, Req, Diags); } -static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM, +/// Print and record diagnostics about the failure to match the input. +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 VerboseVerbose, + std::vector *Diags) { bool PrintDiag = true; if (!ExpectedMatch) { - if (!VerboseVerbose) { - consumeError(std::move(MatchErrors)); + if (!VerboseVerbose) 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. @@ -2035,18 +2018,8 @@ Buffer, 0, Buffer.size(), Diags); if (Diags) Pat.printSubstitutions(SM, Buffer, SearchRange, MatchTy, Diags); - if (!PrintDiag) { - consumeError(std::move(MatchErrors)); - return; - } - - MatchErrors = handleErrors(std::move(MatchErrors), - [](const ErrorDiagnostic &E) { E.log(errs()); }); - - // No problem matching the string per se. - if (!MatchErrors) + if (!PrintDiag) return; - consumeError(std::move(MatchErrors)); // Print "not found" diagnostic. std::string Message = formatv("{0}: {1} string not found in input", @@ -2068,13 +2041,84 @@ Pat.printFuzzyMatch(SM, Buffer, Diags); } -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)); +/// Handle matching failure(s) \p MatchFailures and \returns if they were in +/// fact errors during match. In both cases, it prints and records diagnostics +/// in \p Diags, if non null, for the failures when matching \p MatchCount +/// times \pBuffer against the pattern \p Pat at location \p Loc. The level of +/// diagnostics is controled by \p VerboseVerbose. +static bool handleMatchFailures(bool ExpectedMatch, const SourceMgr &SM, + StringRef Prefix, SMLoc Loc, const Pattern &Pat, + int MatchCount, StringRef Buffer, + Error MatchFailures, bool VerboseVerbose, + std::vector *Diags) { + Error MatchErrors = + handleErrors(std::move(MatchFailures), [](const NotFoundError &E) {}); + if (!MatchErrors) { + printNoMatch(ExpectedMatch, SM, Prefix, Loc, Pat, MatchCount, Buffer, + VerboseVerbose, Diags); + return false; + } + + // If the current position is at the end of a line, advance to the start of + // the next line. + Buffer = Buffer.substr(Buffer.find_first_not_of(" \t\n\r")); + FileCheckDiag::MatchType MatchTy = FileCheckDiag::MatchError; + Check::FileCheckType CheckTy = Pat.getCheckTy(); + ProcessMatchResult(MatchTy, SM, Loc, CheckTy, Buffer, 0, Buffer.size(), + Diags); + + auto PrintMatchErrorDiagnostic = [&](StringRef DiagMsg, SMLoc DiagLoc) { + // We report only the start of the match/search range to suggest we are + // reporting the substitutions as set at the start of the match/search. + // Indicating a non-zero-length range might instead seem to imply that the + // substitution matches or was captured from exactly that range. + if (Diags) + Diags->emplace_back(SM, CheckTy, Loc, MatchTy, SMRange(DiagLoc, DiagLoc), + DiagMsg); + SM.PrintMessage(DiagLoc, SourceMgr::DK_Error, DiagMsg); + }; + + // Print undefined variable diagnostics. + MatchErrors = + handleErrors(std::move(MatchErrors), [&](const UndefVarError &E) { + SmallString<256> Msg; + raw_svector_ostream OS(Msg); + OS << "uses undefined variable: "; + E.log(OS); + PrintMatchErrorDiagnostic(OS.str(), + SMLoc::getFromPointer(E.getVarName().data())); + }); + + // Print errors with diagnostic. No other error should exist at this stage. + handleAllErrors(std::move(MatchErrors), + [&](const UndefVarError &E) { + SmallString<256> Msg; + raw_svector_ostream OS(Msg); + OS << "uses undefined variable: "; + E.log(OS); + PrintMatchErrorDiagnostic( + OS.str(), SMLoc::getFromPointer(E.getVarName().data())); + }, + [&](const ErrorDiagnostic &E) { + const SMDiagnostic &Diagnostic = E.getDiagnostic(); + PrintMatchErrorDiagnostic(Diagnostic.getMessage(), + Diagnostic.getLoc()); + }); + + return true; +} + +/// Wrapper around the other handleMatchFailures function where the pattern, +/// its count and location are set from the positive matching pattern referred +/// to by \p CheckStr. +static bool handleMatchFailures(bool ExpectedMatch, const SourceMgr &SM, + const FileCheckString &CheckStr, + int MatchedCount, StringRef Buffer, + Error MatchFailures, bool VerboseVerbose, + std::vector *Diags) { + return handleMatchFailures(ExpectedMatch, SM, CheckStr.Prefix, CheckStr.Loc, + CheckStr.Pat, MatchedCount, Buffer, + std::move(MatchFailures), VerboseVerbose, Diags); } /// Counts the number of newlines in the specified range. @@ -2132,8 +2176,8 @@ // report if (!MatchResult) { - PrintNoMatch(true, SM, *this, i, MatchBuffer, Req.VerboseVerbose, Diags, - MatchResult.takeError()); + handleMatchFailures(/*ExpectedMatch=*/true, SM, *this, i, MatchBuffer, + MatchResult.takeError(), Req.VerboseVerbose, Diags); return StringRef::npos; } size_t MatchPos = *MatchResult; @@ -2255,8 +2299,10 @@ Expected MatchResult = Pat->match(Buffer, MatchLen, SM); if (!MatchResult) { - PrintNoMatch(false, SM, Prefix, Pat->getLoc(), *Pat, 1, Buffer, - Req.VerboseVerbose, Diags, MatchResult.takeError()); + if (handleMatchFailures( + /*ExpectedMatch=*/false, SM, Prefix, Pat->getLoc(), *Pat, 1, + Buffer, MatchResult.takeError(), Req.VerboseVerbose, Diags)) + DirectiveFail = true; continue; } size_t Pos = *MatchResult; @@ -2316,8 +2362,9 @@ // 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()); + handleMatchFailures(/*ExpectedMatch=*/true, SM, Prefix, Pat.getLoc(), + Pat, 1, MatchBuffer, MatchResult.takeError(), + Req.VerboseVerbose, Diags); return StringRef::npos; } size_t MatchPosBuf = *MatchResult; diff --git a/llvm/lib/Support/FileCheckImpl.h b/llvm/lib/Support/FileCheckImpl.h --- a/llvm/lib/Support/FileCheckImpl.h +++ b/llvm/lib/Support/FileCheckImpl.h @@ -543,6 +543,8 @@ return inconvertibleErrorCode(); } + const SMDiagnostic &getDiagnostic() const { return Diagnostic; } + /// Print diagnostic associated with this error when printing the error. void log(raw_ostream &OS) const override { Diagnostic.print(nullptr, OS); } diff --git a/llvm/test/FileCheck/dump-input-annotations.txt b/llvm/test/FileCheck/dump-input-annotations.txt --- a/llvm/test/FileCheck/dump-input-annotations.txt +++ b/llvm/test/FileCheck/dump-input-annotations.txt @@ -622,11 +622,8 @@ ; SUBST-POS-NEXT:check:1'1 with "DEF_MATCH1" equal to "def-match1" ; SUBST-POS-NEXT:check:1'2 with "DEF_MATCH2" equal to "def-match2" ; SUBST-POS-NEXT: 2: def-match1 def-nomatch -; SUBST-POS-NEXT:check:2'0 X~~~~~~~~~~~~~~~~~~~~~ error: no match found -; SUBST-POS-NEXT:check:2'1 with "DEF_MATCH1" equal to "def-match1" -; SUBST-POS-NEXT:check:2'2 uses undefined variable(s): "UNDEF" -; SUBST-POS-NEXT:check:2'3 with "DEF_NOMATCH" equal to "foobar" -; SUBST-POS-NEXT:check:2'4 ? possible intended match +; SUBST-POS-NEXT:check:2'0 X~~~~~~~~~~~~~~~~~~~~~ error: match error +; SUBST-POS-NEXT:check:2'1 uses undefined variable: "UNDEF" ; SUBST-POS-NEXT:>>>>>> ;-------------------------------------------------- @@ -654,10 +651,8 @@ ; SUBST-NEG:<<<<<< ; SUBST-NEG-NEXT: 1: def-match1 def-nomatch -; SUBST-NEG-NEXT:not:1'0 X~~~~~~~~~~~~~~~~~~~~~ -; SUBST-NEG-NEXT:not:1'1 with "DEF_MATCH1" equal to "def-match1" -; SUBST-NEG-NEXT:not:1'2 uses undefined variable(s): "UNDEF" -; SUBST-NEG-NEXT:not:1'3 with "DEF_NOMATCH" equal to "foobar" +; SUBST-NEG-NEXT:not:1'0 X~~~~~~~~~~~~~~~~~~~~~ error: match error +; SUBST-NEG-NEXT:not:1'1 uses undefined variable: "UNDEF" ; SUBST-NEG-NEXT: 2: def-match1 def-match2 ; SUBST-NEG-NEXT:not:1'0 ~~~~~~~~~~~~~~~~~~~~~ ; SUBST-NEG-NEXT:not:2'0 !~~~~~~~~~~~~~~~~~~~~ error: no match expected diff --git a/llvm/test/FileCheck/numeric-expression.txt b/llvm/test/FileCheck/numeric-expression.txt --- a/llvm/test/FileCheck/numeric-expression.txt +++ b/llvm/test/FileCheck/numeric-expression.txt @@ -363,15 +363,29 @@ UNDEFVAR: 11 UNDEF-USE-LABEL: UNDEF VAR USE UNDEF-USE-NEXT: UNDEFVAR: [[#UNDEFVAR1+UNDEFVAR2]] -UNDEF-USE-MSG: numeric-expression.txt:[[#@LINE-1]]:17: error: {{U}}NDEF-USE-NEXT: expected string not found in input -UNDEF-USE-MSG-NEXT: {{U}}NDEF-USE-NEXT: UNDEFVAR: {{\[\[#UNDEFVAR1\+UNDEFVAR2\]\]}} -UNDEF-USE-MSG-NEXT: {{^}} ^{{$}} -UNDEF-USE-MSG-NEXT: numeric-expression.txt:[[#@LINE-6]]:1: note: scanning from here -UNDEF-USE-MSG-NEXT: UNDEFVAR: 11 -UNDEF-USE-MSG-NEXT: {{^}}^{{$}} -UNDEF-USE-MSG-NEXT: numeric-expression.txt:[[#@LINE-9]]:1: note: uses undefined variable(s): "UNDEFVAR1" "UNDEFVAR2" -UNDEF-USE-MSG-NEXT: UNDEFVAR: 11 -UNDEF-USE-MSG-NEXT: {{^}}^{{$}} +UNDEF-USE-MSG: numeric-expression.txt:[[#@LINE-1]]:30: error: uses undefined variable: "UNDEFVAR1" +UNDEF-USE-MSG-NEXT: {{U}}NDEF-USE-NEXT: {{U}}NDEFVAR: {{\[\[#UNDEFVAR1\+UNDEFVAR2\]\]}} +UNDEF-USE-MSG-NEXT: {{^}} ^{{$}} +UNDEF-USE-MSG-NEXT: numeric-expression.txt:[[#@LINE-4]]:40: error: uses undefined variable: "UNDEFVAR2" +UNDEF-USE-MSG-NEXT: {{U}}NDEF-USE-NEXT: {{U}}NDEFVAR: {{\[\[#UNDEFVAR1\+UNDEFVAR2\]\]}} +UNDEF-USE-MSG-NEXT: {{^}} ^{{$}} + +; Numeric expression in negative directive using undefined variables. +RUN: %ProtectFileCheckOutput \ +RUN: not FileCheck --check-prefix UNDEF-USE2 --input-file %s %s 2>&1 \ +RUN: | FileCheck --strict-whitespace --check-prefix UNDEF-USE-MSG2 %s + +CHECK NOT UNDEF VAR USE +END MARKER +UNDEF-USE2-LABEL: CHECK NOT UNDEF VAR USE +UNDEF-USE2-NOT: UNDEFVAR: [[#UNDEFVAR1+UNDEFVAR2]] +UNDEF-USE2: END MARKER +UNDEF-USE-MSG2: numeric-expression.txt:[[#@LINE-2]]:30: error: uses undefined variable: "UNDEFVAR1" +UNDEF-USE-MSG2-NEXT: {{U}}NDEF-USE2-NOT: {{U}}NDEFVAR: {{\[\[#UNDEFVAR1\+UNDEFVAR2\]\]}} +UNDEF-USE-MSG2-NEXT: {{^}} ^{{$}} +UNDEF-USE-MSG2-NEXT: numeric-expression.txt:[[#@LINE-5]]:40: error: uses undefined variable: "UNDEFVAR2" +UNDEF-USE-MSG2-NEXT: {{U}}NDEF-USE2-NOT: {{U}}NDEFVAR: {{\[\[#UNDEFVAR1\+UNDEFVAR2\]\]}} +UNDEF-USE-MSG2-NEXT: {{^}} ^{{$}} ; Numeric expression with unsupported operator. RUN: %ProtectFileCheckOutput \ diff --git a/llvm/test/FileCheck/var-scope.txt b/llvm/test/FileCheck/var-scope.txt --- a/llvm/test/FileCheck/var-scope.txt +++ b/llvm/test/FileCheck/var-scope.txt @@ -7,11 +7,11 @@ RUN: FileCheck --check-prefixes CHECK,GLOBAL --enable-var-scope --input-file %s %s RUN: %ProtectFileCheckOutput not FileCheck --check-prefixes CHECK,LOCAL1 --enable-var-scope --input-file %s %s 2>&1 \ -RUN: | FileCheck --check-prefixes ERRUNDEF,ERRUNDEFLOCAL %s +RUN: | FileCheck --check-prefix ERRUNDEFLOCAL %s RUN: %ProtectFileCheckOutput not FileCheck --check-prefixes CHECK,LOCAL2 --enable-var-scope --input-file %s %s 2>&1 \ -RUN: | FileCheck --check-prefixes ERRUNDEF,ERRUNDEFLOCNUM %s +RUN: | FileCheck --check-prefix ERRUNDEFLOCNUM %s RUN: %ProtectFileCheckOutput not FileCheck --check-prefixes CHECK,LOCAL3 --enable-var-scope --input-file %s %s 2>&1 \ -RUN: | FileCheck --check-prefixes ERRUNDEF,ERRUNDEFLOCAL,ERRUNDEFLOCNUM %s +RUN: | FileCheck --check-prefixes ERRUNDEFLOCAL,ERRUNDEFLOCNUM %s local1 global1 @@ -33,6 +33,5 @@ LOCAL3: [[LOCAL]][[#LOCNUM+2]] GLOBAL: [[$GLOBAL]][[#$GLOBNUM+2]] -ERRUNDEF: expected string not found in input -ERRUNDEFLOCAL: uses undefined variable(s): "LOCAL" -ERRUNDEFLOCNUM: uses undefined variable(s): "LOCNUM" +ERRUNDEFLOCAL: uses undefined variable: "LOCAL" +ERRUNDEFLOCNUM: uses undefined variable: "LOCNUM" diff --git a/llvm/utils/FileCheck/FileCheck.cpp b/llvm/utils/FileCheck/FileCheck.cpp --- a/llvm/utils/FileCheck/FileCheck.cpp +++ b/llvm/utils/FileCheck/FileCheck.cpp @@ -197,6 +197,9 @@ static MarkerStyle GetMarker(FileCheckDiag::MatchType MatchTy) { switch (MatchTy) { + case FileCheckDiag::MatchError: + return MarkerStyle('X', raw_ostream::RED, "error: match error", + /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundAndExpected: return MarkerStyle('^', raw_ostream::GREEN); case FileCheckDiag::MatchFoundButExcluded: