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 @@ -1160,20 +1160,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. @@ -1181,6 +1183,8 @@ Value->begin(), Value->end()); InsertOffset += Value->size(); } + if (Errs) + return std::move(Errs); // Match the newly constructed regex. RegExToMatch = TmpStr; @@ -1256,32 +1260,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. @@ -1957,18 +1942,62 @@ MatchedCount, Buffer, MatchPos, MatchLen, Req, Diags); } -static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM, +static Error getMatchErrors(Error MatchFailures) { + return handleErrors(std::move(MatchFailures), [](const NotFoundError &E) {}); +} + +void Pattern::printMatchErrors(const SourceMgr &SM, StringRef Buffer, + Error MatchErrors, + std::vector *Diags) const { + // 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")); + SMLoc Loc = getLoc(); + FileCheckDiag::MatchType MatchTy = FileCheckDiag::MatchError; + 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())); + // SM.GetMessage(SMLoc::getFromPointer(E.getVarName.data()), + // SourceMgr::DK_Error, ErrMsg) + }); + + // Print errors with diagnostic. No other error should exist at this stage. + handleAllErrors(std::move(MatchErrors), [&](const ErrorDiagnostic &E) { + const SMDiagnostic &Diagnostic = E.getDiagnostic(); + PrintMatchErrorDiagnostic(Diagnostic.getMessage(), Diagnostic.getLoc()); + }); +} + +/// 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. @@ -1985,18 +2014,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", @@ -2018,13 +2037,12 @@ Pat.printFuzzyMatch(SM, Buffer, Diags); } -static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM, +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)); + std::vector *Diags) { + printNoMatch(ExpectedMatch, SM, CheckStr.Prefix, CheckStr.Loc, CheckStr.Pat, + MatchedCount, Buffer, VerboseVerbose, Diags); } /// Counts the number of newlines in the specified range. @@ -2082,8 +2100,12 @@ // report if (!MatchResult) { - PrintNoMatch(true, SM, *this, i, MatchBuffer, Req.VerboseVerbose, Diags, - MatchResult.takeError()); + Error MatchErrors = getMatchErrors(MatchResult.takeError()); + if (MatchErrors) + Pat.printMatchErrors(SM, Buffer, std::move(MatchErrors), Diags); + else + printNoMatch(/*ExpectedMatch=*/true, SM, *this, i, MatchBuffer, + Req.VerboseVerbose, Diags); return StringRef::npos; } size_t MatchPos = *MatchResult; @@ -2205,8 +2227,13 @@ Expected MatchResult = Pat->match(Buffer, MatchLen, SM); if (!MatchResult) { - PrintNoMatch(false, SM, Prefix, Pat->getLoc(), *Pat, 1, Buffer, - Req.VerboseVerbose, Diags, MatchResult.takeError()); + Error MatchErrors = getMatchErrors(MatchResult.takeError()); + if (MatchErrors) { + Pat->printMatchErrors(SM, Buffer, std::move(MatchErrors), Diags); + DirectiveFail = true; + } else + printNoMatch(/*ExpectedMatch=*/false, SM, Prefix, Pat->getLoc(), *Pat, + 1, Buffer, Req.VerboseVerbose, Diags); continue; } size_t Pos = *MatchResult; @@ -2266,8 +2293,12 @@ // 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()); + Error MatchErrors = getMatchErrors(MatchResult.takeError()); + if (MatchErrors) + Pat.printMatchErrors(SM, Buffer, std::move(MatchErrors), Diags); + else + printNoMatch(/*ExpectedMatch=*/true, SM, Prefix, Pat.getLoc(), Pat, 1, + MatchBuffer, 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 @@ -539,6 +539,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); } @@ -700,6 +702,11 @@ /// match defines new numeric values. Expected match(StringRef Buffer, size_t &MatchLen, const SourceMgr &SM) const; + /// Print and record diagnostics in \p Diags, if non null, for errors + /// \p MatchErrors that happened when matching \p Buffer. + void printMatchErrors(const SourceMgr &SM, StringRef Buffer, + Error MatchErrors, + std::vector *Diags) 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/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 @@ -282,15 +282,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: