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 @@ -79,7 +79,34 @@ std::string getDescription(StringRef Prefix) const; }; -} +} // namespace Check + +/// Class holding the FileCheckPattern global state, shared by all patterns: +/// tables holding values of variables and whether they are defined or not at +/// any given time in the matching process. +class FileCheckPatternContext { + friend class FileCheckPattern; + +private: + /// When matching a given pattern, this holds the value of all the FileCheck + /// variables defined in previous patterns. In a pattern only the last + /// definition for a given variable is recorded in this table, back-references + /// are used for uses after any the other definition. + StringMap GlobalVariableTable; + +public: + /// Return the value of variable \p VarName or nothing if no such variable has + /// been defined. + llvm::Optional getPatternVarValue(StringRef VarName); + + /// Define pattern and numeric variables from definitions given on the + /// command line passed as a vector of VAR=VAL strings in \p CmdlineDefines. + void defineCmdlineVariables(std::vector &CmdlineDefines); + + /// Undefine local variables (variables whose name does not start with a '$' + /// sign), ie remove them from GlobalVariableTable. + void clearLocalVars(void); +}; struct FileCheckDiag; @@ -106,27 +133,32 @@ /// 1. std::map VariableDefs; + /// Pointer to a class instance holding a table with the values of live + /// variables at the start of any given CHECK line. + FileCheckPatternContext *Context; + Check::FileCheckType CheckTy; /// Contains the number of line this pattern is in. unsigned LineNumber; public: - explicit FileCheckPattern(Check::FileCheckType Ty) - : CheckTy(Ty) {} + explicit FileCheckPattern(Check::FileCheckType Ty, + FileCheckPatternContext *Context) + : Context(Context), CheckTy(Ty) {} /// Returns the location in source code. SMLoc getLoc() const { return PatternLoc; } + /// Returns the pointer to the global state for all patterns in this + /// FileCheck instance. + FileCheckPatternContext *getContext() const { return Context; } bool ParsePattern(StringRef PatternStr, StringRef Prefix, SourceMgr &SM, unsigned LineNumber, const FileCheckRequest &Req); - size_t Match(StringRef Buffer, size_t &MatchLen, - StringMap &VariableTable) const; - void PrintVariableUses(const SourceMgr &SM, StringRef Buffer, - const StringMap &VariableTable, + size_t match(StringRef Buffer, size_t &MatchLen) const; + void printVariableUses(const SourceMgr &SM, StringRef Buffer, SMRange MatchRange = None) const; - void PrintFuzzyMatch(const SourceMgr &SM, StringRef Buffer, - const StringMap &VariableTable, + void printFuzzyMatch(const SourceMgr &SM, StringRef Buffer, std::vector *Diags) const; bool hasVariable() const { @@ -140,9 +172,7 @@ private: bool AddRegExToRegEx(StringRef RS, unsigned &CurParen, SourceMgr &SM); void AddBackrefToRegEx(unsigned BackrefNum); - unsigned - ComputeMatchDistance(StringRef Buffer, - const StringMap &VariableTable) const; + unsigned computeMatchDistance(StringRef Buffer) const; bool EvaluateExpression(StringRef Expr, std::string &Value) const; size_t FindRegexVarEnd(StringRef Str, SourceMgr &SM); }; @@ -223,19 +253,17 @@ : Pat(P), Prefix(S), Loc(L) {} size_t Check(const SourceMgr &SM, StringRef Buffer, bool IsLabelScanMode, - size_t &MatchLen, StringMap &VariableTable, - FileCheckRequest &Req, std::vector *Diags) const; + size_t &MatchLen, FileCheckRequest &Req, + std::vector *Diags) const; bool CheckNext(const SourceMgr &SM, StringRef Buffer) const; bool CheckSame(const SourceMgr &SM, StringRef Buffer) const; bool CheckNot(const SourceMgr &SM, StringRef Buffer, const std::vector &NotStrings, - StringMap &VariableTable, const FileCheckRequest &Req, std::vector *Diags) const; size_t CheckDag(const SourceMgr &SM, StringRef Buffer, std::vector &NotStrings, - StringMap &VariableTable, const FileCheckRequest &Req, std::vector *Diags) const; }; 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 @@ -269,10 +269,9 @@ /// there is a match, the size of the matched string is returned in \p /// MatchLen. /// -/// The \p VariableTable StringMap provides the current values of filecheck +/// The GlobalVariableTable StringMap provides the current values of FileCheck /// variables and is updated if this match defines new values. -size_t FileCheckPattern::Match(StringRef Buffer, size_t &MatchLen, - StringMap &VariableTable) const { +size_t FileCheckPattern::match(StringRef Buffer, size_t &MatchLen) const { // If this is the EOF pattern, match it immediately. if (CheckTy == Check::CheckEOF) { MatchLen = 0; @@ -302,14 +301,14 @@ if (!EvaluateExpression(VariableUse.first, Value)) return StringRef::npos; } else { - StringMap::iterator it = - VariableTable.find(VariableUse.first); + llvm::Optional OptValue = + Context->getPatternVarValue(VariableUse.first); // If the variable is undefined, return an error. - if (it == VariableTable.end()) + if (!OptValue.hasValue()) return StringRef::npos; // Look up the value and escape it so that we can put it into the regex. - Value += Regex::escape(it->second); + Value += Regex::escape(OptValue.getValue()); } // Plop it into the regex at the adjusted offset. @@ -333,7 +332,8 @@ // If this defines any variables, remember their values. for (const auto &VariableDef : VariableDefs) { assert(VariableDef.second < MatchInfo.size() && "Internal paren error"); - VariableTable[VariableDef.first] = MatchInfo[VariableDef.second]; + Context->GlobalVariableTable[VariableDef.first] = + MatchInfo[VariableDef.second]; } // Like CHECK-NEXT, CHECK-EMPTY's match range is considered to start after @@ -344,13 +344,10 @@ return FullMatch.data() - Buffer.data() + MatchStartSkip; } - /// Computes an arbitrary estimate for the quality of matching this pattern at /// the start of \p Buffer; a distance of zero should correspond to a perfect /// match. -unsigned -FileCheckPattern::ComputeMatchDistance(StringRef Buffer, - const StringMap &VariableTable) const { +unsigned FileCheckPattern::computeMatchDistance(StringRef Buffer) const { // Just compute the number of matching characters. For regular expressions, we // just compare against the regex itself and hope for the best. // @@ -367,9 +364,10 @@ return BufferPrefix.edit_distance(ExampleString); } -void FileCheckPattern::PrintVariableUses(const SourceMgr &SM, StringRef Buffer, - const StringMap &VariableTable, - SMRange MatchRange) const { +/// Print value of successful substitutions or name of undefined pattern +/// variables preventing such a successful substitution. +void FileCheckPattern::printVariableUses(const SourceMgr &SM, StringRef Buffer, + SMRange MatchRange) const { // If this was a regular expression using variables, print the current // variable values. if (!VariableUses.empty()) { @@ -388,16 +386,16 @@ OS.write_escaped(Var) << "\""; } } else { - StringMap::const_iterator it = VariableTable.find(Var); + llvm::Optional OptValue = Context->getPatternVarValue(Var); // Check for undefined variable references. - if (it == VariableTable.end()) { + if (!OptValue.hasValue()) { OS << "uses undefined variable \""; OS.write_escaped(Var) << "\""; } else { OS << "with variable \""; OS.write_escaped(Var) << "\" equal to \""; - OS.write_escaped(it->second) << "\""; + OS.write_escaped(OptValue.getValue()) << "\""; } } @@ -429,9 +427,8 @@ return Range; } -void FileCheckPattern::PrintFuzzyMatch( +void FileCheckPattern::printFuzzyMatch( const SourceMgr &SM, StringRef Buffer, - const StringMap &VariableTable, std::vector *Diags) const { // Attempt to find the closest/best fuzzy match. Usually an error happens // because some string in the output didn't exactly match. In these cases, we @@ -453,7 +450,7 @@ // Compute the "quality" of this match as an arbitrary combination of the // match distance and the number of lines skipped to get to this match. - unsigned Distance = ComputeMatchDistance(Buffer.substr(i), VariableTable); + unsigned Distance = computeMatchDistance(Buffer.substr(i)); double Quality = Distance + (NumLinesForward / 100.); if (Quality < BestQuality || Best == StringRef::npos) { @@ -477,6 +474,17 @@ } } +/// Return the value of variable \p VarName or nothing if no such variable +/// has been defined. +llvm::Optional +FileCheckPatternContext::getPatternVarValue(StringRef VarName) { + auto git = GlobalVariableTable.find(VarName); + if (git == GlobalVariableTable.end()) + return llvm::None; + + return git->second; +} + /// Finds the closing sequence of a regex variable usage or definition. /// /// \p Str has to point in the beginning of the definition (right after the @@ -750,6 +758,9 @@ bool llvm::FileCheck::ReadCheckFile(SourceMgr &SM, StringRef Buffer, Regex &PrefixRE, std::vector &CheckStrings) { + auto *PatternContext = new FileCheckPatternContext(); + PatternContext->defineCmdlineVariables(Req.GlobalDefines); + std::vector ImplicitNegativeChecks; for (const auto &PatternString : Req.ImplicitCheckNot) { // Create a buffer with fake command line content in order to display the @@ -763,7 +774,8 @@ CmdLine->getBuffer().substr(Prefix.size(), PatternString.size()); SM.AddNewSourceBuffer(std::move(CmdLine), SMLoc()); - ImplicitNegativeChecks.push_back(FileCheckPattern(Check::CheckNot)); + ImplicitNegativeChecks.push_back( + FileCheckPattern(Check::CheckNot, PatternContext)); ImplicitNegativeChecks.back().ParsePattern(PatternInBuffer, "IMPLICIT-CHECK", SM, 0, Req); } @@ -826,7 +838,7 @@ SMLoc PatternLoc = SMLoc::getFromPointer(Buffer.data()); // Parse the pattern. - FileCheckPattern P(CheckTy); + FileCheckPattern P(CheckTy, PatternContext); if (P.ParsePattern(Buffer.substr(0, EOL), UsedPrefix, SM, LineNumber, Req)) return true; @@ -870,7 +882,8 @@ // Add an EOF pattern for any trailing CHECK-DAG/-NOTs, and use the first // prefix as a filler for the error message. if (!DagNotMatches.empty()) { - CheckStrings.emplace_back(FileCheckPattern(Check::CheckEOF), *Req.CheckPrefixes.begin(), + CheckStrings.emplace_back(FileCheckPattern(Check::CheckEOF, PatternContext), + *Req.CheckPrefixes.begin(), SMLoc::getFromPointer(Buffer.data())); std::swap(DagNotMatches, CheckStrings.back().DagNotStrings); } @@ -896,8 +909,7 @@ static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM, StringRef Prefix, SMLoc Loc, const FileCheckPattern &Pat, - int MatchedCount, StringRef Buffer, - StringMap &VariableTable, size_t MatchPos, + int MatchedCount, StringRef Buffer, size_t MatchPos, size_t MatchLen, const FileCheckRequest &Req, std::vector *Diags) { bool PrintDiag = true; @@ -929,24 +941,22 @@ Loc, ExpectedMatch ? SourceMgr::DK_Remark : SourceMgr::DK_Error, Message); SM.PrintMessage(MatchRange.Start, SourceMgr::DK_Note, "found here", {MatchRange}); - Pat.PrintVariableUses(SM, Buffer, VariableTable, MatchRange); + Pat.printVariableUses(SM, Buffer, MatchRange); } static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM, const FileCheckString &CheckStr, int MatchedCount, - StringRef Buffer, StringMap &VariableTable, - size_t MatchPos, size_t MatchLen, FileCheckRequest &Req, + StringRef Buffer, size_t MatchPos, size_t MatchLen, + FileCheckRequest &Req, std::vector *Diags) { PrintMatch(ExpectedMatch, SM, CheckStr.Prefix, CheckStr.Loc, CheckStr.Pat, - MatchedCount, Buffer, VariableTable, MatchPos, MatchLen, Req, - Diags); + MatchedCount, Buffer, MatchPos, MatchLen, Req, Diags); } static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM, StringRef Prefix, SMLoc Loc, const FileCheckPattern &Pat, int MatchedCount, - StringRef Buffer, StringMap &VariableTable, - bool VerboseVerbose, + StringRef Buffer, bool VerboseVerbose, std::vector *Diags) { bool PrintDiag = true; if (!ExpectedMatch) { @@ -982,19 +992,18 @@ SM.PrintMessage(SearchRange.Start, SourceMgr::DK_Note, "scanning from here"); // Allow the pattern to print additional information if desired. - Pat.PrintVariableUses(SM, Buffer, VariableTable); + Pat.printVariableUses(SM, Buffer); if (ExpectedMatch) - Pat.PrintFuzzyMatch(SM, Buffer, VariableTable, Diags); + Pat.printFuzzyMatch(SM, Buffer, Diags); } static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM, const FileCheckString &CheckStr, int MatchedCount, - StringRef Buffer, StringMap &VariableTable, - bool VerboseVerbose, + StringRef Buffer, bool VerboseVerbose, std::vector *Diags) { PrintNoMatch(ExpectedMatch, SM, CheckStr.Prefix, CheckStr.Loc, CheckStr.Pat, - MatchedCount, Buffer, VariableTable, VerboseVerbose, Diags); + MatchedCount, Buffer, VerboseVerbose, Diags); } /// Count the number of newlines in the specified range. @@ -1023,7 +1032,6 @@ /// Match check string and its "not strings" and/or "dag strings". size_t FileCheckString::Check(const SourceMgr &SM, StringRef Buffer, bool IsLabelScanMode, size_t &MatchLen, - StringMap &VariableTable, FileCheckRequest &Req, std::vector *Diags) const { size_t LastPos = 0; @@ -1035,7 +1043,7 @@ // over the block again (including the last CHECK-LABEL) in normal mode. if (!IsLabelScanMode) { // Match "dag strings" (with mixed "not strings" if any). - LastPos = CheckDag(SM, Buffer, NotStrings, VariableTable, Req, Diags); + LastPos = CheckDag(SM, Buffer, NotStrings, Req, Diags); if (LastPos == StringRef::npos) return StringRef::npos; } @@ -1050,18 +1058,17 @@ StringRef MatchBuffer = Buffer.substr(LastMatchEnd); size_t CurrentMatchLen; // get a match at current start point - size_t MatchPos = Pat.Match(MatchBuffer, CurrentMatchLen, VariableTable); + size_t MatchPos = Pat.match(MatchBuffer, CurrentMatchLen); if (i == 1) FirstMatchPos = LastPos + MatchPos; // report if (MatchPos == StringRef::npos) { - PrintNoMatch(true, SM, *this, i, MatchBuffer, VariableTable, - Req.VerboseVerbose, Diags); + PrintNoMatch(true, SM, *this, i, MatchBuffer, Req.VerboseVerbose, Diags); return StringRef::npos; } - PrintMatch(true, SM, *this, i, MatchBuffer, VariableTable, MatchPos, - CurrentMatchLen, Req, Diags); + PrintMatch(true, SM, *this, i, MatchBuffer, MatchPos, CurrentMatchLen, Req, + Diags); // move start point after the match LastMatchEnd += MatchPos + CurrentMatchLen; @@ -1096,7 +1103,7 @@ // If this match had "not strings", verify that they don't exist in the // skipped region. - if (CheckNot(SM, SkippedRegion, NotStrings, VariableTable, Req, Diags)) + if (CheckNot(SM, SkippedRegion, NotStrings, Req, Diags)) return StringRef::npos; } @@ -1170,22 +1177,21 @@ bool FileCheckString::CheckNot( const SourceMgr &SM, StringRef Buffer, const std::vector &NotStrings, - StringMap &VariableTable, const FileCheckRequest &Req, - std::vector *Diags) const { + const FileCheckRequest &Req, std::vector *Diags) const { for (const FileCheckPattern *Pat : NotStrings) { assert((Pat->getCheckTy() == Check::CheckNot) && "Expect CHECK-NOT!"); size_t MatchLen = 0; - size_t Pos = Pat->Match(Buffer, MatchLen, VariableTable); + size_t Pos = Pat->match(Buffer, MatchLen); if (Pos == StringRef::npos) { PrintNoMatch(false, SM, Prefix, Pat->getLoc(), *Pat, 1, Buffer, - VariableTable, Req.VerboseVerbose, Diags); + Req.VerboseVerbose, Diags); continue; } - PrintMatch(false, SM, Prefix, Pat->getLoc(), *Pat, 1, Buffer, VariableTable, - Pos, MatchLen, Req, Diags); + PrintMatch(false, SM, Prefix, Pat->getLoc(), *Pat, 1, Buffer, Pos, MatchLen, + Req, Diags); return true; } @@ -1197,7 +1203,6 @@ size_t FileCheckString::CheckDag(const SourceMgr &SM, StringRef Buffer, std::vector &NotStrings, - StringMap &VariableTable, const FileCheckRequest &Req, std::vector *Diags) const { if (DagNotStrings.empty()) @@ -1238,19 +1243,19 @@ // CHECK-DAG group. for (auto MI = MatchRanges.begin(), ME = MatchRanges.end(); true; ++MI) { StringRef MatchBuffer = Buffer.substr(MatchPos); - size_t MatchPosBuf = Pat.Match(MatchBuffer, MatchLen, VariableTable); + size_t MatchPosBuf = Pat.match(MatchBuffer, MatchLen); // 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, 1, MatchBuffer, - VariableTable, Req.VerboseVerbose, Diags); + Req.VerboseVerbose, Diags); 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, 1, Buffer, - VariableTable, MatchPos, MatchLen, Req, Diags); + PrintMatch(true, SM, Prefix, Pat.getLoc(), Pat, 1, Buffer, MatchPos, + MatchLen, Req, Diags); MatchRange M{MatchPos, MatchPos + MatchLen}; if (Req.AllowDeprecatedDagOverlap) { // We don't need to track all matches in this mode, so we just maintain @@ -1297,8 +1302,8 @@ MatchPos = MI->End; } if (!Req.VerboseVerbose) - PrintMatch(true, SM, Prefix, Pat.getLoc(), Pat, 1, Buffer, VariableTable, - MatchPos, MatchLen, Req, Diags); + PrintMatch(true, SM, Prefix, Pat.getLoc(), Pat, 1, Buffer, MatchPos, + MatchLen, Req, Diags); // Handle the end of a CHECK-DAG group. if (std::next(PatItr) == PatEnd || @@ -1309,7 +1314,7 @@ // region. StringRef SkippedRegion = Buffer.slice(StartPos, MatchRanges.begin()->Pos); - if (CheckNot(SM, SkippedRegion, NotStrings, VariableTable, Req, Diags)) + if (CheckNot(SM, SkippedRegion, NotStrings, Req, Diags)) return StringRef::npos; // Clear "not strings". NotStrings.clear(); @@ -1373,16 +1378,30 @@ return Regex(PrefixRegexStr); } -// Remove local variables from \p VariableTable. Global variables -// (start with '$') are preserved. -static void ClearLocalVars(StringMap &VariableTable) { - SmallVector LocalVars; - for (const auto &Var : VariableTable) +/// Define pattern and numeric variables from definitions given on the command +/// line passed as a vector of VAR=VAL strings in \p CmdlineDefines. +void FileCheckPatternContext::defineCmdlineVariables( + std::vector &CmdlineDefines) { + static bool CmdlineVariablesDefined = false; + + if (CmdlineVariablesDefined) + return; + CmdlineVariablesDefined = true; + + for (const auto &CmdlineDef : CmdlineDefines) + GlobalVariableTable.insert(StringRef(CmdlineDef).split('=')); +} + +/// Undefine local variables (variables whose name does not start with a '$' +/// sign), ie remove them from GlobalVariableTable. +void FileCheckPatternContext::clearLocalVars(void) { + SmallVector LocalPatternVars, LocalNumericVars; + for (const auto &Var : GlobalVariableTable) if (Var.first()[0] != '$') - LocalVars.push_back(Var.first()); + LocalPatternVars.push_back(Var.first()); - for (const auto &Var : LocalVars) - VariableTable.erase(Var); + for (const auto &Var : LocalPatternVars) + GlobalVariableTable.erase(Var); } /// Check the input to FileCheck provided in the \p Buffer against the \p @@ -1394,12 +1413,8 @@ std::vector *Diags) { bool ChecksFailed = false; - /// VariableTable - This holds all the current filecheck variables. - StringMap VariableTable; - - for (const auto& Def : Req.GlobalDefines) - VariableTable.insert(StringRef(Def).split('=')); - + FileCheckPatternContext *Context = + CheckStrings.empty() ? nullptr : CheckStrings[0].Pat.getContext(); unsigned i = 0, j = 0, e = CheckStrings.size(); while (true) { StringRef CheckRegion; @@ -1414,10 +1429,10 @@ // 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, Req, Diags); + size_t MatchLabelPos = + CheckLabelStr.Check(SM, Buffer, true, MatchLabelLen, Req, Diags); if (MatchLabelPos == StringRef::npos) - // Immediately bail of CHECK-LABEL fails, nothing else we can do. + // Immediately bail if CHECK-LABEL fails, nothing else we can do. return false; CheckRegion = Buffer.substr(0, MatchLabelPos + MatchLabelLen); @@ -1425,8 +1440,8 @@ ++j; } - if (Req.EnableVarScope) - ClearLocalVars(VariableTable); + if (Req.EnableVarScope && Context != nullptr) + Context->clearLocalVars(); for (; i != j; ++i) { const FileCheckString &CheckStr = CheckStrings[i]; @@ -1434,8 +1449,8 @@ // 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, Req, Diags); + size_t MatchPos = + CheckStr.Check(SM, CheckRegion, false, MatchLen, Req, Diags); if (MatchPos == StringRef::npos) { ChecksFailed = true; @@ -1449,6 +1464,7 @@ if (j == e) break; } + delete Context; // Success if no checks failed. return !ChecksFailed;