Index: include/llvm/Support/FileCheck.h =================================================================== --- include/llvm/Support/FileCheck.h +++ include/llvm/Support/FileCheck.h @@ -43,7 +43,8 @@ //===----------------------------------------------------------------------===// namespace Check { -enum FileCheckType { + +enum FileCheckKind { CheckNone = 0, CheckPlain, CheckNext, @@ -58,8 +59,27 @@ CheckEOF, /// Marks when parsing found a -NOT check combined with another CHECK suffix. - CheckBadNot + CheckBadNot, + /// Marks when parsing found a -COUNT directive with invalid count value. + CheckBadCount +}; + +class FileCheckType { + FileCheckKind Kind; + unsigned Count; //< optional Count for some checks + +public: + FileCheckType(FileCheckKind Kind = CheckNone) : Kind(Kind), Count(1) { } + FileCheckType(const FileCheckType&) = default; + + operator FileCheckKind() const { return Kind; } + + unsigned getCount() const { return Count; } + FileCheckType& setCount(unsigned C); + + std::string getDescription(StringRef Prefix) const; }; + } class FileCheckPattern { @@ -113,6 +133,8 @@ Check::FileCheckType getCheckTy() const { return CheckTy; } + unsigned getCount() const { return CheckTy.getCount(); } + private: bool AddRegExToRegEx(StringRef RS, unsigned &CurParen, SourceMgr &SM); void AddBackrefToRegEx(unsigned BackrefNum); Index: lib/Support/FileCheck.cpp =================================================================== --- lib/Support/FileCheck.cpp +++ lib/Support/FileCheck.cpp @@ -16,6 +16,7 @@ #include "llvm/Support/FileCheck.h" #include "llvm/ADT/StringSet.h" +#include "llvm/Support/FormatVariadic.h" #include #include @@ -531,44 +532,16 @@ return (isalnum(c) || c == '-' || c == '_'); } -// Get the size of the prefix extension. -static size_t CheckTypeSize(Check::FileCheckType Ty) { - switch (Ty) { - case Check::CheckNone: - case Check::CheckBadNot: - return 0; - - case Check::CheckPlain: - return sizeof(":") - 1; - - case Check::CheckNext: - return sizeof("-NEXT:") - 1; - - case Check::CheckSame: - return sizeof("-SAME:") - 1; - - case Check::CheckNot: - return sizeof("-NOT:") - 1; - - case Check::CheckDAG: - return sizeof("-DAG:") - 1; - - case Check::CheckLabel: - return sizeof("-LABEL:") - 1; - - case Check::CheckEmpty: - return sizeof("-EMPTY:") - 1; - - case Check::CheckEOF: - llvm_unreachable("Should not be using EOF size"); - } - - llvm_unreachable("Bad check type"); +Check::FileCheckType& Check::FileCheckType::setCount(unsigned C) { + assert(Count > 0 || "zero count is not supported"); + assert((C == 1 || Kind == CheckPlain) && "count supported only for plain CHECK directives"); + Count = C; + return *this; } // Get a description of the type. -static std::string CheckTypeName(StringRef Prefix, Check::FileCheckType Ty) { - switch (Ty) { +std::string Check::FileCheckType::getDescription(StringRef Prefix) const { + switch (Kind) { case Check::CheckNone: return "invalid"; case Check::CheckPlain: @@ -589,50 +562,65 @@ return "implicit EOF"; case Check::CheckBadNot: return "bad NOT"; + case Check::CheckBadCount: + return "bad COUNT"; } llvm_unreachable("unknown FileCheckType"); } -static Check::FileCheckType FindCheckType(StringRef Buffer, StringRef Prefix) { +static std::pair FindCheckType(StringRef Buffer, StringRef Prefix) { if (Buffer.size() <= Prefix.size()) - return Check::CheckNone; + return { Check::CheckNone, StringRef() }; char NextChar = Buffer[Prefix.size()]; + StringRef Rest = Buffer.drop_front(Prefix.size() + 1); // Verify that the : is present after the prefix. - if (NextChar == ':') - return Check::CheckPlain; + if (NextChar == ':') { + return { Check::CheckPlain, Rest }; + } if (NextChar != '-') - return Check::CheckNone; + return { Check::CheckNone, StringRef() }; - StringRef Rest = Buffer.drop_front(Prefix.size() + 1); - if (Rest.startswith("NEXT:")) - return Check::CheckNext; + if (Rest.consume_front("COUNT-")) { + long long Count; + if (Rest.consumeInteger(10, Count)) { + // error in string parsing + return { Check::CheckBadCount, Rest }; + } + if (Count <= 0) + return { Check::CheckBadCount, Rest }; + if (!Rest.consume_front(":")) + return { Check::CheckBadCount, Rest }; + return { Check::FileCheckType(Check::CheckPlain).setCount((unsigned)Count), Rest }; + } + if (Rest.consume_front("NEXT:")) + return { Check::CheckNext, Rest }; - if (Rest.startswith("SAME:")) - return Check::CheckSame; + if (Rest.consume_front("SAME:")) + return { Check::CheckSame, Rest }; - if (Rest.startswith("NOT:")) - return Check::CheckNot; + if (Rest.consume_front("NOT:")) + return { Check::CheckNot, Rest }; - if (Rest.startswith("DAG:")) - return Check::CheckDAG; + if (Rest.consume_front("DAG:")) + return { Check::CheckDAG, Rest }; - if (Rest.startswith("LABEL:")) - return Check::CheckLabel; + if (Rest.consume_front("LABEL:")) + return { Check::CheckLabel, Rest }; - if (Rest.startswith("EMPTY:")) - return Check::CheckEmpty; + if (Rest.consume_front("EMPTY:")) + return { Check::CheckEmpty, Rest }; // You can't combine -NOT with another suffix. if (Rest.startswith("DAG-NOT:") || Rest.startswith("NOT-DAG:") || Rest.startswith("NEXT-NOT:") || Rest.startswith("NOT-NEXT:") || Rest.startswith("SAME-NOT:") || Rest.startswith("NOT-SAME:") || Rest.startswith("EMPTY-NOT:") || Rest.startswith("NOT-EMPTY:")) - return Check::CheckBadNot; + return { Check::CheckBadNot, Rest }; - return Check::CheckNone; + return { Check::CheckNone, Rest }; } // From the given position, find the next character after the word. @@ -651,8 +639,11 @@ /// 2) The found prefix must be followed by a valid check type suffix using \c /// FindCheckType above. /// -/// The first match of the regular expression to satisfy these two is returned, -/// otherwise an empty StringRef is returned to indicate failure. +/// Returns a pair of StringRefs into the Buffer, which combines: +/// - the first match of the regular expression to satisfy these two is returned, +/// otherwise an empty StringRef is returned to indicate failure. +/// - buffer rewound to the location right after parsed suffix, for parsing +/// to continue from /// /// If this routine returns a valid prefix, it will also shrink \p Buffer to /// start at the beginning of the returned prefix, increment \p LineNumber for @@ -661,7 +652,7 @@ /// /// If no valid prefix is found, the state of Buffer, LineNumber, and CheckTy /// is unspecified. -static StringRef FindFirstMatchingPrefix(Regex &PrefixRE, StringRef &Buffer, +static std::pair FindFirstMatchingPrefix(Regex &PrefixRE, StringRef &Buffer, unsigned &LineNumber, Check::FileCheckType &CheckTy) { SmallVector Matches; @@ -670,7 +661,7 @@ // Find the first (longest) match using the RE. if (!PrefixRE.match(Buffer, &Matches)) // No match at all, bail. - return StringRef(); + return { StringRef(), StringRef() }; StringRef Prefix = Matches[0]; Matches.clear(); @@ -690,11 +681,12 @@ // intentional and unintentional uses of this feature. if (Skipped.empty() || !IsPartOfWord(Skipped.back())) { // Now extract the type. - CheckTy = FindCheckType(Buffer, Prefix); + StringRef AfterSuffix; + std::tie(CheckTy, AfterSuffix) = FindCheckType(Buffer, Prefix); // If we've found a valid check type for this prefix, we're done. if (CheckTy != Check::CheckNone) - return Prefix; + return { Prefix, AfterSuffix }; } // If we didn't successfully find a prefix, we need to skip this invalid @@ -704,7 +696,7 @@ } // We ran out of buffer while skipping partial matches so give up. - return StringRef(); + return { StringRef(), StringRef() }; } /// Read the check file, which specifies the sequence of expected strings. @@ -742,19 +734,27 @@ Check::FileCheckType CheckTy; // See if a prefix occurs in the memory buffer. - StringRef UsedPrefix = FindFirstMatchingPrefix(PrefixRE, Buffer, LineNumber, + StringRef UsedPrefix; + StringRef AfterSuffix; + std::tie(UsedPrefix, AfterSuffix) = FindFirstMatchingPrefix(PrefixRE, Buffer, LineNumber, CheckTy); if (UsedPrefix.empty()) break; assert(UsedPrefix.data() == Buffer.data() && "Failed to move Buffer's start forward, or pointed prefix outside " "of the buffer!"); + assert(AfterSuffix.data() >= Buffer.data() && + AfterSuffix.data() < Buffer.data() + Buffer.size() && + "Parsing after suffix doesn't start inside of buffer!"); // Location to use for error messages. const char *UsedPrefixStart = UsedPrefix.data(); - // Skip the buffer to the end. - Buffer = Buffer.drop_front(UsedPrefix.size() + CheckTypeSize(CheckTy)); + // Skip the buffer to the end of parsed suffix (or just prefix, if no good suffix was processed). + if (AfterSuffix.empty()) + Buffer = Buffer.drop_front(UsedPrefix.size()); + else + Buffer = AfterSuffix; // Complain about useful-looking but unsupported suffixes. if (CheckTy == Check::CheckBadNot) { @@ -763,6 +763,13 @@ return true; } + // Complain about invalid count specification. + if (CheckTy == Check::CheckBadCount) { + SM.PrintMessage(SMLoc::getFromPointer(Buffer.data()), SourceMgr::DK_Error, + "invalid count in -COUNT specification on prefix '" + UsedPrefix + "'"); + return true; + } + // Okay, we found the prefix, yay. Remember the rest of the line, but ignore // leading whitespace. if (!(Req.NoCanonicalizeWhiteSpace && Req.MatchFullLines)) @@ -845,6 +852,7 @@ static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM, StringRef Prefix, SMLoc Loc, const FileCheckPattern &Pat, + unsigned MatchedCount, StringRef Buffer, StringMap &VariableTable, size_t MatchPos, size_t MatchLen, const FileCheckRequest &Req) { @@ -857,25 +865,30 @@ SMLoc MatchStart = SMLoc::getFromPointer(Buffer.data() + MatchPos); SMLoc MatchEnd = SMLoc::getFromPointer(Buffer.data() + MatchPos + MatchLen); SMRange MatchRange(MatchStart, MatchEnd); - SM.PrintMessage( - Loc, ExpectedMatch ? SourceMgr::DK_Remark : SourceMgr::DK_Error, - CheckTypeName(Prefix, Pat.getCheckTy()) + ": " + - (ExpectedMatch ? "expected" : "excluded") + - " string found in input"); + 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(MatchStart, SourceMgr::DK_Note, "found here", {MatchRange}); Pat.PrintVariableUses(SM, Buffer, VariableTable, MatchRange); } static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM, - const FileCheckString &CheckStr, StringRef Buffer, + const FileCheckString &CheckStr, unsigned MatchedCount, + StringRef Buffer, StringMap &VariableTable, size_t MatchPos, size_t MatchLen, FileCheckRequest &Req) { - PrintMatch(ExpectedMatch, SM, CheckStr.Prefix, CheckStr.Loc, CheckStr.Pat, + PrintMatch(ExpectedMatch, SM, CheckStr.Prefix, CheckStr.Loc, CheckStr.Pat, MatchedCount, Buffer, VariableTable, MatchPos, MatchLen, Req); } static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM, StringRef Prefix, SMLoc Loc, const FileCheckPattern &Pat, + unsigned MatchedCount, StringRef Buffer, StringMap &VariableTable, bool VerboseVerbose) { @@ -883,11 +896,16 @@ return; // Otherwise, we have an error, emit an error message. + 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, - CheckTypeName(Prefix, Pat.getCheckTy()) + ": " + - (ExpectedMatch ? "expected" : "excluded") + - " string not found in input"); + Message); // Print the "scanning from here" line. If the current position is at the // end of a line, advance to the start of the next line. @@ -903,10 +921,11 @@ } static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM, - const FileCheckString &CheckStr, StringRef Buffer, + const FileCheckString &CheckStr, unsigned MatchedCount, + StringRef Buffer, StringMap &VariableTable, bool VerboseVerbose) { - PrintNoMatch(ExpectedMatch, SM, CheckStr.Prefix, CheckStr.Loc, CheckStr.Pat, + PrintNoMatch(ExpectedMatch, SM, CheckStr.Prefix, CheckStr.Loc, CheckStr.Pat, MatchedCount, Buffer, VariableTable, VerboseVerbose); } @@ -953,18 +972,35 @@ } // Match itself from the last position after matching CHECK-DAG. - StringRef MatchBuffer = Buffer.substr(LastPos); - size_t MatchPos = Pat.Match(MatchBuffer, MatchLen, VariableTable); - if (MatchPos == StringRef::npos) { - PrintNoMatch(true, SM, *this, MatchBuffer, VariableTable, Req.VerboseVerbose); - return StringRef::npos; + size_t LastMatchEnd = LastPos; + size_t FirstMatchPos = 0; + // Go match the pattern Count times. Majority of patterns only match with count 1 though. + assert(Pat.getCount() != 0 && "pattern count can not be zero"); + for (unsigned i = 1; i <= Pat.getCount(); i++) { + StringRef MatchBuffer = Buffer.substr(LastMatchEnd); + size_t CurrentMatchLen; + // get a match at current start point + size_t MatchPos = Pat.Match(MatchBuffer, CurrentMatchLen, VariableTable); + if (i == 1) + FirstMatchPos = LastPos + MatchPos; + + // report + if (MatchPos == StringRef::npos) { + PrintNoMatch(true, SM, *this, i, MatchBuffer, VariableTable, Req.VerboseVerbose); + return StringRef::npos; + } + PrintMatch(true, SM, *this, i, MatchBuffer, VariableTable, MatchPos, CurrentMatchLen, Req); + + // move start point after the match + LastMatchEnd += MatchPos + CurrentMatchLen; } - PrintMatch(true, SM, *this, MatchBuffer, VariableTable, MatchPos, MatchLen, Req); + // Full match len counts from first match pos. + MatchLen = LastMatchEnd - FirstMatchPos; // Similar to the above, in "label-scan mode" we can't yet handle CHECK-NEXT // or CHECK-NOT if (!IsLabelScanMode) { - StringRef SkippedRegion = Buffer.substr(LastPos, MatchPos); + StringRef SkippedRegion = Buffer.substr(LastPos, FirstMatchPos - LastPos); // If this check is a "CHECK-NEXT", verify that the previous match was on // the previous line (i.e. that there is one newline between them). @@ -982,7 +1018,7 @@ return StringRef::npos; } - return LastPos + MatchPos; + return FirstMatchPos; } /// Verify there is a single line in the given buffer. @@ -1072,12 +1108,12 @@ size_t Pos = Pat->Match(Buffer, MatchLen, VariableTable); if (Pos == StringRef::npos) { - PrintNoMatch(false, SM, Prefix, Pat->getLoc(), *Pat, Buffer, + PrintNoMatch(false, SM, Prefix, Pat->getLoc(), *Pat, 1, Buffer, VariableTable, Req.VerboseVerbose); continue; } - PrintMatch(false, SM, Prefix, Pat->getLoc(), *Pat, Buffer, VariableTable, + PrintMatch(false, SM, Prefix, Pat->getLoc(), *Pat, 1, Buffer, VariableTable, Pos, MatchLen, Req); return true; @@ -1133,14 +1169,14 @@ // With a group of CHECK-DAGs, a single mismatching means the match on // that group of CHECK-DAGs fails immediately. if (MatchPosBuf == StringRef::npos) { - PrintNoMatch(true, SM, Prefix, Pat.getLoc(), Pat, MatchBuffer, + PrintNoMatch(true, SM, Prefix, Pat.getLoc(), Pat, 1, MatchBuffer, VariableTable, Req.VerboseVerbose); return StringRef::npos; } // 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, Buffer, VariableTable, + PrintMatch(true, SM, Prefix, Pat.getLoc(), Pat, 1, Buffer, VariableTable, MatchPos, MatchLen, Req); MatchRange M{MatchPos, MatchPos + MatchLen}; if (Req.AllowDeprecatedDagOverlap) { @@ -1182,7 +1218,7 @@ MatchPos = MI->End; } if (!Req.VerboseVerbose) - PrintMatch(true, SM, Prefix, Pat.getLoc(), Pat, Buffer, VariableTable, + PrintMatch(true, SM, Prefix, Pat.getLoc(), Pat, 1, Buffer, VariableTable, MatchPos, MatchLen, Req); // Handle the end of a CHECK-DAG group. Index: test/FileCheck/check-count.txt =================================================================== --- /dev/null +++ test/FileCheck/check-count.txt @@ -0,0 +1,100 @@ +; +; Basic error checking. +; + +this is something else + +; RUN: not FileCheck %s --input-file %s --check-prefix=CHECK-ERR1 2>&1 | FileCheck %s --check-prefix=ERRCOUNT1 +CHECK-ERR1-COUNT-xx: this +ERRCOUNT1: [[@LINE-1]]:18: error: invalid count in -COUNT specification on prefix 'CHECK-ERR1' + +; RUN: not FileCheck %s --input-file %s --check-prefix=CHECK-ERR2 2>&1 | FileCheck %s --check-prefix=ERRCOUNT2 +CHECK-ERR2-COUNT-0x1: something +ERRCOUNT2: [[@LINE-1]]:19: error: invalid count in -COUNT specification on prefix 'CHECK-ERR2' + +; RUN: not FileCheck %s --input-file %s --check-prefix=CHECK-ERR3 2>&1 | FileCheck %s --check-prefix=ERRCOUNT3 +CHECK-ERR3-COUNT-100x: else +ERRCOUNT3: [[@LINE-1]]:21: error: invalid count in -COUNT specification on prefix 'CHECK-ERR3' + +; RUN: not FileCheck %s --input-file %s --check-prefix=CHECK-ERR4 2>&1 | FileCheck %s --check-prefix=ERRCOUNT4 +CHECK-ERR4-COUNT-0: else +ERRCOUNT4: [[@LINE-1]]:19: error: invalid count in -COUNT specification on prefix 'CHECK-ERR4' + +; +; Main functionality +; + +this is duplicate +this is duplicate +this is not duplicate +this is duplicate +this is duplicate +this is duplicate + +; RUN: FileCheck %s --input-file %s --check-prefix=CHECK-CNT1 +CHECK-CNT1-COUNT-1: this is duplicate +CHECK-CNT1: this is duplicate +CHECK-CNT1-NEXT: this is not duplicate + +; RUN: FileCheck %s --input-file %s --check-prefix=CHECK-CNT2 +CHECK-CNT2-COUNT-2: this is duplicate +CHECK-CNT2: this is not duplicate + +; RUN: FileCheck %s --input-file %s --check-prefix=CHECK-CNT3 +CHECK-CNT3-COUNT-2: this is duplicate +CHECK-CNT3: this is not duplicate +CHECK-CNT3-COUNT-3: this is duplicate +CHECK-CNT3-NOT: {{^}}this is duplicate + +; RUN: FileCheck %s --input-file %s --check-prefix=CHECK-CNT4 +CHECK-CNT4-COUNT-5: this is duplicate +CHECK-CNT4-EMPTY: + +Many-label: + +-many- +-many- +-many- +-many- +-many- +-many- +-many- +-many- +-many- +-many- +-many- +-many- +-many- +-many- +-many- +-many- + +; RUN: FileCheck %s --input-file %s --check-prefix=CHECK-CNTMANY +CHECK-CNTMANY-COUNT-2: this is duplicate +CHECK-CNTMANY-LABEL: Many-label: +CHECK-CNTMANY-EMPTY: +CHECK-CNTMANY-COUNT-16: {{^}}-many- +CHECK-CNTMANY-EMPTY: + +; +; Tests on mismatches: +; + +; RUN: not FileCheck %s --input-file %s --check-prefix=CHECK-MIS1 2>&1 | FileCheck %s --check-prefix=MISCOUNT1 +CHECK-MIS1-COUNT-3: this is duplicate +CHECK-MIS1: {{^}}this is not duplicate +MISCOUNT1: [[@LINE-1]]:13: error: CHECK-MIS1: expected string not found in input + +; RUN: not FileCheck %s --input-file %s --check-prefix=CHECK-MIS2 2>&1 | FileCheck %s --check-prefix=MISCOUNT2 +CHECK-MIS2-COUNT-10: {{^this is duplicate}} +CHECK-MIS2: {{^}}this is not duplicate +MISCOUNT2: [[@LINE-2]]:22: error: CHECK-MIS2: expected string not found in input + +; RUN: not FileCheck %s --input-file %s --check-prefix=CHECK-MIS3 2>&1 | FileCheck %s --check-prefix=MISCOUNT3 +CHECK-MIS3-COUNT-5: this is duplicate +CHECK-MIS3-EMPTY: +CHECK-MIS3-LABEL: Many-label: +CHECK-MIS3-EMPTY: +CHECK-MIS3-COUNT-160: {{^}}-many- +CHECK-MIS3-EMPTY: +MISCOUNT3: [[@LINE-2]]:23: error: CHECK-MIS3: expected string not found in input (17 out of 160)