//===- FileCheck.cpp - Check that File's Contents match what is expected --===// | |
// | |
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
// See https://llvm.org/LICENSE.txt for license information. | |
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
// | |
//===----------------------------------------------------------------------===// | |
// | |
// FileCheck does a line-by line check of a file that validates whether it | |
// contains the expected content. This is useful for regression tests etc. | |
// | |
// This file implements most of the API that will be used by the FileCheck utility | |
// as well as various unittests. | |
//===----------------------------------------------------------------------===// | |
#include "llvm/Support/FileCheck.h" | |
#include "FileCheckImpl.h" | |
#include "llvm/ADT/StringSet.h" | |
#include "llvm/ADT/Twine.h" | |
#include "llvm/Support/FormatVariadic.h" | |
#include <cstdint> | |
#include <list> | |
#include <tuple> | |
#include <utility> | |
using namespace llvm; | |
Expected<uint64_t> FileCheckNumericVariableUse::eval() const { | |
Optional<uint64_t> Value = NumericVariable->getValue(); | |
if (Value) | |
return *Value; | |
return make_error<FileCheckUndefVarError>(Name); | |
} | |
Expected<uint64_t> FileCheckASTBinop::eval() const { | |
Expected<uint64_t> LeftOp = LeftOperand->eval(); | |
Expected<uint64_t> RightOp = RightOperand->eval(); | |
// Bubble up any error (e.g. undefined variables) in the recursive | |
// evaluation. | |
if (!LeftOp || !RightOp) { | |
Error Err = Error::success(); | |
if (!LeftOp) | |
Err = joinErrors(std::move(Err), LeftOp.takeError()); | |
if (!RightOp) | |
Err = joinErrors(std::move(Err), RightOp.takeError()); | |
return std::move(Err); | |
} | |
return EvalBinop(*LeftOp, *RightOp); | |
} | |
Expected<std::string> FileCheckNumericSubstitution::getResult() const { | |
Expected<uint64_t> EvaluatedValue = ExpressionAST->eval(); | |
if (!EvaluatedValue) | |
return EvaluatedValue.takeError(); | |
return utostr(*EvaluatedValue); | |
} | |
Expected<std::string> FileCheckStringSubstitution::getResult() const { | |
// Look up the value and escape it so that we can put it into the regex. | |
Expected<StringRef> VarVal = Context->getPatternVarValue(FromStr); | |
if (!VarVal) | |
return VarVal.takeError(); | |
return Regex::escape(*VarVal); | |
} | |
bool FileCheckPattern::isValidVarNameStart(char C) { | |
return C == '_' || isalpha(C); | |
} | |
Expected<FileCheckPattern::VariableProperties> | |
FileCheckPattern::parseVariable(StringRef &Str, const SourceMgr &SM) { | |
if (Str.empty()) | |
return FileCheckErrorDiagnostic::get(SM, Str, "empty variable name"); | |
bool ParsedOneChar = false; | |
unsigned I = 0; | |
bool IsPseudo = Str[0] == '@'; | |
// Global vars start with '$'. | |
if (Str[0] == '$' || IsPseudo) | |
++I; | |
for (unsigned E = Str.size(); I != E; ++I) { | |
if (!ParsedOneChar && !isValidVarNameStart(Str[I])) | |
return FileCheckErrorDiagnostic::get(SM, Str, "invalid variable name"); | |
// Variable names are composed of alphanumeric characters and underscores. | |
if (Str[I] != '_' && !isalnum(Str[I])) | |
break; | |
ParsedOneChar = true; | |
} | |
StringRef Name = Str.take_front(I); | |
Str = Str.substr(I); | |
return VariableProperties {Name, IsPseudo}; | |
} | |
// StringRef holding all characters considered as horizontal whitespaces by | |
// FileCheck input canonicalization. | |
constexpr StringLiteral SpaceChars = " \t"; | |
// Parsing helper function that strips the first character in S and returns it. | |
static char popFront(StringRef &S) { | |
char C = S.front(); | |
S = S.drop_front(); | |
return C; | |
} | |
char FileCheckUndefVarError::ID = 0; | |
char FileCheckErrorDiagnostic::ID = 0; | |
char FileCheckNotFoundError::ID = 0; | |
Expected<FileCheckNumericVariable *> | |
FileCheckPattern::parseNumericVariableDefinition( | |
StringRef &Expr, FileCheckPatternContext *Context, | |
Optional<size_t> LineNumber, const SourceMgr &SM) { | |
Expected<VariableProperties> ParseVarResult = parseVariable(Expr, SM); | |
if (!ParseVarResult) | |
return ParseVarResult.takeError(); | |
StringRef Name = ParseVarResult->Name; | |
if (ParseVarResult->IsPseudo) | |
return FileCheckErrorDiagnostic::get( | |
SM, Name, "definition of pseudo numeric variable unsupported"); | |
// Detect collisions between string and numeric variables when the latter | |
// is created later than the former. | |
if (Context->DefinedVariableTable.find(Name) != | |
Context->DefinedVariableTable.end()) | |
return FileCheckErrorDiagnostic::get( | |
SM, Name, "string variable with name '" + Name + "' already exists"); | |
Expr = Expr.ltrim(SpaceChars); | |
if (!Expr.empty()) | |
return FileCheckErrorDiagnostic::get( | |
SM, Expr, "unexpected characters after numeric variable name"); | |
FileCheckNumericVariable *DefinedNumericVariable; | |
auto VarTableIter = Context->GlobalNumericVariableTable.find(Name); | |
if (VarTableIter != Context->GlobalNumericVariableTable.end()) | |
DefinedNumericVariable = VarTableIter->second; | |
else | |
DefinedNumericVariable = Context->makeNumericVariable(Name, LineNumber); | |
return DefinedNumericVariable; | |
} | |
Expected<std::unique_ptr<FileCheckNumericVariableUse>> | |
FileCheckPattern::parseNumericVariableUse(StringRef Name, bool IsPseudo, | |
Optional<size_t> LineNumber, | |
FileCheckPatternContext *Context, | |
const SourceMgr &SM) { | |
if (IsPseudo && !Name.equals("@LINE")) | |
return FileCheckErrorDiagnostic::get( | |
SM, Name, "invalid pseudo numeric variable '" + Name + "'"); | |
// Numeric variable definitions and uses are parsed in the order in which | |
// they appear in the CHECK patterns. For each definition, the pointer to the | |
// class instance of the corresponding numeric variable definition is stored | |
// in GlobalNumericVariableTable in parsePattern. Therefore, if the pointer | |
// we get below is null, it means no such variable was defined before. When | |
// that happens, we create a dummy variable so that parsing can continue. All | |
// uses of undefined variables, whether string or numeric, are then diagnosed | |
// in printSubstitutions() after failing to match. | |
auto VarTableIter = Context->GlobalNumericVariableTable.find(Name); | |
FileCheckNumericVariable *NumericVariable; | |
if (VarTableIter != Context->GlobalNumericVariableTable.end()) | |
NumericVariable = VarTableIter->second; | |
else { | |
NumericVariable = Context->makeNumericVariable(Name); | |
Context->GlobalNumericVariableTable[Name] = NumericVariable; | |
} | |
Optional<size_t> DefLineNumber = NumericVariable->getDefLineNumber(); | |
if (DefLineNumber && LineNumber && *DefLineNumber == *LineNumber) | |
return FileCheckErrorDiagnostic::get( | |
SM, Name, | |
"numeric variable '" + Name + | |
"' defined earlier in the same CHECK directive"); | |
return std::make_unique<FileCheckNumericVariableUse>(Name, NumericVariable); | |
} | |
Expected<std::unique_ptr<FileCheckExpressionAST>> | |
FileCheckPattern::parseNumericOperand(StringRef &Expr, AllowedOperand AO, | |
Optional<size_t> LineNumber, | |
FileCheckPatternContext *Context, | |
const SourceMgr &SM) { | |
if (AO == AllowedOperand::LineVar || AO == AllowedOperand::Any) { | |
// Try to parse as a numeric variable use. | |
Expected<FileCheckPattern::VariableProperties> ParseVarResult = | |
parseVariable(Expr, SM); | |
if (ParseVarResult) | |
return parseNumericVariableUse(ParseVarResult->Name, | |
ParseVarResult->IsPseudo, LineNumber, | |
Context, SM); | |
if (AO == AllowedOperand::LineVar) | |
return ParseVarResult.takeError(); | |
// Ignore the error and retry parsing as a literal. | |
consumeError(ParseVarResult.takeError()); | |
} | |
// Otherwise, parse it as a literal. | |
uint64_t LiteralValue; | |
if (!Expr.consumeInteger(/*Radix=*/10, LiteralValue)) | |
return std::make_unique<FileCheckExpressionLiteral>(LiteralValue); | |
return FileCheckErrorDiagnostic::get(SM, Expr, | |
"invalid operand format '" + Expr + "'"); | |
} | |
static uint64_t add(uint64_t LeftOp, uint64_t RightOp) { | |
return LeftOp + RightOp; | |
} | |
static uint64_t sub(uint64_t LeftOp, uint64_t RightOp) { | |
return LeftOp - RightOp; | |
} | |
Expected<std::unique_ptr<FileCheckExpressionAST>> FileCheckPattern::parseBinop( | |
StringRef &Expr, std::unique_ptr<FileCheckExpressionAST> LeftOp, | |
bool IsLegacyLineExpr, Optional<size_t> LineNumber, | |
FileCheckPatternContext *Context, const SourceMgr &SM) { | |
Expr = Expr.ltrim(SpaceChars); | |
if (Expr.empty()) | |
return std::move(LeftOp); | |
// Check if this is a supported operation and select a function to perform | |
// it. | |
SMLoc OpLoc = SMLoc::getFromPointer(Expr.data()); | |
char Operator = popFront(Expr); | |
binop_eval_t EvalBinop; | |
switch (Operator) { | |
case '+': | |
EvalBinop = add; | |
break; | |
case '-': | |
EvalBinop = sub; | |
break; | |
default: | |
return FileCheckErrorDiagnostic::get( | |
SM, OpLoc, Twine("unsupported operation '") + Twine(Operator) + "'"); | |
} | |
// Parse right operand. | |
Expr = Expr.ltrim(SpaceChars); | |
if (Expr.empty()) | |
return FileCheckErrorDiagnostic::get(SM, Expr, | |
"missing operand in expression"); | |
// The second operand in a legacy @LINE expression is always a literal. | |
AllowedOperand AO = | |
IsLegacyLineExpr ? AllowedOperand::Literal : AllowedOperand::Any; | |
Expected<std::unique_ptr<FileCheckExpressionAST>> RightOpResult = | |
parseNumericOperand(Expr, AO, LineNumber, Context, SM); | |
if (!RightOpResult) | |
return RightOpResult; | |
Expr = Expr.ltrim(SpaceChars); | |
return std::make_unique<FileCheckASTBinop>(EvalBinop, std::move(LeftOp), | |
std::move(*RightOpResult)); | |
} | |
Expected<std::unique_ptr<FileCheckExpressionAST>> | |
FileCheckPattern::parseNumericSubstitutionBlock( | |
StringRef Expr, | |
Optional<FileCheckNumericVariable *> &DefinedNumericVariable, | |
bool IsLegacyLineExpr, Optional<size_t> LineNumber, | |
FileCheckPatternContext *Context, const SourceMgr &SM) { | |
std::unique_ptr<FileCheckExpressionAST> ExpressionAST = nullptr; | |
StringRef DefExpr = StringRef(); | |
DefinedNumericVariable = None; | |
// Save variable definition expression if any. | |
size_t DefEnd = Expr.find(':'); | |
if (DefEnd != StringRef::npos) { | |
DefExpr = Expr.substr(0, DefEnd); | |
Expr = Expr.substr(DefEnd + 1); | |
} | |
// Parse the expression itself. | |
Expr = Expr.ltrim(SpaceChars); | |
if (!Expr.empty()) { | |
// The first operand in a legacy @LINE expression is always the @LINE | |
// pseudo variable. | |
AllowedOperand AO = | |
IsLegacyLineExpr ? AllowedOperand::LineVar : AllowedOperand::Any; | |
Expected<std::unique_ptr<FileCheckExpressionAST>> ParseResult = | |
parseNumericOperand(Expr, AO, LineNumber, Context, SM); | |
while (ParseResult && !Expr.empty()) { | |
ParseResult = parseBinop(Expr, std::move(*ParseResult), IsLegacyLineExpr, | |
LineNumber, Context, SM); | |
// Legacy @LINE expressions only allow 2 operands. | |
if (ParseResult && IsLegacyLineExpr && !Expr.empty()) | |
return FileCheckErrorDiagnostic::get( | |
SM, Expr, | |
"unexpected characters at end of expression '" + Expr + "'"); | |
} | |
if (!ParseResult) | |
return ParseResult; | |
ExpressionAST = std::move(*ParseResult); | |
} | |
// Parse the numeric variable definition. | |
if (DefEnd != StringRef::npos) { | |
DefExpr = DefExpr.ltrim(SpaceChars); | |
Expected<FileCheckNumericVariable *> ParseResult = | |
parseNumericVariableDefinition(DefExpr, Context, LineNumber, SM); | |
if (!ParseResult) | |
return ParseResult.takeError(); | |
DefinedNumericVariable = *ParseResult; | |
} | |
return std::move(ExpressionAST); | |
} | |
bool FileCheckPattern::parsePattern(StringRef PatternStr, StringRef Prefix, | |
SourceMgr &SM, | |
const FileCheckRequest &Req) { | |
bool MatchFullLinesHere = Req.MatchFullLines && CheckTy != Check::CheckNot; | |
IgnoreCase = Req.IgnoreCase; | |
PatternLoc = SMLoc::getFromPointer(PatternStr.data()); | |
if (!(Req.NoCanonicalizeWhiteSpace && Req.MatchFullLines)) | |
// Ignore trailing whitespace. | |
while (!PatternStr.empty() && | |
(PatternStr.back() == ' ' || PatternStr.back() == '\t')) | |
PatternStr = PatternStr.substr(0, PatternStr.size() - 1); | |
// Check that there is something on the line. | |
if (PatternStr.empty() && CheckTy != Check::CheckEmpty) { | |
SM.PrintMessage(PatternLoc, SourceMgr::DK_Error, | |
"found empty check string with prefix '" + Prefix + ":'"); | |
return true; | |
} | |
if (!PatternStr.empty() && CheckTy == Check::CheckEmpty) { | |
SM.PrintMessage( | |
PatternLoc, SourceMgr::DK_Error, | |
"found non-empty check string for empty check with prefix '" + Prefix + | |
":'"); | |
return true; | |
} | |
if (CheckTy == Check::CheckEmpty) { | |
RegExStr = "(\n$)"; | |
return false; | |
} | |
// Check to see if this is a fixed string, or if it has regex pieces. | |
if (!MatchFullLinesHere && | |
(PatternStr.size() < 2 || (PatternStr.find("{{") == StringRef::npos && | |
PatternStr.find("[[") == StringRef::npos))) { | |
FixedStr = PatternStr; | |
return false; | |
} | |
if (MatchFullLinesHere) { | |
RegExStr += '^'; | |
if (!Req.NoCanonicalizeWhiteSpace) | |
RegExStr += " *"; | |
} | |
// Paren value #0 is for the fully matched string. Any new parenthesized | |
// values add from there. | |
unsigned CurParen = 1; | |
// Otherwise, there is at least one regex piece. Build up the regex pattern | |
// by escaping scary characters in fixed strings, building up one big regex. | |
while (!PatternStr.empty()) { | |
// RegEx matches. | |
if (PatternStr.startswith("{{")) { | |
// This is the start of a regex match. Scan for the }}. | |
size_t End = PatternStr.find("}}"); | |
if (End == StringRef::npos) { | |
SM.PrintMessage(SMLoc::getFromPointer(PatternStr.data()), | |
SourceMgr::DK_Error, | |
"found start of regex string with no end '}}'"); | |
return true; | |
} | |
// Enclose {{}} patterns in parens just like [[]] even though we're not | |
// capturing the result for any purpose. This is required in case the | |
// expression contains an alternation like: CHECK: abc{{x|z}}def. We | |
// want this to turn into: "abc(x|z)def" not "abcx|zdef". | |
RegExStr += '('; | |
++CurParen; | |
if (AddRegExToRegEx(PatternStr.substr(2, End - 2), CurParen, SM)) | |
return true; | |
RegExStr += ')'; | |
PatternStr = PatternStr.substr(End + 2); | |
continue; | |
} | |
// String and numeric substitution blocks. Pattern substitution blocks come | |
// in two forms: [[foo:.*]] and [[foo]]. The former matches .* (or some | |
// other regex) and assigns it to the string variable 'foo'. The latter | |
// substitutes foo's value. Numeric substitution blocks recognize the same | |
// form as string ones, but start with a '#' sign after the double | |
// brackets. They also accept a combined form which sets a numeric variable | |
// to the evaluation of an expression. Both string and numeric variable | |
// names must satisfy the regular expression "[a-zA-Z_][0-9a-zA-Z_]*" to be | |
// valid, as this helps catch some common errors. | |
if (PatternStr.startswith("[[")) { | |
StringRef UnparsedPatternStr = PatternStr.substr(2); | |
// Find the closing bracket pair ending the match. End is going to be an | |
// offset relative to the beginning of the match string. | |
size_t End = FindRegexVarEnd(UnparsedPatternStr, SM); | |
StringRef MatchStr = UnparsedPatternStr.substr(0, End); | |
bool IsNumBlock = MatchStr.consume_front("#"); | |
if (End == StringRef::npos) { | |
SM.PrintMessage(SMLoc::getFromPointer(PatternStr.data()), | |
SourceMgr::DK_Error, | |
"Invalid substitution block, no ]] found"); | |
return true; | |
} | |
// Strip the substitution block we are parsing. End points to the start | |
// of the "]]" closing the expression so account for it in computing the | |
// index of the first unparsed character. | |
PatternStr = UnparsedPatternStr.substr(End + 2); | |
bool IsDefinition = false; | |
bool SubstNeeded = false; | |
// Whether the substitution block is a legacy use of @LINE with string | |
// substitution block syntax. | |
bool IsLegacyLineExpr = false; | |
StringRef DefName; | |
StringRef SubstStr; | |
StringRef MatchRegexp; | |
size_t SubstInsertIdx = RegExStr.size(); | |
// Parse string variable or legacy @LINE expression. | |
if (!IsNumBlock) { | |
size_t VarEndIdx = MatchStr.find(":"); | |
size_t SpacePos = MatchStr.substr(0, VarEndIdx).find_first_of(" \t"); | |
if (SpacePos != StringRef::npos) { | |
SM.PrintMessage(SMLoc::getFromPointer(MatchStr.data() + SpacePos), | |
SourceMgr::DK_Error, "unexpected whitespace"); | |
return true; | |
} | |
// Get the name (e.g. "foo") and verify it is well formed. | |
StringRef OrigMatchStr = MatchStr; | |
Expected<FileCheckPattern::VariableProperties> ParseVarResult = | |
parseVariable(MatchStr, SM); | |
if (!ParseVarResult) { | |
logAllUnhandledErrors(ParseVarResult.takeError(), errs()); | |
return true; | |
} | |
StringRef Name = ParseVarResult->Name; | |
bool IsPseudo = ParseVarResult->IsPseudo; | |
IsDefinition = (VarEndIdx != StringRef::npos); | |
SubstNeeded = !IsDefinition; | |
if (IsDefinition) { | |
if ((IsPseudo || !MatchStr.consume_front(":"))) { | |
SM.PrintMessage(SMLoc::getFromPointer(Name.data()), | |
SourceMgr::DK_Error, | |
"invalid name in string variable definition"); | |
return true; | |
} | |
// Detect collisions between string and numeric variables when the | |
// former is created later than the latter. | |
if (Context->GlobalNumericVariableTable.find(Name) != | |
Context->GlobalNumericVariableTable.end()) { | |
SM.PrintMessage( | |
SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, | |
"numeric variable with name '" + Name + "' already exists"); | |
return true; | |
} | |
DefName = Name; | |
MatchRegexp = MatchStr; | |
} else { | |
if (IsPseudo) { | |
MatchStr = OrigMatchStr; | |
IsLegacyLineExpr = IsNumBlock = true; | |
} else | |
SubstStr = Name; | |
} | |
} | |
// Parse numeric substitution block. | |
std::unique_ptr<FileCheckExpressionAST> ExpressionAST; | |
Optional<FileCheckNumericVariable *> DefinedNumericVariable; | |
if (IsNumBlock) { | |
Expected<std::unique_ptr<FileCheckExpressionAST>> ParseResult = | |
parseNumericSubstitutionBlock(MatchStr, DefinedNumericVariable, | |
IsLegacyLineExpr, LineNumber, Context, | |
SM); | |
if (!ParseResult) { | |
logAllUnhandledErrors(ParseResult.takeError(), errs()); | |
return true; | |
} | |
ExpressionAST = std::move(*ParseResult); | |
SubstNeeded = ExpressionAST != nullptr; | |
if (DefinedNumericVariable) { | |
IsDefinition = true; | |
DefName = (*DefinedNumericVariable)->getName(); | |
} | |
if (SubstNeeded) | |
SubstStr = MatchStr; | |
else | |
MatchRegexp = "[0-9]+"; | |
} | |
// Handle variable definition: [[<def>:(...)]] and [[#(...)<def>:(...)]]. | |
if (IsDefinition) { | |
RegExStr += '('; | |
++SubstInsertIdx; | |
if (IsNumBlock) { | |
FileCheckNumericVariableMatch NumericVariableDefinition = { | |
*DefinedNumericVariable, CurParen}; | |
NumericVariableDefs[DefName] = NumericVariableDefinition; | |
// This store is done here rather than in match() to allow | |
// parseNumericVariableUse() to get the pointer to the class instance | |
// of the right variable definition corresponding to a given numeric | |
// variable use. | |
Context->GlobalNumericVariableTable[DefName] = | |
*DefinedNumericVariable; | |
} else { | |
VariableDefs[DefName] = CurParen; | |
// Mark string variable as defined to detect collisions between | |
// string and numeric variables in parseNumericVariableUse() and | |
// defineCmdlineVariables() when the latter is created later than the | |
// former. We cannot reuse GlobalVariableTable for this by populating | |
// it with an empty string since we would then lose the ability to | |
// detect the use of an undefined variable in match(). | |
Context->DefinedVariableTable[DefName] = true; | |
} | |
++CurParen; | |
} | |
if (!MatchRegexp.empty() && AddRegExToRegEx(MatchRegexp, CurParen, SM)) | |
return true; | |
if (IsDefinition) | |
RegExStr += ')'; | |
// Handle substitutions: [[foo]] and [[#<foo expr>]]. | |
if (SubstNeeded) { | |
// Handle substitution of string variables that were defined earlier on | |
// the same line by emitting a backreference. Expressions do not | |
// support substituting a numeric variable defined on the same line. | |
if (!IsNumBlock && VariableDefs.find(SubstStr) != VariableDefs.end()) { | |
unsigned CaptureParenGroup = VariableDefs[SubstStr]; | |
if (CaptureParenGroup < 1 || CaptureParenGroup > 9) { | |
SM.PrintMessage(SMLoc::getFromPointer(SubstStr.data()), | |
SourceMgr::DK_Error, | |
"Can't back-reference more than 9 variables"); | |
return true; | |
} | |
AddBackrefToRegEx(CaptureParenGroup); | |
} else { | |
// Handle substitution of string variables ([[<var>]]) defined in | |
// previous CHECK patterns, and substitution of expressions. | |
FileCheckSubstitution *Substitution = | |
IsNumBlock | |
? Context->makeNumericSubstitution( | |
SubstStr, std::move(ExpressionAST), SubstInsertIdx) | |
: Context->makeStringSubstitution(SubstStr, SubstInsertIdx); | |
Substitutions.push_back(Substitution); | |
} | |
} | |
} | |
// Handle fixed string matches. | |
// Find the end, which is the start of the next regex. | |
size_t FixedMatchEnd = PatternStr.find("{{"); | |
FixedMatchEnd = std::min(FixedMatchEnd, PatternStr.find("[[")); | |
RegExStr += Regex::escape(PatternStr.substr(0, FixedMatchEnd)); | |
PatternStr = PatternStr.substr(FixedMatchEnd); | |
} | |
if (MatchFullLinesHere) { | |
if (!Req.NoCanonicalizeWhiteSpace) | |
RegExStr += " *"; | |
RegExStr += '$'; | |
} | |
return false; | |
} | |
bool FileCheckPattern::AddRegExToRegEx(StringRef RS, unsigned &CurParen, SourceMgr &SM) { | |
Regex R(RS); | |
std::string Error; | |
if (!R.isValid(Error)) { | |
SM.PrintMessage(SMLoc::getFromPointer(RS.data()), SourceMgr::DK_Error, | |
"invalid regex: " + Error); | |
return true; | |
} | |
RegExStr += RS.str(); | |
CurParen += R.getNumMatches(); | |
return false; | |
} | |
void FileCheckPattern::AddBackrefToRegEx(unsigned BackrefNum) { | |
assert(BackrefNum >= 1 && BackrefNum <= 9 && "Invalid backref number"); | |
std::string Backref = std::string("\\") + std::string(1, '0' + BackrefNum); | |
RegExStr += Backref; | |
} | |
Expected<size_t> FileCheckPattern::match(StringRef Buffer, size_t &MatchLen, | |
const SourceMgr &SM) const { | |
// If this is the EOF pattern, match it immediately. | |
if (CheckTy == Check::CheckEOF) { | |
MatchLen = 0; | |
return Buffer.size(); | |
} | |
// If this is a fixed string pattern, just match it now. | |
if (!FixedStr.empty()) { | |
MatchLen = FixedStr.size(); | |
size_t Pos = IgnoreCase ? Buffer.find_lower(FixedStr) | |
: Buffer.find(FixedStr); | |
if (Pos == StringRef::npos) | |
return make_error<FileCheckNotFoundError>(); | |
return Pos; | |
} | |
// Regex match. | |
// If there are substitutions, we need to create a temporary string with the | |
// actual value. | |
StringRef RegExToMatch = RegExStr; | |
std::string TmpStr; | |
if (!Substitutions.empty()) { | |
TmpStr = RegExStr; | |
if (LineNumber) | |
Context->LineVariable->setValue(*LineNumber); | |
size_t InsertOffset = 0; | |
// Substitute all string variables and expressions whose values are only | |
// now known. Use of string variables defined on the same line are handled | |
// by back-references. | |
for (const auto &Substitution : Substitutions) { | |
// Substitute and check for failure (e.g. use of undefined variable). | |
Expected<std::string> Value = Substitution->getResult(); | |
if (!Value) | |
return Value.takeError(); | |
// Plop it into the regex at the adjusted offset. | |
TmpStr.insert(TmpStr.begin() + Substitution->getIndex() + InsertOffset, | |
Value->begin(), Value->end()); | |
InsertOffset += Value->size(); | |
} | |
// Match the newly constructed regex. | |
RegExToMatch = TmpStr; | |
} | |
SmallVector<StringRef, 4> MatchInfo; | |
unsigned int Flags = Regex::Newline; | |
if (IgnoreCase) | |
Flags |= Regex::IgnoreCase; | |
if (!Regex(RegExToMatch, Flags).match(Buffer, &MatchInfo)) | |
return make_error<FileCheckNotFoundError>(); | |
// Successful regex match. | |
assert(!MatchInfo.empty() && "Didn't get any match"); | |
StringRef FullMatch = MatchInfo[0]; | |
// If this defines any string variables, remember their values. | |
for (const auto &VariableDef : VariableDefs) { | |
assert(VariableDef.second < MatchInfo.size() && "Internal paren error"); | |
Context->GlobalVariableTable[VariableDef.first] = | |
MatchInfo[VariableDef.second]; | |
} | |
// If this defines any numeric variables, remember their values. | |
for (const auto &NumericVariableDef : NumericVariableDefs) { | |
const FileCheckNumericVariableMatch &NumericVariableMatch = | |
NumericVariableDef.getValue(); | |
unsigned CaptureParenGroup = NumericVariableMatch.CaptureParenGroup; | |
assert(CaptureParenGroup < MatchInfo.size() && "Internal paren error"); | |
FileCheckNumericVariable *DefinedNumericVariable = | |
NumericVariableMatch.DefinedNumericVariable; | |
StringRef MatchedValue = MatchInfo[CaptureParenGroup]; | |
uint64_t Val; | |
if (MatchedValue.getAsInteger(10, Val)) | |
return FileCheckErrorDiagnostic::get(SM, MatchedValue, | |
"Unable to represent numeric value"); | |
DefinedNumericVariable->setValue(Val); | |
} | |
// Like CHECK-NEXT, CHECK-EMPTY's match range is considered to start after | |
// the required preceding newline, which is consumed by the pattern in the | |
// case of CHECK-EMPTY but not CHECK-NEXT. | |
size_t MatchStartSkip = CheckTy == Check::CheckEmpty; | |
MatchLen = FullMatch.size() - MatchStartSkip; | |
return FullMatch.data() - Buffer.data() + MatchStartSkip; | |
} | |
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. | |
// | |
// FIXME: One easy improvement here is have the regex lib generate a single | |
// example regular expression which matches, and use that as the example | |
// string. | |
StringRef ExampleString(FixedStr); | |
if (ExampleString.empty()) | |
ExampleString = RegExStr; | |
// Only compare up to the first line in the buffer, or the string size. | |
StringRef BufferPrefix = Buffer.substr(0, ExampleString.size()); | |
BufferPrefix = BufferPrefix.split('\n').first; | |
return BufferPrefix.edit_distance(ExampleString); | |
} | |
void FileCheckPattern::printSubstitutions(const SourceMgr &SM, StringRef Buffer, | |
SMRange MatchRange) const { | |
// Print what we know about substitutions. | |
if (!Substitutions.empty()) { | |
for (const auto &Substitution : Substitutions) { | |
SmallString<256> Msg; | |
raw_svector_ostream OS(Msg); | |
Expected<std::string> MatchedValue = Substitution->getResult(); | |
// Substitution failed or is not known at match time, print the undefined | |
// variables it uses. | |
if (!MatchedValue) { | |
bool UndefSeen = false; | |
handleAllErrors(MatchedValue.takeError(), | |
[](const FileCheckNotFoundError &E) {}, | |
// Handled in PrintNoMatch(). | |
[](const FileCheckErrorDiagnostic &E) {}, | |
[&](const FileCheckUndefVarError &E) { | |
if (!UndefSeen) { | |
OS << "uses undefined variable(s):"; | |
UndefSeen = true; | |
} | |
OS << " "; | |
E.log(OS); | |
}); | |
} else { | |
// Substitution succeeded. Print substituted value. | |
OS << "with \""; | |
OS.write_escaped(Substitution->getFromString()) << "\" equal to \""; | |
OS.write_escaped(*MatchedValue) << "\""; | |
} | |
if (MatchRange.isValid()) | |
SM.PrintMessage(MatchRange.Start, SourceMgr::DK_Note, OS.str(), | |
{MatchRange}); | |
else | |
SM.PrintMessage(SMLoc::getFromPointer(Buffer.data()), | |
SourceMgr::DK_Note, OS.str()); | |
} | |
} | |
} | |
static SMRange ProcessMatchResult(FileCheckDiag::MatchType MatchTy, | |
const SourceMgr &SM, SMLoc Loc, | |
Check::FileCheckType CheckTy, | |
StringRef Buffer, size_t Pos, size_t Len, | |
std::vector<FileCheckDiag> *Diags, | |
bool AdjustPrevDiag = false) { | |
SMLoc Start = SMLoc::getFromPointer(Buffer.data() + Pos); | |
SMLoc End = SMLoc::getFromPointer(Buffer.data() + Pos + Len); | |
SMRange Range(Start, End); | |
if (Diags) { | |
if (AdjustPrevDiag) | |
Diags->rbegin()->MatchTy = MatchTy; | |
else | |
Diags->emplace_back(SM, CheckTy, Loc, MatchTy, Range); | |
} | |
return Range; | |
} | |
void FileCheckPattern::printFuzzyMatch( | |
const SourceMgr &SM, StringRef Buffer, | |
std::vector<FileCheckDiag> *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 | |
// would like to show the user a best guess at what "should have" matched, to | |
// save them having to actually check the input manually. | |
size_t NumLinesForward = 0; | |
size_t Best = StringRef::npos; | |
double BestQuality = 0; | |
// Use an arbitrary 4k limit on how far we will search. | |
for (size_t i = 0, e = std::min(size_t(4096), Buffer.size()); i != e; ++i) { | |
if (Buffer[i] == '\n') | |
++NumLinesForward; | |
// Patterns have leading whitespace stripped, so skip whitespace when | |
// looking for something which looks like a pattern. | |
if (Buffer[i] == ' ' || Buffer[i] == '\t') | |
continue; | |
// 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)); | |
double Quality = Distance + (NumLinesForward / 100.); | |
if (Quality < BestQuality || Best == StringRef::npos) { | |
Best = i; | |
BestQuality = Quality; | |
} | |
} | |
// Print the "possible intended match here" line if we found something | |
// reasonable and not equal to what we showed in the "scanning from here" | |
// line. | |
if (Best && Best != StringRef::npos && BestQuality < 50) { | |
SMRange MatchRange = | |
ProcessMatchResult(FileCheckDiag::MatchFuzzy, SM, getLoc(), | |
getCheckTy(), Buffer, Best, 0, Diags); | |
SM.PrintMessage(MatchRange.Start, SourceMgr::DK_Note, | |
"possible intended match here"); | |
// FIXME: If we wanted to be really friendly we would show why the match | |
// failed, as it can be hard to spot simple one character differences. | |
} | |
} | |
Expected<StringRef> | |
FileCheckPatternContext::getPatternVarValue(StringRef VarName) { | |
auto VarIter = GlobalVariableTable.find(VarName); | |
if (VarIter == GlobalVariableTable.end()) | |
return make_error<FileCheckUndefVarError>(VarName); | |
return VarIter->second; | |
} | |
template <class... Types> | |
FileCheckNumericVariable * | |
FileCheckPatternContext::makeNumericVariable(Types... args) { | |
NumericVariables.push_back( | |
std::make_unique<FileCheckNumericVariable>(args...)); | |
return NumericVariables.back().get(); | |
} | |
FileCheckSubstitution * | |
FileCheckPatternContext::makeStringSubstitution(StringRef VarName, | |
size_t InsertIdx) { | |
Substitutions.push_back( | |
std::make_unique<FileCheckStringSubstitution>(this, VarName, InsertIdx)); | |
return Substitutions.back().get(); | |
} | |
FileCheckSubstitution *FileCheckPatternContext::makeNumericSubstitution( | |
StringRef ExpressionStr, | |
std::unique_ptr<FileCheckExpressionAST> ExpressionAST, size_t InsertIdx) { | |
Substitutions.push_back(std::make_unique<FileCheckNumericSubstitution>( | |
this, ExpressionStr, std::move(ExpressionAST), InsertIdx)); | |
return Substitutions.back().get(); | |
} | |
size_t FileCheckPattern::FindRegexVarEnd(StringRef Str, SourceMgr &SM) { | |
// Offset keeps track of the current offset within the input Str | |
size_t Offset = 0; | |
// [...] Nesting depth | |
size_t BracketDepth = 0; | |
while (!Str.empty()) { | |
if (Str.startswith("]]") && BracketDepth == 0) | |
return Offset; | |
if (Str[0] == '\\') { | |
// Backslash escapes the next char within regexes, so skip them both. | |
Str = Str.substr(2); | |
Offset += 2; | |
} else { | |
switch (Str[0]) { | |
default: | |
break; | |
case '[': | |
BracketDepth++; | |
break; | |
case ']': | |
if (BracketDepth == 0) { | |
SM.PrintMessage(SMLoc::getFromPointer(Str.data()), | |
SourceMgr::DK_Error, | |
"missing closing \"]\" for regex variable"); | |
exit(1); | |
} | |
BracketDepth--; | |
break; | |
} | |
Str = Str.substr(1); | |
Offset++; | |
} | |
} | |
return StringRef::npos; | |
} | |
StringRef FileCheck::CanonicalizeFile(MemoryBuffer &MB, | |
SmallVectorImpl<char> &OutputBuffer) { | |
OutputBuffer.reserve(MB.getBufferSize()); | |
for (const char *Ptr = MB.getBufferStart(), *End = MB.getBufferEnd(); | |
Ptr != End; ++Ptr) { | |
// Eliminate trailing dosish \r. | |
if (Ptr <= End - 2 && Ptr[0] == '\r' && Ptr[1] == '\n') { | |
continue; | |
} | |
// If current char is not a horizontal whitespace or if horizontal | |
// whitespace canonicalization is disabled, dump it to output as is. | |
if (Req.NoCanonicalizeWhiteSpace || (*Ptr != ' ' && *Ptr != '\t')) { | |
OutputBuffer.push_back(*Ptr); | |
continue; | |
} | |
// Otherwise, add one space and advance over neighboring space. | |
OutputBuffer.push_back(' '); | |
while (Ptr + 1 != End && (Ptr[1] == ' ' || Ptr[1] == '\t')) | |
++Ptr; | |
} | |
// Add a null byte and then return all but that byte. | |
OutputBuffer.push_back('\0'); | |
return StringRef(OutputBuffer.data(), OutputBuffer.size() - 1); | |
} | |
FileCheckDiag::FileCheckDiag(const SourceMgr &SM, | |
const Check::FileCheckType &CheckTy, | |
SMLoc CheckLoc, MatchType MatchTy, | |
SMRange InputRange) | |
: CheckTy(CheckTy), MatchTy(MatchTy) { | |
auto Start = SM.getLineAndColumn(InputRange.Start); | |
auto End = SM.getLineAndColumn(InputRange.End); | |
InputStartLine = Start.first; | |
InputStartCol = Start.second; | |
InputEndLine = End.first; | |
InputEndCol = End.second; | |
Start = SM.getLineAndColumn(CheckLoc); | |
CheckLine = Start.first; | |
CheckCol = Start.second; | |
} | |
static bool IsPartOfWord(char c) { | |
return (isalnum(c) || c == '-' || c == '_'); | |
} | |
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; | |
} | |
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"; | |
case Check::CheckSame: | |
return Prefix.str() + "-SAME"; | |
case Check::CheckNot: | |
return Prefix.str() + "-NOT"; | |
case Check::CheckDAG: | |
return Prefix.str() + "-DAG"; | |
case Check::CheckLabel: | |
return Prefix.str() + "-LABEL"; | |
case Check::CheckEmpty: | |
return Prefix.str() + "-EMPTY"; | |
case Check::CheckEOF: | |
return "implicit EOF"; | |
case Check::CheckBadNot: | |
return "bad NOT"; | |
case Check::CheckBadCount: | |
return "bad COUNT"; | |
} | |
llvm_unreachable("unknown FileCheckType"); | |
} | |
static std::pair<Check::FileCheckType, StringRef> | |
FindCheckType(StringRef Buffer, StringRef Prefix) { | |
if (Buffer.size() <= Prefix.size()) | |
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, Rest}; | |
if (NextChar != '-') | |
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}; | |
} | |
if (Rest.consume_front("NEXT:")) | |
return {Check::CheckNext, Rest}; | |
if (Rest.consume_front("SAME:")) | |
return {Check::CheckSame, Rest}; | |
if (Rest.consume_front("NOT:")) | |
return {Check::CheckNot, Rest}; | |
if (Rest.consume_front("DAG:")) | |
return {Check::CheckDAG, Rest}; | |
if (Rest.consume_front("LABEL:")) | |
return {Check::CheckLabel, Rest}; | |
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, Rest}; | |
return {Check::CheckNone, Rest}; | |
} | |
// From the given position, find the next character after the word. | |
static size_t SkipWord(StringRef Str, size_t Loc) { | |
while (Loc < Str.size() && IsPartOfWord(Str[Loc])) | |
++Loc; | |
return Loc; | |
} | |
/// Searches the buffer for the first prefix in the prefix regular expression. | |
/// | |
/// This searches the buffer using the provided regular expression, however it | |
/// enforces constraints beyond that: | |
/// 1) The found prefix must not be a suffix of something that looks like | |
/// a valid prefix. | |
/// 2) The found prefix must be followed by a valid check type suffix using \c | |
/// FindCheckType above. | |
/// | |
/// \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 | |
/// each new line consumed from \p Buffer, and set \p CheckTy to the type of | |
/// check found by examining the suffix. | |
/// | |
/// If no valid prefix is found, the state of Buffer, LineNumber, and CheckTy | |
/// is unspecified. | |
static std::pair<StringRef, StringRef> | |
FindFirstMatchingPrefix(Regex &PrefixRE, StringRef &Buffer, | |
unsigned &LineNumber, Check::FileCheckType &CheckTy) { | |
SmallVector<StringRef, 2> Matches; | |
while (!Buffer.empty()) { | |
// Find the first (longest) match using the RE. | |
if (!PrefixRE.match(Buffer, &Matches)) | |
// No match at all, bail. | |
return {StringRef(), StringRef()}; | |
StringRef Prefix = Matches[0]; | |
Matches.clear(); | |
assert(Prefix.data() >= Buffer.data() && | |
Prefix.data() < Buffer.data() + Buffer.size() && | |
"Prefix doesn't start inside of buffer!"); | |
size_t Loc = Prefix.data() - Buffer.data(); | |
StringRef Skipped = Buffer.substr(0, Loc); | |
Buffer = Buffer.drop_front(Loc); | |
LineNumber += Skipped.count('\n'); | |
// Check that the matched prefix isn't a suffix of some other check-like | |
// word. | |
// FIXME: This is a very ad-hoc check. it would be better handled in some | |
// other way. Among other things it seems hard to distinguish between | |
// intentional and unintentional uses of this feature. | |
if (Skipped.empty() || !IsPartOfWord(Skipped.back())) { | |
// Now extract the type. | |
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, AfterSuffix}; | |
} | |
// If we didn't successfully find a prefix, we need to skip this invalid | |
// prefix and continue scanning. We directly skip the prefix that was | |
// matched and any additional parts of that check-like word. | |
Buffer = Buffer.drop_front(SkipWord(Buffer, Prefix.size())); | |
} | |
// We ran out of buffer while skipping partial matches so give up. | |
return {StringRef(), StringRef()}; | |
} | |
void FileCheckPatternContext::createLineVariable() { | |
assert(!LineVariable && "@LINE pseudo numeric variable already created"); | |
StringRef LineName = "@LINE"; | |
LineVariable = makeNumericVariable(LineName); | |
GlobalNumericVariableTable[LineName] = LineVariable; | |
} | |
FileCheck::FileCheck(FileCheckRequest Req) | |
: Req(Req), PatternContext(std::make_unique<FileCheckPatternContext>()), | |
CheckStrings(std::make_unique<std::vector<FileCheckString>>()) {} | |
FileCheck::~FileCheck() = default; | |
bool FileCheck::readCheckFile(SourceMgr &SM, StringRef Buffer, | |
Regex &PrefixRE) { | |
Error DefineError = | |
PatternContext->defineCmdlineVariables(Req.GlobalDefines, SM); | |
if (DefineError) { | |
logAllUnhandledErrors(std::move(DefineError), errs()); | |
return true; | |
} | |
PatternContext->createLineVariable(); | |
std::vector<FileCheckPattern> ImplicitNegativeChecks; | |
for (const auto &PatternString : Req.ImplicitCheckNot) { | |
// Create a buffer with fake command line content in order to display the | |
// command line option responsible for the specific implicit CHECK-NOT. | |
std::string Prefix = "-implicit-check-not='"; | |
std::string Suffix = "'"; | |
std::unique_ptr<MemoryBuffer> CmdLine = MemoryBuffer::getMemBufferCopy( | |
Prefix + PatternString + Suffix, "command line"); | |
StringRef PatternInBuffer = | |
CmdLine->getBuffer().substr(Prefix.size(), PatternString.size()); | |
SM.AddNewSourceBuffer(std::move(CmdLine), SMLoc()); | |
ImplicitNegativeChecks.push_back( | |
FileCheckPattern(Check::CheckNot, PatternContext.get())); | |
ImplicitNegativeChecks.back().parsePattern(PatternInBuffer, | |
"IMPLICIT-CHECK", SM, Req); | |
} | |
std::vector<FileCheckPattern> DagNotMatches = ImplicitNegativeChecks; | |
// LineNumber keeps track of the line on which CheckPrefix instances are | |
// found. | |
unsigned LineNumber = 1; | |
while (1) { | |
Check::FileCheckType CheckTy; | |
// See if a prefix occurs in the memory buffer. | |
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 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) { | |
SM.PrintMessage(SMLoc::getFromPointer(Buffer.data()), SourceMgr::DK_Error, | |
"unsupported -NOT combo on prefix '" + UsedPrefix + "'"); | |
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)) | |
Buffer = Buffer.substr(Buffer.find_first_not_of(" \t")); | |
// Scan ahead to the end of line. | |
size_t EOL = Buffer.find_first_of("\n\r"); | |
// Remember the location of the start of the pattern, for diagnostics. | |
SMLoc PatternLoc = SMLoc::getFromPointer(Buffer.data()); | |
// Parse the pattern. | |
FileCheckPattern P(CheckTy, PatternContext.get(), LineNumber); | |
if (P.parsePattern(Buffer.substr(0, EOL), UsedPrefix, SM, Req)) | |
return true; | |
// Verify that CHECK-LABEL lines do not define or use variables | |
if ((CheckTy == Check::CheckLabel) && P.hasVariable()) { | |
SM.PrintMessage( | |
SMLoc::getFromPointer(UsedPrefixStart), SourceMgr::DK_Error, | |
"found '" + UsedPrefix + "-LABEL:'" | |
" with variable definition or use"); | |
return true; | |
} | |
Buffer = Buffer.substr(EOL); | |
// Verify that CHECK-NEXT/SAME/EMPTY lines have at least one CHECK line before them. | |
if ((CheckTy == Check::CheckNext || CheckTy == Check::CheckSame || | |
CheckTy == Check::CheckEmpty) && | |
CheckStrings->empty()) { | |
StringRef Type = CheckTy == Check::CheckNext | |
? "NEXT" | |
: CheckTy == Check::CheckEmpty ? "EMPTY" : "SAME"; | |
SM.PrintMessage(SMLoc::getFromPointer(UsedPrefixStart), | |
SourceMgr::DK_Error, | |
"found '" + UsedPrefix + "-" + Type + | |
"' without previous '" + UsedPrefix + ": line"); | |
return true; | |
} | |
// Handle CHECK-DAG/-NOT. | |
if (CheckTy == Check::CheckDAG || CheckTy == Check::CheckNot) { | |
DagNotMatches.push_back(P); | |
continue; | |
} | |
// Okay, add the string we captured to the output vector and move on. | |
CheckStrings->emplace_back(P, UsedPrefix, PatternLoc); | |
std::swap(DagNotMatches, CheckStrings->back().DagNotStrings); | |
DagNotMatches = ImplicitNegativeChecks; | |
} | |
// 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, PatternContext.get(), LineNumber + 1), | |
*Req.CheckPrefixes.begin(), SMLoc::getFromPointer(Buffer.data())); | |
std::swap(DagNotMatches, CheckStrings->back().DagNotStrings); | |
} | |
if (CheckStrings->empty()) { | |
errs() << "error: no check strings found with prefix" | |
<< (Req.CheckPrefixes.size() > 1 ? "es " : " "); | |
auto I = Req.CheckPrefixes.begin(); | |
auto E = Req.CheckPrefixes.end(); | |
if (I != E) { | |
errs() << "\'" << *I << ":'"; | |
++I; | |
} | |
for (; I != E; ++I) | |
errs() << ", \'" << *I << ":'"; | |
errs() << '\n'; | |
return true; | |
} | |
return false; | |
} | |
static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM, | |
StringRef Prefix, SMLoc Loc, const FileCheckPattern &Pat, | |
int MatchedCount, StringRef Buffer, size_t MatchPos, | |
size_t MatchLen, const FileCheckRequest &Req, | |
std::vector<FileCheckDiag> *Diags) { | |
bool PrintDiag = true; | |
if (ExpectedMatch) { | |
if (!Req.Verbose) | |
return; | |
if (!Req.VerboseVerbose && Pat.getCheckTy() == Check::CheckEOF) | |
return; | |
// Due to their verbosity, we don't print verbose diagnostics here if we're | |
// gathering them for a different rendering, but we always print other | |
// diagnostics. | |
PrintDiag = !Diags; | |
} | |
SMRange MatchRange = ProcessMatchResult( | |
ExpectedMatch ? FileCheckDiag::MatchFoundAndExpected | |
: FileCheckDiag::MatchFoundButExcluded, | |
SM, Loc, Pat.getCheckTy(), Buffer, MatchPos, MatchLen, Diags); | |
if (!PrintDiag) | |
return; | |
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(MatchRange.Start, SourceMgr::DK_Note, "found here", | |
{MatchRange}); | |
Pat.printSubstitutions(SM, Buffer, MatchRange); | |
} | |
static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM, | |
const FileCheckString &CheckStr, int MatchedCount, | |
StringRef Buffer, size_t MatchPos, size_t MatchLen, | |
FileCheckRequest &Req, | |
std::vector<FileCheckDiag> *Diags) { | |
PrintMatch(ExpectedMatch, SM, CheckStr.Prefix, CheckStr.Loc, CheckStr.Pat, | |
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, bool VerboseVerbose, | |
std::vector<FileCheckDiag> *Diags, Error MatchErrors) { | |
assert(MatchErrors && "Called on successful match"); | |
bool PrintDiag = true; | |
if (!ExpectedMatch) { | |
if (!VerboseVerbose) { | |
consumeError(std::move(MatchErrors)); | |
return; | |
} | |
// Due to their verbosity, we don't print verbose diagnostics here if we're | |
// gathering them for a different rendering, but we always print other | |
// diagnostics. | |
PrintDiag = !Diags; | |
} | |
// If the current position is at the end of a line, advance to the start of | |
// the next line. | |
Buffer = Buffer.substr(Buffer.find_first_not_of(" \t\n\r")); | |
SMRange SearchRange = ProcessMatchResult( | |
ExpectedMatch ? FileCheckDiag::MatchNoneButExpected | |
: FileCheckDiag::MatchNoneAndExcluded, | |
SM, Loc, Pat.getCheckTy(), Buffer, 0, Buffer.size(), Diags); | |
if (!PrintDiag) { | |
consumeError(std::move(MatchErrors)); | |
return; | |
} | |
MatchErrors = | |
handleErrors(std::move(MatchErrors), | |
[](const FileCheckErrorDiagnostic &E) { E.log(errs()); }); | |
// No problem matching the string per se. | |
if (!MatchErrors) | |
return; | |
consumeError(std::move(MatchErrors)); | |
// Print "not found" diagnostic. | |
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. | |
SM.PrintMessage(SearchRange.Start, SourceMgr::DK_Note, "scanning from here"); | |
// Allow the pattern to print additional information if desired. | |
Pat.printSubstitutions(SM, Buffer); | |
if (ExpectedMatch) | |
Pat.printFuzzyMatch(SM, Buffer, Diags); | |
} | |
static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM, | |
const FileCheckString &CheckStr, int MatchedCount, | |
StringRef Buffer, bool VerboseVerbose, | |
std::vector<FileCheckDiag> *Diags, Error MatchErrors) { | |
PrintNoMatch(ExpectedMatch, SM, CheckStr.Prefix, CheckStr.Loc, CheckStr.Pat, | |
MatchedCount, Buffer, VerboseVerbose, Diags, | |
std::move(MatchErrors)); | |
} | |
/// Counts the number of newlines in the specified range. | |
static unsigned CountNumNewlinesBetween(StringRef Range, | |
const char *&FirstNewLine) { | |
unsigned NumNewLines = 0; | |
while (1) { | |
// Scan for newline. | |
Range = Range.substr(Range.find_first_of("\n\r")); | |
if (Range.empty()) | |
return NumNewLines; | |
++NumNewLines; | |
// Handle \n\r and \r\n as a single newline. | |
if (Range.size() > 1 && (Range[1] == '\n' || Range[1] == '\r') && | |
(Range[0] != Range[1])) | |
Range = Range.substr(1); | |
Range = Range.substr(1); | |
if (NumNewLines == 1) | |
FirstNewLine = Range.begin(); | |
} | |
} | |
size_t FileCheckString::Check(const SourceMgr &SM, StringRef Buffer, | |
bool IsLabelScanMode, size_t &MatchLen, | |
FileCheckRequest &Req, | |
std::vector<FileCheckDiag> *Diags) const { | |
size_t LastPos = 0; | |
std::vector<const FileCheckPattern *> NotStrings; | |
// IsLabelScanMode is true when we are scanning forward to find CHECK-LABEL | |
// bounds; we have not processed variable definitions within the bounded block | |
// yet so cannot handle any final CHECK-DAG yet; this is handled when going | |
// 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, Req, Diags); | |
if (LastPos == StringRef::npos) | |
return StringRef::npos; | |
} | |
// Match itself from the last position after matching CHECK-DAG. | |
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 | |
Expected<size_t> MatchResult = Pat.match(MatchBuffer, CurrentMatchLen, SM); | |
// report | |
if (!MatchResult) { | |
PrintNoMatch(true, SM, *this, i, MatchBuffer, Req.VerboseVerbose, Diags, | |
MatchResult.takeError()); | |
return StringRef::npos; | |
} | |
size_t MatchPos = *MatchResult; | |
PrintMatch(true, SM, *this, i, MatchBuffer, MatchPos, CurrentMatchLen, Req, | |
Diags); | |
if (i == 1) | |
FirstMatchPos = LastPos + MatchPos; | |
// move start point after the match | |
LastMatchEnd += MatchPos + CurrentMatchLen; | |
} | |
// 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) { | |
size_t MatchPos = FirstMatchPos - LastPos; | |
StringRef MatchBuffer = Buffer.substr(LastPos); | |
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)) { | |
ProcessMatchResult(FileCheckDiag::MatchFoundButWrongLine, SM, Loc, | |
Pat.getCheckTy(), MatchBuffer, MatchPos, MatchLen, | |
Diags, Req.Verbose); | |
return StringRef::npos; | |
} | |
// If this check is a "CHECK-SAME", verify that the previous match was on | |
// the same line (i.e. that there is no newline between them). | |
if (CheckSame(SM, SkippedRegion)) { | |
ProcessMatchResult(FileCheckDiag::MatchFoundButWrongLine, SM, Loc, | |
Pat.getCheckTy(), MatchBuffer, MatchPos, MatchLen, | |
Diags, Req.Verbose); | |
return StringRef::npos; | |
} | |
// If this match had "not strings", verify that they don't exist in the | |
// skipped region. | |
if (CheckNot(SM, SkippedRegion, NotStrings, Req, Diags)) | |
return StringRef::npos; | |
} | |
return FirstMatchPos; | |
} | |
bool FileCheckString::CheckNext(const SourceMgr &SM, StringRef Buffer) const { | |
if (Pat.getCheckTy() != Check::CheckNext && | |
Pat.getCheckTy() != Check::CheckEmpty) | |
return false; | |
Twine CheckName = | |
Prefix + | |
Twine(Pat.getCheckTy() == Check::CheckEmpty ? "-EMPTY" : "-NEXT"); | |
// Count the number of newlines between the previous match and this one. | |
const char *FirstNewLine = nullptr; | |
unsigned NumNewLines = CountNumNewlinesBetween(Buffer, FirstNewLine); | |
if (NumNewLines == 0) { | |
SM.PrintMessage(Loc, SourceMgr::DK_Error, | |
CheckName + ": is on the same line as previous match"); | |
SM.PrintMessage(SMLoc::getFromPointer(Buffer.end()), SourceMgr::DK_Note, | |
"'next' match was here"); | |
SM.PrintMessage(SMLoc::getFromPointer(Buffer.data()), SourceMgr::DK_Note, | |
"previous match ended here"); | |
return true; | |
} | |
if (NumNewLines != 1) { | |
SM.PrintMessage(Loc, SourceMgr::DK_Error, | |
CheckName + | |
": is not on the line after the previous match"); | |
SM.PrintMessage(SMLoc::getFromPointer(Buffer.end()), SourceMgr::DK_Note, | |
"'next' match was here"); | |
SM.PrintMessage(SMLoc::getFromPointer(Buffer.data()), SourceMgr::DK_Note, | |
"previous match ended here"); | |
SM.PrintMessage(SMLoc::getFromPointer(FirstNewLine), SourceMgr::DK_Note, | |
"non-matching line after previous match is here"); | |
return true; | |
} | |
return false; | |
} | |
bool FileCheckString::CheckSame(const SourceMgr &SM, StringRef Buffer) const { | |
if (Pat.getCheckTy() != Check::CheckSame) | |
return false; | |
// Count the number of newlines between the previous match and this one. | |
const char *FirstNewLine = nullptr; | |
unsigned NumNewLines = CountNumNewlinesBetween(Buffer, FirstNewLine); | |
if (NumNewLines != 0) { | |
SM.PrintMessage(Loc, SourceMgr::DK_Error, | |
Prefix + | |
"-SAME: is not on the same line as the previous match"); | |
SM.PrintMessage(SMLoc::getFromPointer(Buffer.end()), SourceMgr::DK_Note, | |
"'next' match was here"); | |
SM.PrintMessage(SMLoc::getFromPointer(Buffer.data()), SourceMgr::DK_Note, | |
"previous match ended here"); | |
return true; | |
} | |
return false; | |
} | |
bool FileCheckString::CheckNot( | |
const SourceMgr &SM, StringRef Buffer, | |
const std::vector<const FileCheckPattern *> &NotStrings, | |
const FileCheckRequest &Req, std::vector<FileCheckDiag> *Diags) const { | |
for (const FileCheckPattern *Pat : NotStrings) { | |
assert((Pat->getCheckTy() == Check::CheckNot) && "Expect CHECK-NOT!"); | |
size_t MatchLen = 0; | |
Expected<size_t> MatchResult = Pat->match(Buffer, MatchLen, SM); | |
if (!MatchResult) { | |
PrintNoMatch(false, SM, Prefix, Pat->getLoc(), *Pat, 1, Buffer, | |
Req.VerboseVerbose, Diags, MatchResult.takeError()); | |
continue; | |
} | |
size_t Pos = *MatchResult; | |
PrintMatch(false, SM, Prefix, Pat->getLoc(), *Pat, 1, Buffer, Pos, MatchLen, | |
Req, Diags); | |
return true; | |
} | |
return false; | |
} | |
size_t | |
FileCheckString::CheckDag(const SourceMgr &SM, StringRef Buffer, | |
std::vector<const FileCheckPattern *> &NotStrings, | |
const FileCheckRequest &Req, | |
std::vector<FileCheckDiag> *Diags) const { | |
if (DagNotStrings.empty()) | |
return 0; | |
// The start of the search range. | |
size_t StartPos = 0; | |
struct MatchRange { | |
size_t Pos; | |
size_t End; | |
}; | |
// A sorted list of ranges for non-overlapping CHECK-DAG matches. Match | |
// ranges are erased from this list once they are no longer in the search | |
// range. | |
std::list<MatchRange> MatchRanges; | |
// We need PatItr and PatEnd later for detecting the end of a CHECK-DAG | |
// group, so we don't use a range-based for loop here. | |
for (auto PatItr = DagNotStrings.begin(), PatEnd = DagNotStrings.end(); | |
PatItr != PatEnd; ++PatItr) { | |
const FileCheckPattern &Pat = *PatItr; | |
assert((Pat.getCheckTy() == Check::CheckDAG || | |
Pat.getCheckTy() == Check::CheckNot) && | |
"Invalid CHECK-DAG or CHECK-NOT!"); | |
if (Pat.getCheckTy() == Check::CheckNot) { | |
NotStrings.push_back(&Pat); | |
continue; | |
} | |
assert((Pat.getCheckTy() == Check::CheckDAG) && "Expect CHECK-DAG!"); | |
// CHECK-DAG always matches from the start. | |
size_t MatchLen = 0, MatchPos = StartPos; | |
// Search for a match that doesn't overlap a previous match in this | |
// CHECK-DAG group. | |
for (auto MI = MatchRanges.begin(), ME = MatchRanges.end(); true; ++MI) { | |
StringRef MatchBuffer = Buffer.substr(MatchPos); | |
Expected<size_t> MatchResult = Pat.match(MatchBuffer, MatchLen, SM); | |
// With a group of CHECK-DAGs, a single mismatching means the match on | |
// that group of CHECK-DAGs fails immediately. | |
if (!MatchResult) { | |
PrintNoMatch(true, SM, Prefix, Pat.getLoc(), Pat, 1, MatchBuffer, | |
Req.VerboseVerbose, Diags, MatchResult.takeError()); | |
return StringRef::npos; | |
} | |
size_t MatchPosBuf = *MatchResult; | |
// 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, 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 | |
// one match range that encompasses the current CHECK-DAG group's | |
// matches. | |
if (MatchRanges.empty()) | |
MatchRanges.insert(MatchRanges.end(), M); | |
else { | |
auto Block = MatchRanges.begin(); | |
Block->Pos = std::min(Block->Pos, M.Pos); | |
Block->End = std::max(Block->End, M.End); | |
} | |
break; | |
} | |
// Iterate previous matches until overlapping match or insertion point. | |
bool Overlap = false; | |
for (; MI != ME; ++MI) { | |
if (M.Pos < MI->End) { | |
// !Overlap => New match has no overlap and is before this old match. | |
// Overlap => New match overlaps this old match. | |
Overlap = MI->Pos < M.End; | |
break; | |
} | |
} | |
if (!Overlap) { | |
// Insert non-overlapping match into list. | |
MatchRanges.insert(MI, M); | |
break; | |
} | |
if (Req.VerboseVerbose) { | |
// Due to their verbosity, we don't print verbose diagnostics here if | |
// we're gathering them for a different rendering, but we always print | |
// other diagnostics. | |
if (!Diags) { | |
SMLoc OldStart = SMLoc::getFromPointer(Buffer.data() + MI->Pos); | |
SMLoc OldEnd = SMLoc::getFromPointer(Buffer.data() + MI->End); | |
SMRange OldRange(OldStart, OldEnd); | |
SM.PrintMessage(OldStart, SourceMgr::DK_Note, | |
"match discarded, overlaps earlier DAG match here", | |
{OldRange}); | |
} else | |
Diags->rbegin()->MatchTy = FileCheckDiag::MatchFoundButDiscarded; | |
} | |
MatchPos = MI->End; | |
} | |
if (!Req.VerboseVerbose) | |
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 || | |
std::next(PatItr)->getCheckTy() == Check::CheckNot) { | |
if (!NotStrings.empty()) { | |
// If there are CHECK-NOTs between two CHECK-DAGs or from CHECK to | |
// CHECK-DAG, verify that there are no 'not' strings occurred in that | |
// region. | |
StringRef SkippedRegion = | |
Buffer.slice(StartPos, MatchRanges.begin()->Pos); | |
if (CheckNot(SM, SkippedRegion, NotStrings, Req, Diags)) | |
return StringRef::npos; | |
// Clear "not strings". | |
NotStrings.clear(); | |
} | |
// All subsequent CHECK-DAGs and CHECK-NOTs should be matched from the | |
// end of this CHECK-DAG group's match range. | |
StartPos = MatchRanges.rbegin()->End; | |
// Don't waste time checking for (impossible) overlaps before that. | |
MatchRanges.clear(); | |
} | |
} | |
return StartPos; | |
} | |
// A check prefix must contain only alphanumeric, hyphens and underscores. | |
static bool ValidateCheckPrefix(StringRef CheckPrefix) { | |
static const Regex Validator("^[a-zA-Z0-9_-]*$"); | |
return Validator.match(CheckPrefix); | |
} | |
bool FileCheck::ValidateCheckPrefixes() { | |
StringSet<> PrefixSet; | |
for (StringRef Prefix : Req.CheckPrefixes) { | |
// Reject empty prefixes. | |
if (Prefix == "") | |
return false; | |
if (!PrefixSet.insert(Prefix).second) | |
return false; | |
if (!ValidateCheckPrefix(Prefix)) | |
return false; | |
} | |
return true; | |
} | |
Regex FileCheck::buildCheckPrefixRegex() { | |
// I don't think there's a way to specify an initial value for cl::list, | |
// so if nothing was specified, add the default | |
if (Req.CheckPrefixes.empty()) | |
Req.CheckPrefixes.push_back("CHECK"); | |
// We already validated the contents of CheckPrefixes so just concatenate | |
// them as alternatives. | |
SmallString<32> PrefixRegexStr; | |
for (StringRef Prefix : Req.CheckPrefixes) { | |
if (Prefix != Req.CheckPrefixes.front()) | |
PrefixRegexStr.push_back('|'); | |
PrefixRegexStr.append(Prefix); | |
} | |
return Regex(PrefixRegexStr); | |
} | |
Error FileCheckPatternContext::defineCmdlineVariables( | |
std::vector<std::string> &CmdlineDefines, SourceMgr &SM) { | |
assert(GlobalVariableTable.empty() && GlobalNumericVariableTable.empty() && | |
"Overriding defined variable with command-line variable definitions"); | |
if (CmdlineDefines.empty()) | |
return Error::success(); | |
// Create a string representing the vector of command-line definitions. Each | |
// definition is on its own line and prefixed with a definition number to | |
// clarify which definition a given diagnostic corresponds to. | |
unsigned I = 0; | |
Error Errs = Error::success(); | |
std::string CmdlineDefsDiag; | |
SmallVector<std::pair<size_t, size_t>, 4> CmdlineDefsIndices; | |
for (StringRef CmdlineDef : CmdlineDefines) { | |
std::string DefPrefix = ("Global define #" + Twine(++I) + ": ").str(); | |
size_t EqIdx = CmdlineDef.find('='); | |
if (EqIdx == StringRef::npos) { | |
CmdlineDefsIndices.push_back(std::make_pair(CmdlineDefsDiag.size(), 0)); | |
continue; | |
} | |
// Numeric variable definition. | |
if (CmdlineDef[0] == '#') { | |
// Append a copy of the command-line definition adapted to use the same | |
// format as in the input file to be able to reuse | |
// parseNumericSubstitutionBlock. | |
CmdlineDefsDiag += (DefPrefix + CmdlineDef + " (parsed as: [[").str(); | |
std::string SubstitutionStr = CmdlineDef; | |
SubstitutionStr[EqIdx] = ':'; | |
CmdlineDefsIndices.push_back( | |
std::make_pair(CmdlineDefsDiag.size(), SubstitutionStr.size())); | |
CmdlineDefsDiag += (SubstitutionStr + Twine("]])\n")).str(); | |
} else { | |
CmdlineDefsDiag += DefPrefix; | |
CmdlineDefsIndices.push_back( | |
std::make_pair(CmdlineDefsDiag.size(), CmdlineDef.size())); | |
CmdlineDefsDiag += (CmdlineDef + "\n").str(); | |
} | |
} | |
// Create a buffer with fake command line content in order to display | |
// parsing diagnostic with location information and point to the | |
// global definition with invalid syntax. | |
std::unique_ptr<MemoryBuffer> CmdLineDefsDiagBuffer = | |
MemoryBuffer::getMemBufferCopy(CmdlineDefsDiag, "Global defines"); | |
StringRef CmdlineDefsDiagRef = CmdLineDefsDiagBuffer->getBuffer(); | |
SM.AddNewSourceBuffer(std::move(CmdLineDefsDiagBuffer), SMLoc()); | |
for (std::pair<size_t, size_t> CmdlineDefIndices : CmdlineDefsIndices) { | |
StringRef CmdlineDef = CmdlineDefsDiagRef.substr(CmdlineDefIndices.first, | |
CmdlineDefIndices.second); | |
if (CmdlineDef.empty()) { | |
Errs = joinErrors( | |
std::move(Errs), | |
FileCheckErrorDiagnostic::get( | |
SM, CmdlineDef, "missing equal sign in global definition")); | |
continue; | |
} | |
// Numeric variable definition. | |
if (CmdlineDef[0] == '#') { | |
// Now parse the definition both to check that the syntax is correct and | |
// to create the necessary class instance. | |
StringRef CmdlineDefExpr = CmdlineDef.substr(1); | |
Optional<FileCheckNumericVariable *> DefinedNumericVariable; | |
Expected<std::unique_ptr<FileCheckExpressionAST>> ExpressionASTResult = | |
FileCheckPattern::parseNumericSubstitutionBlock( | |
CmdlineDefExpr, DefinedNumericVariable, false, None, this, SM); | |
if (!ExpressionASTResult) { | |
Errs = joinErrors(std::move(Errs), ExpressionASTResult.takeError()); | |
continue; | |
} | |
std::unique_ptr<FileCheckExpressionAST> ExpressionAST = | |
std::move(*ExpressionASTResult); | |
// Now evaluate the expression whose value this variable should be set | |
// to, since the expression of a command-line variable definition should | |
// only use variables defined earlier on the command-line. If not, this | |
// is an error and we report it. | |
Expected<uint64_t> Value = ExpressionAST->eval(); | |
if (!Value) { | |
Errs = joinErrors(std::move(Errs), Value.takeError()); | |
continue; | |
} | |
assert(DefinedNumericVariable && "No variable defined"); | |
(*DefinedNumericVariable)->setValue(*Value); | |
// Record this variable definition. | |
GlobalNumericVariableTable[(*DefinedNumericVariable)->getName()] = | |
*DefinedNumericVariable; | |
} else { | |
// String variable definition. | |
std::pair<StringRef, StringRef> CmdlineNameVal = CmdlineDef.split('='); | |
StringRef CmdlineName = CmdlineNameVal.first; | |
StringRef OrigCmdlineName = CmdlineName; | |
Expected<FileCheckPattern::VariableProperties> ParseVarResult = | |
FileCheckPattern::parseVariable(CmdlineName, SM); | |
if (!ParseVarResult) { | |
Errs = joinErrors(std::move(Errs), ParseVarResult.takeError()); | |
continue; | |
} | |
// Check that CmdlineName does not denote a pseudo variable is only | |
// composed of the parsed numeric variable. This catches cases like | |
// "FOO+2" in a "FOO+2=10" definition. | |
if (ParseVarResult->IsPseudo || !CmdlineName.empty()) { | |
Errs = joinErrors(std::move(Errs), | |
FileCheckErrorDiagnostic::get( | |
SM, OrigCmdlineName, | |
"invalid name in string variable definition '" + | |
OrigCmdlineName + "'")); | |
continue; | |
} | |
StringRef Name = ParseVarResult->Name; | |
// Detect collisions between string and numeric variables when the former | |
// is created later than the latter. | |
if (GlobalNumericVariableTable.find(Name) != | |
GlobalNumericVariableTable.end()) { | |
Errs = joinErrors(std::move(Errs), FileCheckErrorDiagnostic::get( | |
SM, Name, | |
"numeric variable with name '" + | |
Name + "' already exists")); | |
continue; | |
} | |
GlobalVariableTable.insert(CmdlineNameVal); | |
// Mark the string variable as defined to detect collisions between | |
// string and numeric variables in defineCmdlineVariables when the latter | |
// is created later than the former. We cannot reuse GlobalVariableTable | |
// for this by populating it with an empty string since we would then | |
// lose the ability to detect the use of an undefined variable in | |
// match(). | |
DefinedVariableTable[Name] = true; | |
} | |
} | |
return Errs; | |
} | |
void FileCheckPatternContext::clearLocalVars() { | |
SmallVector<StringRef, 16> LocalPatternVars, LocalNumericVars; | |
for (const StringMapEntry<StringRef> &Var : GlobalVariableTable) | |
if (Var.first()[0] != '$') | |
LocalPatternVars.push_back(Var.first()); | |
// Numeric substitution reads the value of a variable directly, not via | |
// GlobalNumericVariableTable. Therefore, we clear local variables by | |
// clearing their value which will lead to a numeric substitution failure. We | |
// also mark the variable for removal from GlobalNumericVariableTable since | |
// this is what defineCmdlineVariables checks to decide that no global | |
// variable has been defined. | |
for (const auto &Var : GlobalNumericVariableTable) | |
if (Var.first()[0] != '$') { | |
Var.getValue()->clearValue(); | |
LocalNumericVars.push_back(Var.first()); | |
} | |
for (const auto &Var : LocalPatternVars) | |
GlobalVariableTable.erase(Var); | |
for (const auto &Var : LocalNumericVars) | |
GlobalNumericVariableTable.erase(Var); | |
} | |
bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer, | |
std::vector<FileCheckDiag> *Diags) { | |
bool ChecksFailed = false; | |
unsigned i = 0, j = 0, e = CheckStrings->size(); | |
while (true) { | |
StringRef CheckRegion; | |
if (j == e) { | |
CheckRegion = Buffer; | |
} else { | |
const FileCheckString &CheckLabelStr = (*CheckStrings)[j]; | |
if (CheckLabelStr.Pat.getCheckTy() != Check::CheckLabel) { | |
++j; | |
continue; | |
} | |
// 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, Req, Diags); | |
if (MatchLabelPos == StringRef::npos) | |
// Immediately bail if CHECK-LABEL fails, nothing else we can do. | |
return false; | |
CheckRegion = Buffer.substr(0, MatchLabelPos + MatchLabelLen); | |
Buffer = Buffer.substr(MatchLabelPos + MatchLabelLen); | |
++j; | |
} | |
// Do not clear the first region as it's the one before the first | |
// CHECK-LABEL and it would clear variables defined on the command-line | |
// before they get used. | |
if (i != 0 && Req.EnableVarScope) | |
PatternContext->clearLocalVars(); | |
for (; i != j; ++i) { | |
const FileCheckString &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, Req, Diags); | |
if (MatchPos == StringRef::npos) { | |
ChecksFailed = true; | |
i = j; | |
break; | |
} | |
CheckRegion = CheckRegion.substr(MatchPos + MatchLen); | |
} | |
if (j == e) | |
break; | |
} | |
// Success if no checks failed. | |
return !ChecksFailed; | |
} |