Index: docs/CommandGuide/FileCheck.rst =================================================================== --- docs/CommandGuide/FileCheck.rst +++ docs/CommandGuide/FileCheck.rst @@ -311,6 +311,29 @@ ; CHECK: ret i8 } +The "CHECK-COUNT:" directive +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you need to match multiple lines with the same pattern over and over again +you can repeat a plain ``CHECK:`` as many times as needed. If that looks too +boring you can instead use a counted check "``CHECK-COUNT-:``", where +```` is a positive decimal number. It will match the pattern exactly +```` times, no more and no less. If you specified a custom check prefix, +just use "``-COUNT-:``" for the same effect. +Here is a simple example: + +.. code-block:: llvm + + Loop at depth 1 + Loop at depth 1 + Loop at depth 1 + Loop at depth 1 + Loop at depth 2 + Loop at depth 3 + + ; CHECK-COUNT-6: Loop at depth {{[0-9]+}} + ; CHECK-NOT: Loop at depth {{[0-9]+}} + The "CHECK-DAG:" directive ~~~~~~~~~~~~~~~~~~~~~~~~~~ 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,7 +59,26 @@ 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; + int Count; //< optional Count for some checks + +public: + FileCheckType(FileCheckKind Kind = CheckNone) : Kind(Kind), Count(1) {} + FileCheckType(const FileCheckType &) = default; + + operator FileCheckKind() const { return Kind; } + + int getCount() const { return Count; } + FileCheckType &setCount(int C); + + std::string getDescription(StringRef Prefix) const; }; } @@ -113,6 +133,8 @@ Check::FileCheckType getCheckTy() const { return CheckTy; } + int 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,8 +16,12 @@ #include "llvm/Support/FileCheck.h" #include "llvm/ADT/StringSet.h" +#include "llvm/Support/FormatVariadic.h" +#include #include #include +#include +#include using namespace llvm; @@ -531,47 +535,22 @@ 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(int C) { + assert(Count > 0 || "zero and negative counts are 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: + if (Count > 1) + return Prefix.str() + "-COUNT"; return Prefix; case Check::CheckNext: return Prefix.str() + "-NEXT"; @@ -589,50 +568,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; + return {Check::CheckPlain, Rest}; if (NextChar != '-') - return Check::CheckNone; + return {Check::CheckNone, StringRef()}; + + if (Rest.consume_front("COUNT-")) { + int64_t Count; + if (Rest.consumeInteger(10, Count)) + // Error happened in parsing integer. + return {Check::CheckBadCount, Rest}; + if (Count <= 0 || Count > INT32_MAX) + return {Check::CheckBadCount, Rest}; + if (!Rest.consume_front(":")) + return {Check::CheckBadCount, Rest}; + return {Check::FileCheckType(Check::CheckPlain).setCount(Count), Rest}; + } - StringRef Rest = Buffer.drop_front(Prefix.size() + 1); - if (Rest.startswith("NEXT:")) - return Check::CheckNext; + 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 +645,12 @@ /// 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,16 +659,16 @@ /// /// If no valid prefix is found, the state of Buffer, LineNumber, and CheckTy /// is unspecified. -static StringRef FindFirstMatchingPrefix(Regex &PrefixRE, StringRef &Buffer, - unsigned &LineNumber, - Check::FileCheckType &CheckTy) { +static std::pair +FindFirstMatchingPrefix(Regex &PrefixRE, StringRef &Buffer, + unsigned &LineNumber, Check::FileCheckType &CheckTy) { SmallVector Matches; while (!Buffer.empty()) { // Find the first (longest) match using the RE. if (!PrefixRE.match(Buffer, &Matches)) // No match at all, bail. - return StringRef(); + return {"", ""}; StringRef Prefix = Matches[0]; Matches.clear(); @@ -690,11 +688,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 +703,7 @@ } // We ran out of buffer while skipping partial matches so give up. - return StringRef(); + return {"", ""}; } /// Read the check file, which specifies the sequence of expected strings. @@ -742,19 +741,26 @@ Check::FileCheckType CheckTy; // See if a prefix occurs in the memory buffer. - StringRef UsedPrefix = FindFirstMatchingPrefix(PrefixRE, Buffer, LineNumber, - CheckTy); + 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). + Buffer = AfterSuffix.empty() ? Buffer.drop_front(UsedPrefix.size()) + : AfterSuffix; // Complain about useful-looking but unsupported suffixes. if (CheckTy == Check::CheckBadNot) { @@ -763,6 +769,14 @@ 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,9 +859,9 @@ static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM, StringRef Prefix, SMLoc Loc, const FileCheckPattern &Pat, - StringRef Buffer, StringMap &VariableTable, - size_t MatchPos, size_t MatchLen, - const FileCheckRequest &Req) { + int MatchedCount, StringRef Buffer, + StringMap &VariableTable, size_t MatchPos, + size_t MatchLen, const FileCheckRequest &Req) { if (ExpectedMatch) { if (!Req.Verbose) return; @@ -857,37 +871,46 @@ SMLoc MatchStart = SMLoc::getFromPointer(Buffer.data() + MatchPos); SMLoc MatchEnd = SMLoc::getFromPointer(Buffer.data() + MatchPos + MatchLen); SMRange MatchRange(MatchStart, MatchEnd); + 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, - CheckTypeName(Prefix, Pat.getCheckTy()) + ": " + - (ExpectedMatch ? "expected" : "excluded") + - " string found in input"); + 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, - StringMap &VariableTable, size_t MatchPos, - size_t MatchLen, FileCheckRequest &Req) { + const FileCheckString &CheckStr, int MatchedCount, + StringRef Buffer, StringMap &VariableTable, + size_t MatchPos, size_t MatchLen, + FileCheckRequest &Req) { PrintMatch(ExpectedMatch, SM, CheckStr.Prefix, CheckStr.Loc, CheckStr.Pat, - Buffer, VariableTable, MatchPos, MatchLen, Req); + MatchedCount, Buffer, VariableTable, MatchPos, MatchLen, Req); } static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM, - StringRef Prefix, SMLoc Loc, const FileCheckPattern &Pat, - StringRef Buffer, - StringMap &VariableTable, + StringRef Prefix, SMLoc Loc, + const FileCheckPattern &Pat, int MatchedCount, + StringRef Buffer, StringMap &VariableTable, bool VerboseVerbose) { if (!ExpectedMatch && !VerboseVerbose) return; // Otherwise, we have an error, emit an error message. - SM.PrintMessage(Loc, - ExpectedMatch ? SourceMgr::DK_Error : SourceMgr::DK_Remark, - CheckTypeName(Prefix, Pat.getCheckTy()) + ": " + - (ExpectedMatch ? "expected" : "excluded") + - " string not found in input"); + 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. If the current position is at the // end of a line, advance to the start of the next line. @@ -903,11 +926,11 @@ } static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM, - const FileCheckString &CheckStr, StringRef Buffer, - StringMap &VariableTable, + const FileCheckString &CheckStr, int MatchedCount, + StringRef Buffer, StringMap &VariableTable, bool VerboseVerbose) { PrintNoMatch(ExpectedMatch, SM, CheckStr.Prefix, CheckStr.Loc, CheckStr.Pat, - Buffer, VariableTable, VerboseVerbose); + MatchedCount, Buffer, VariableTable, VerboseVerbose); } /// Count the number of newlines in the specified range. @@ -953,18 +976,38 @@ } // 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 (int 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 +1025,7 @@ return StringRef::npos; } - return LastPos + MatchPos; + return FirstMatchPos; } /// Verify there is a single line in the given buffer. @@ -1072,12 +1115,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,15 +1176,15 @@ // 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, - MatchPos, MatchLen, Req); + PrintMatch(true, SM, Prefix, Pat.getLoc(), Pat, 1, Buffer, + VariableTable, MatchPos, MatchLen, Req); MatchRange M{MatchPos, MatchPos + MatchLen}; if (Req.AllowDeprecatedDagOverlap) { // We don't need to track all matches in this mode, so we just maintain @@ -1182,7 +1225,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-COUNT: 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-COUNT: expected string not found in input (17 out of 160)