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 @@ -26,6 +26,7 @@ struct FileCheckRequest { std::vector CheckPrefixes; std::vector CommentPrefixes; + std::vector AnchorPrefixes; bool NoCanonicalizeWhiteSpace = false; std::vector ImplicitCheckNot; std::vector GlobalDefines; @@ -55,6 +56,7 @@ CheckLabel, CheckEmpty, CheckComment, + CheckAnchor, /// Indicates the pattern only matches the end of file. This is used for /// trailing CHECK-NOTs. 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 @@ -1497,6 +1497,7 @@ return Prefix.str() + "-LABEL"; case Check::CheckEmpty: return Prefix.str() + "-EMPTY"; + case Check::CheckAnchor: case Check::CheckComment: return std::string(Prefix); case Check::CheckEOF: @@ -1519,15 +1520,20 @@ StringRef Rest = Buffer.drop_front(Prefix.size() + 1); // Check for comment. - if (Req.CommentPrefixes.end() != std::find(Req.CommentPrefixes.begin(), - Req.CommentPrefixes.end(), - Prefix)) { + if (llvm::is_contained(Req.CommentPrefixes, Prefix)) { if (NextChar == ':') return {Check::CheckComment, Rest}; // Ignore a comment prefix if it has a suffix like "-NOT". return {Check::CheckNone, StringRef()}; } + // Check for line anchor + if (llvm::is_contained(Req.AnchorPrefixes, Prefix)) { + if (NextChar == ':') + return {Check::CheckAnchor, Rest}; + return {Check::CheckNone, StringRef()}; + } + // Verify that the : is present after the prefix. if (NextChar == ':') return {Check::CheckPlain, Rest}; @@ -1661,6 +1667,34 @@ GlobalNumericVariableTable[LineName] = LineVariable; } +Error FileCheckPatternContext::createLineAnchorVariable(StringRef Buffer, + SourceMgr &SM, + unsigned LineNumber) { + if (Buffer[0] == '$') + return ErrorDiagnostic::get(SM, Buffer, + "line anchor names can't start with '$'"); + Expected ParseVarResult = + Pattern::parseVariable(Buffer, SM); + if (!ParseVarResult) + return ParseVarResult.takeError(); + if (ParseVarResult->IsPseudo) { + return ErrorDiagnostic::get( + SM, ParseVarResult->Name, + "line anchor names can't be pseudo variable names"); + } + llvm::NumericVariable *&NumericVar = + GlobalNumericVariableTable[ParseVarResult->Name]; + if (NumericVar) + return ErrorDiagnostic::get(SM, ParseVarResult->Name, + "duplicate line anchor name '" + + ParseVarResult->Name + "'"); + NumericVar = makeNumericVariable( + ParseVarResult->Name, ExpressionFormat(ExpressionFormat::Kind::Unsigned), + LineNumber); + NumericVar->setValue(ExpressionValue(LineNumber)); + return Error::success(); +} + FileCheck::FileCheck(FileCheckRequest Req) : Req(Req), PatternContext(std::make_unique()), CheckStrings(std::make_unique>()) {} @@ -1728,7 +1762,7 @@ FindFirstMatchingPrefix(Req, PrefixRE, Buffer, LineNumber, CheckTy); if (UsedPrefix.empty()) break; - if (CheckTy != Check::CheckComment) + if (CheckTy != Check::CheckComment && CheckTy != Check::CheckAnchor) FoundUsedCheckPrefix = true; assert(UsedPrefix.data() == Buffer.data() && @@ -1780,6 +1814,15 @@ if (CheckTy == Check::CheckComment) continue; + if (CheckTy == Check::CheckAnchor) { + if (Error E = PatternContext->createLineAnchorVariable(PatternBuffer, SM, + LineNumber)) { + logAllUnhandledErrors(std::move(E), errs()); + return true; + } + continue; + } + // Parse the pattern. Pattern P(CheckTy, PatternContext.get(), LineNumber); if (P.parsePattern(PatternBuffer, UsedPrefix, SM, Req)) @@ -2308,6 +2351,7 @@ static const char *DefaultCheckPrefixes[] = {"CHECK"}; static const char *DefaultCommentPrefixes[] = {"COM", "RUN"}; +static const char *DefaultAnchorPrefixes[] = {"LINE-ANCHOR"}; bool FileCheck::ValidateCheckPrefixes() { StringSet<> UniquePrefixes; @@ -2320,12 +2364,18 @@ for (const char *Prefix : DefaultCommentPrefixes) UniquePrefixes.insert(Prefix); } + if (Req.AnchorPrefixes.empty()) { + for (const char *Prefix : DefaultAnchorPrefixes) + UniquePrefixes.insert(Prefix); + } // Do not validate the default prefixes, or diagnostics about duplicates might // incorrectly indicate that they were supplied by the user. if (!ValidatePrefixes("check", UniquePrefixes, Req.CheckPrefixes)) return false; if (!ValidatePrefixes("comment", UniquePrefixes, Req.CommentPrefixes)) return false; + if (!ValidatePrefixes("anchor", UniquePrefixes, Req.AnchorPrefixes)) + return false; return true; } @@ -2339,6 +2389,10 @@ for (const char *Prefix : DefaultCommentPrefixes) Req.CommentPrefixes.push_back(Prefix); } + if (Req.AnchorPrefixes.empty()) { + for (const char *Prefix : DefaultAnchorPrefixes) + Req.AnchorPrefixes.push_back(Prefix); + } // We already validated the contents of CheckPrefixes and CommentPrefixes so // just concatenate them as alternatives. @@ -2352,6 +2406,10 @@ PrefixRegexStr.push_back('|'); PrefixRegexStr.append(Prefix); } + for (StringRef Prefix : Req.AnchorPrefixes) { + PrefixRegexStr.push_back('|'); + PrefixRegexStr.append(Prefix); + } return Regex(PrefixRegexStr); } 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 @@ -482,6 +482,11 @@ /// matched. void createLineVariable(); + /// Creates a variable called \p Name with the value \p LineNumber. + /// Returns an Error if a numeric variable already exists with the same name. + Error createLineAnchorVariable(StringRef Buffer, SourceMgr &SM, + unsigned LineNumber); + /// Undefines local variables (variables whose name does not start with a '$' /// sign), i.e. removes them from GlobalVariableTable and from /// GlobalNumericVariableTable and also clears the value of numeric diff --git a/llvm/test/FileCheck/line-anchor-errors.txt b/llvm/test/FileCheck/line-anchor-errors.txt new file mode 100644 --- /dev/null +++ b/llvm/test/FileCheck/line-anchor-errors.txt @@ -0,0 +1,26 @@ +; RUN: %ProtectFileCheckOutput not FileCheck -check-prefix BAD1 -anchor-prefix BAD-ANCHOR1 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR1 %s +; RUN: %ProtectFileCheckOutput not FileCheck -check-prefix BAD2 -anchor-prefix BAD-ANCHOR2 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR2 %s +; RUN: %ProtectFileCheckOutput not FileCheck -check-prefix BAD3 -anchor-prefix BAD-ANCHOR3 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR3 %s +; RUN: %ProtectFileCheckOutput not FileCheck -check-prefix BAD4 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR4 %s +; RUN: %ProtectFileCheckOutput not FileCheck -check-prefix BAD5 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR5 %s +; RUN: %ProtectFileCheckOutput not FileCheck -check-prefix BAD6 -anchor-prefix BAD-ANCHOR6 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR6 %s + +BAD-ANCHOR1: @NAME +ERR1: line-anchor-errors.txt:[[@LINE-1]]:14: error: line anchor names can't be pseudo variable names{{$}} + +BAD-ANCHOR2: $NAME +ERR2: line-anchor-errors.txt:[[@LINE-1]]:14: error: line anchor names can't start with '$'{{$}} + +BAD-ANCHOR3: %NAME +ERR3: line-anchor-errors.txt:[[@LINE-1]]:14: error: invalid variable name{{$}} + +LINE-ANCHOR: NAME +BAD4: [[@NAME]] +ERR4: line-anchor-errors.txt:[[@LINE-1]]:9: error: invalid pseudo numeric variable '@NAME' + +BAD5: [[NAME]] +ERR5: line-anchor-errors.txt:1:1: note: uses undefined variable(s): "NAME" + +BAD-ANCHOR6: NAME +BAD-ANCHOR6: NAME +ERR6: line-anchor-errors.txt:[[@LINE-1]]:14: error: duplicate line anchor name 'NAME' diff --git a/llvm/test/FileCheck/line-anchors.txt b/llvm/test/FileCheck/line-anchors.txt new file mode 100644 --- /dev/null +++ b/llvm/test/FileCheck/line-anchors.txt @@ -0,0 +1,23 @@ +; RUN: FileCheck -input-file %s %s +2 +; LINE-ANCHOR: ANCHOR +4 aaa +5 bbb +6 ccc +7 ddd +8 eee +9 fff +10 +; LINE-ANCHOR: ANCHOR2 +12 +; CHECK: [[#ANCHOR+1]] aaa +; CHECK-NEXT: [[#ANCHOR+2]] bbb +; CHECK-NEXT: [[#ANCHOR+3]] ccc +; CHECK-NEXT: [[#ANCHOR+4]] ddd +; CHECK-NEXT: [[#ANCHOR2-3]] eee +; CHECK-NEXT: [[#ANCHOR2-2]] fff +; CHECK-NEXT: [[#ANCHOR2-1]] +20 +21 aaa ; LINE-ANCHOR: INPLACE +; CHECK: [[#INPLACE]] aaa +22 diff --git a/llvm/unittests/Support/FileCheckTest.cpp b/llvm/unittests/Support/FileCheckTest.cpp --- a/llvm/unittests/Support/FileCheckTest.cpp +++ b/llvm/unittests/Support/FileCheckTest.cpp @@ -1605,6 +1605,27 @@ ASSERT_THAT_EXPECTED(ExpressionVal, Succeeded()); EXPECT_EQ(cantFail(ExpressionVal->getSignedValue()), 36); + expectDiagnosticError("line anchor names can't be pseudo variable names", + Cxt.createLineAnchorVariable( + bufferize(SM, "@LINE_NAME"), SM, ++LineNumber)); + expectDiagnosticError("line anchor names can't start with '$'", + Cxt.createLineAnchorVariable( + bufferize(SM, "$LINE_NAME"), SM, ++LineNumber)); + ASSERT_THAT_ERROR( + Cxt.createLineAnchorVariable(bufferize(SM, "LINE_NAME"), SM, 50), + Succeeded()); + P = Pattern(Check::CheckPlain, &Cxt, ++LineNumber); + ASSERT_FALSE(P.parsePattern("[[#LINE_NAME]]", "CHECK", SM, Req)); + ASSERT_THAT_EXPECTED(P.match("50", MatchLen, SM), Succeeded()); + P = Pattern(Check::CheckPlain, &Cxt, ++LineNumber); + ASSERT_FALSE( + P.parsePattern(bufferize(SM, "[[#LINE_NAME+2]]"), "CHECK", SM, Req)); + ASSERT_THAT_EXPECTED(P.match("52", MatchLen, SM), Succeeded()); + P = Pattern(Check::CheckPlain, &Cxt, ++LineNumber); + ASSERT_FALSE( + P.parsePattern(bufferize(SM, "[[#LINE_NAME-2]]"), "CHECK", SM, Req)); + ASSERT_THAT_EXPECTED(P.match("48", MatchLen, SM), Succeeded()); + // Clear local variables and check global variables remain defined. Cxt.clearLocalVars(); EXPECT_THAT_EXPECTED(Cxt.getPatternVarValue(GlobalVarStr), Succeeded()); 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 @@ -52,6 +52,11 @@ "maintain if they all follow a consistent comment style. This\n" "feature is meant for non-LIT test suites using FileCheck.")); +static cl::list + AnchorPrefixes("anchor-prefix", cl::CommaSeparated, cl::Hidden, + cl::desc("prefix to use for line anchors\n" + "(defaults to 'LINE-ANCHOR').")); + static cl::opt NoCanonicalizeWhiteSpace( "strict-whitespace", cl::desc("Do not treat all horizontal whitespace as equivalent")); @@ -344,6 +349,8 @@ return "empty"; case Check::CheckComment: return "com"; + case Check::CheckAnchor: + return "anchor"; case Check::CheckEOF: return "eof"; case Check::CheckBadNot: @@ -735,6 +742,9 @@ for (StringRef Prefix : CommentPrefixes) Req.CommentPrefixes.push_back(Prefix); + for (StringRef Prefix : AnchorPrefixes) + Req.AnchorPrefixes.push_back(Prefix); + for (StringRef CheckNot : ImplicitCheckNot) Req.ImplicitCheckNot.push_back(CheckNot);