diff --git a/docs/CommandGuide/FileCheck.rst b/docs/CommandGuide/FileCheck.rst index 809eee0469d..9d7f63cea91 100644 --- a/docs/CommandGuide/FileCheck.rst +++ b/docs/CommandGuide/FileCheck.rst @@ -243,6 +243,55 @@ occurrences matching ``CHECK-DAG:`` after ``CHECK-NOT:``. For example, This case will reject input strings where ``BEFORE`` occurs after ``AFTER``. +The "CHECK-LABEL:" directive +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes in a file containing multiple tests divided into logical blocks, one +or more ``CHECK:`` directives may inadvertently succeed by matching lines in a +later block. While an error will usually eventually be generated, the check +flagged as causing the error may not actually bear any relationship to the +actual source of the problem. + +In order to produce better error messages in these cases, the "``CHECK-LABEL:``" +directive can be used. It is treated identically to a normal ``CHECK`` +directive except that the FileCheck utility makes an additional assumption that +a line matched by the directive cannot also be matched by any other check +present in ``match-filename``; this is intended to be used for lines containing +labels or other unique identifiers. Conceptually, the presence of +``CHECK-LABEL`` divides the input stream into separate blocks, each of which is +processed independently, preventing a ``CHECK:`` directive in one block +matching a line in another block. For example, + +.. code-block:: llvm + + define %struct.C* @C_ctor_base(%struct.C* %this, i32 %x) { + entry: + ; CHECK-LABEL: C_ctor_base: + ; CHECK: mov [[SAVETHIS:r[0-9]+]], r0 + ; CHECK: bl A_ctor_base + ; CHECK: mov r0, [[SAVETHIS]] + %0 = bitcast %struct.C* %this to %struct.A* + %call = tail call %struct.A* @A_ctor_base(%struct.A* %0) + %1 = bitcast %struct.C* %this to %struct.B* + %call2 = tail call %struct.B* @B_ctor_base(%struct.B* %1, i32 %x) + ret %struct.C* %this + } + + define %struct.D* @D_ctor_base(%struct.D* %this, i32 %x) { + entry: + ; CHECK-LABEL: D_ctor_base: + +The use of ``CHECK-LABEL:`` directives in this case ensures that the three +``CHECK:`` directives only accept lines corresponding to the body of the +``@C_ctor_base`` function, even if the patterns match lines found later in +the file. + +There is no requirement that ``CHECK-LABEL:`` directives contain strings that +correspond to actual syntactic labels in a source or output language: they must +simply uniquely match a single line in the file being verified. + +``CHECK-LABEL:`` directives cannot contain variable definitions or uses. + FileCheck Pattern Matching Syntax ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/test/FileCheck/check-label.txt b/test/FileCheck/check-label.txt new file mode 100644 index 00000000000..27f0beeb291 --- /dev/null +++ b/test/FileCheck/check-label.txt @@ -0,0 +1,51 @@ +; RUN: FileCheck -input-file %s %s -check-prefix=CHECKOK +; RUN: not FileCheck -input-file %s %s -check-prefix=CHECKFAIL 2>&1 | FileCheck %s -check-prefix=CHECKERROR + +label0: +a +b + +label1: +b +c + +label2: +a +c + +; CHECKOK-LABEL: {{^}}label0: +; CHECKOK: {{^}}a +; CHECKOK: {{^}}b + +; CHECKOK-LABEL: {{^}}label1: +; CHECKOK: {{^}}b +; CHECKOK: {{^}}c + +; CHECKOK-LABEL: {{^}}label2: +; CHECKOK: {{^}}a +; CHECKOK: {{^}}c + +; CHECKFAIL-LABEL: {{^}}label0: +; CHECKFAIL: {{^}}a +; CHECKFAIL: {{^}}b +; CHECKFAIL: {{^}}c + +; CHECKERROR: expected string not found in input +; CHECKERROR-NEXT: CHECKFAIL: {{[{][{]\^[}][}]}}c + +; CHECKFAIL-LABEL: {{^}}label1: +; CHECKFAIL: {{^}}a +; CHECKFAIL: {{^}}b +; CHECKFAIL: {{^}}c + +; CHECKERROR: expected string not found in input +; CHECKERROR-NEXT: CHECKFAIL: {{[{][{]\^[}][}]}}a + +; CHECKFAIL-LABEL: {{^}}label2: +; CHECKFAIL: {{^}}a +; CHECKFAIL: {{^}}b +; CHECKFAIL: {{^}}c + +; CHECKERROR: expected string not found in input +; CHECKERROR-NEXT: CHECKFAIL: {{[{][{]\^[}][}]}}b + diff --git a/utils/FileCheck/FileCheck.cpp b/utils/FileCheck/FileCheck.cpp index 5e2d93bf399..e252db91418 100644 --- a/utils/FileCheck/FileCheck.cpp +++ b/utils/FileCheck/FileCheck.cpp @@ -115,6 +115,9 @@ public: void PrintFailureInfo(const SourceMgr &SM, StringRef Buffer, const StringMap &VariableTable) const; + bool hasVariable() const { return !(VariableUses.empty() && + VariableDefs.empty()); } + void setMatchNot(bool Not) { MatchNot = Not; } bool getMatchNot() const { return MatchNot; } @@ -594,17 +597,21 @@ struct CheckString { /// to a CHECK: directive. bool IsCheckNext; + /// IsCheckLabel - This is true if this is a CHECK-LABEL: directive (as + /// opposed to a CHECK: directive. + bool IsCheckLabel; + /// DagNotStrings - These are all of the strings that are disallowed from /// occurring between this match string and the previous one (or start of /// file). std::vector DagNotStrings; - CheckString(const Pattern &P, SMLoc L, bool isCheckNext) - : Pat(P), Loc(L), IsCheckNext(isCheckNext) {} + CheckString(const Pattern &P, SMLoc L, bool isCheckNext, bool isCheckLabel) + : Pat(P), Loc(L), IsCheckNext(isCheckNext), IsCheckLabel(isCheckLabel) {} /// Check - Match check string and its "not strings" and/or "dag strings". - size_t Check(const SourceMgr &SM, StringRef Buffer, size_t &MatchLen, - StringMap &VariableTable) const; + size_t Check(const SourceMgr &SM, StringRef Buffer, bool IsLabel, + size_t &MatchLen, StringMap &VariableTable) const; /// CheckNext - Verify there is a single line in the given buffer. bool CheckNext(const SourceMgr &SM, StringRef Buffer) const; @@ -703,7 +710,8 @@ static bool ReadCheckFile(SourceMgr &SM, // When we find a check prefix, keep track of whether we find CHECK: or // CHECK-NEXT: - bool IsCheckNext = false, IsCheckNot = false, IsCheckDag = false; + bool IsCheckNext = false, IsCheckNot = false, IsCheckDag = false, + IsCheckLabel = false; // Verify that the : is present after the prefix. if (Buffer[CheckPrefix.size()] == ':') { @@ -720,6 +728,10 @@ static bool ReadCheckFile(SourceMgr &SM, memcmp(Buffer.data()+CheckPrefix.size(), "-DAG:", 5) == 0) { Buffer = Buffer.substr(CheckPrefix.size()+5); IsCheckDag = true; + } else if (Buffer.size() > CheckPrefix.size()+7 && + memcmp(Buffer.data()+CheckPrefix.size(), "-LABEL:", 7) == 0) { + Buffer = Buffer.substr(CheckPrefix.size()+7); + IsCheckLabel = true; } else { Buffer = Buffer.substr(1); continue; @@ -740,6 +752,15 @@ static bool ReadCheckFile(SourceMgr &SM, if (P.ParsePattern(Buffer.substr(0, EOL), SM, LineNumber)) return true; + // Verify that CHECK-LABEL lines do not define or use variables + if (IsCheckLabel && P.hasVariable()) { + SM.PrintMessage(SMLoc::getFromPointer(CheckPrefixStart), + SourceMgr::DK_Error, + "found '"+CheckPrefix+"-LABEL:' with variable definition" + " or use'"); + return true; + } + P.setMatchNot(IsCheckNot); P.setMatchDag(IsCheckDag); @@ -763,7 +784,8 @@ static bool ReadCheckFile(SourceMgr &SM, // Okay, add the string we captured to the output vector and move on. CheckStrings.push_back(CheckString(P, PatternLoc, - IsCheckNext)); + IsCheckNext, + IsCheckLabel)); std::swap(DagNotMatches, CheckStrings.back().DagNotStrings); } @@ -771,6 +793,7 @@ static bool ReadCheckFile(SourceMgr &SM, if (!DagNotMatches.empty()) { CheckStrings.push_back(CheckString(Pattern(true), SMLoc::getFromPointer(Buffer.data()), + false, false)); std::swap(DagNotMatches, CheckStrings.back().DagNotStrings); } @@ -829,15 +852,17 @@ static unsigned CountNumNewlinesBetween(StringRef Range) { } size_t CheckString::Check(const SourceMgr &SM, StringRef Buffer, - size_t &MatchLen, + bool IsLabel, size_t &MatchLen, StringMap &VariableTable) const { size_t LastPos = 0; std::vector NotStrings; - // Match "dag strings" (with mixed "not strings" if any). - LastPos = CheckDag(SM, Buffer, NotStrings, VariableTable); - if (LastPos == StringRef::npos) - return StringRef::npos; + if (!IsLabel) { + // Match "dag strings" (with mixed "not strings" if any). + LastPos = CheckDag(SM, Buffer, NotStrings, VariableTable); + if (LastPos == StringRef::npos) + return StringRef::npos; + } // Match itself from the last position after matching CHECK-DAG. StringRef MatchBuffer = Buffer.substr(LastPos); @@ -848,17 +873,19 @@ size_t CheckString::Check(const SourceMgr &SM, StringRef Buffer, } MatchPos += LastPos; - StringRef SkippedRegion = Buffer.substr(LastPos, MatchPos); + if (!IsLabel) { + StringRef SkippedRegion = Buffer.substr(LastPos, MatchPos); - // 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). - if (CheckNext(SM, SkippedRegion)) - return StringRef::npos; + // 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). + if (CheckNext(SM, SkippedRegion)) + return StringRef::npos; - // If this match had "not strings", verify that they don't exist in the - // skipped region. - if (CheckNot(SM, SkippedRegion, NotStrings, VariableTable)) - return StringRef::npos; + // If this match had "not strings", verify that they don't exist in the + // skipped region. + if (CheckNot(SM, SkippedRegion, NotStrings, VariableTable)) + return StringRef::npos; + } return MatchPos; } @@ -1040,18 +1067,56 @@ int main(int argc, char **argv) { // file. StringRef Buffer = F->getBuffer(); - for (unsigned StrNo = 0, e = CheckStrings.size(); StrNo != e; ++StrNo) { - const CheckString &CheckStr = CheckStrings[StrNo]; + bool hasError = false; - // Find StrNo in the file. - size_t MatchLen = 0; - size_t MatchPos = CheckStr.Check(SM, Buffer, MatchLen, VariableTable); + unsigned i = 0, j = 0, e = CheckStrings.size(); - if (MatchPos == StringRef::npos) - return 1; + while (true) { + StringRef CheckRegion; + if (j == e) { + CheckRegion = Buffer; + } else { + const CheckString &CheckLabelStr = CheckStrings[j]; + if (!CheckLabelStr.IsCheckLabel) { + ++j; + continue; + } - Buffer = Buffer.substr(MatchPos + MatchLen); + // Scan to next CHECK-LABEL match, ignoring CHECK-NOT and CHECK-DAG + size_t MatchLabelLen = 0; + size_t MatchLabelPos = CheckLabelStr.Check(SM, Buffer, true, + MatchLabelLen, VariableTable); + if (MatchLabelPos == StringRef::npos) { + hasError = true; + break; + } + + CheckRegion = Buffer.substr(0, MatchLabelPos + MatchLabelLen); + Buffer = Buffer.substr(MatchLabelPos + MatchLabelLen); + ++j; + } + + for ( ; i != j; ++i) { + const CheckString &CheckStr = CheckStrings[i]; + + // Check each string within the scanned region, including a second check + // of any final CHECK-LABEL (to verify CHECK-NOT and CHECK-DAG) + size_t MatchLen = 0; + size_t MatchPos = CheckStr.Check(SM, CheckRegion, false, MatchLen, + VariableTable); + + if (MatchPos == StringRef::npos) { + hasError = true; + i = j; + break; + } + + CheckRegion = CheckRegion.substr(MatchPos + MatchLen); + } + + if (j == e) + break; } - return 0; + return hasError ? 1 : 0; }