#include "llvm/FileCheck/FileCheck.h"
#include "FileCheckImpl.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/CheckedArithmetic.h"
#include "llvm/Support/FormatVariadic.h"
#include <cstdint>
#include <list>
#include <set>
#include <tuple>
#include <utility>
using namespace llvm;
StringRef ExpressionFormat::toString() const {
switch (Value) {
case Kind::NoFormat:
return StringRef("<none>");
case Kind::Unsigned:
return StringRef("%u");
case Kind::Signed:
return StringRef("%d");
case Kind::HexUpper:
return StringRef("%X");
case Kind::HexLower:
return StringRef("%x");
}
llvm_unreachable("unknown expression format");
}
Expected<std::string> ExpressionFormat::getWildcardRegex() const {
StringRef AlternateFormPrefix = AlternateForm ? StringRef("0x") : StringRef();
auto CreatePrecisionRegex = [&](StringRef S) {
return (Twine(AlternateFormPrefix) + S + Twine('{') + Twine(Precision) +
"}")
.str();
};
switch (Value) {
case Kind::Unsigned:
if (Precision)
return CreatePrecisionRegex("([1-9][0-9]*)?[0-9]");
return std::string("[0-9]+");
case Kind::Signed:
if (Precision)
return CreatePrecisionRegex("-?([1-9][0-9]*)?[0-9]");
return std::string("-?[0-9]+");
case Kind::HexUpper:
if (Precision)
return CreatePrecisionRegex("([1-9A-F][0-9A-F]*)?[0-9A-F]");
return (Twine(AlternateFormPrefix) + Twine("[0-9A-F]+")).str();
case Kind::HexLower:
if (Precision)
return CreatePrecisionRegex("([1-9a-f][0-9a-f]*)?[0-9a-f]");
return (Twine(AlternateFormPrefix) + Twine("[0-9a-f]+")).str();
default:
return createStringError(std::errc::invalid_argument,
"trying to match value with invalid format");
}
}
Expected<std::string>
ExpressionFormat::getMatchingString(ExpressionValue IntegerValue) const {
uint64_t AbsoluteValue;
StringRef SignPrefix = IntegerValue.isNegative() ? "-" : "";
if (Value == Kind::Signed) {
Expected<int64_t> SignedValue = IntegerValue.getSignedValue();
if (!SignedValue)
return SignedValue.takeError();
if (*SignedValue < 0)
AbsoluteValue = cantFail(IntegerValue.getAbsolute().getUnsignedValue());
else
AbsoluteValue = *SignedValue;
} else {
Expected<uint64_t> UnsignedValue = IntegerValue.getUnsignedValue();
if (!UnsignedValue)
return UnsignedValue.takeError();
AbsoluteValue = *UnsignedValue;
}
std::string AbsoluteValueStr;
switch (Value) {
case Kind::Unsigned:
case Kind::Signed:
AbsoluteValueStr = utostr(AbsoluteValue);
break;
case Kind::HexUpper:
case Kind::HexLower:
AbsoluteValueStr = utohexstr(AbsoluteValue, Value == Kind::HexLower);
break;
default:
return createStringError(std::errc::invalid_argument,
"trying to match value with invalid format");
}
StringRef AlternateFormPrefix = AlternateForm ? StringRef("0x") : StringRef();
if (Precision > AbsoluteValueStr.size()) {
unsigned LeadingZeros = Precision - AbsoluteValueStr.size();
return (Twine(SignPrefix) + Twine(AlternateFormPrefix) +
std::string(LeadingZeros, '0') + AbsoluteValueStr)
.str();
}
return (Twine(SignPrefix) + Twine(AlternateFormPrefix) + AbsoluteValueStr)
.str();
}
Expected<ExpressionValue>
ExpressionFormat::valueFromStringRepr(StringRef StrVal,
const SourceMgr &SM) const {
bool ValueIsSigned = Value == Kind::Signed;
StringRef IntegerParseErrorStr = "unable to represent numeric value";
if (ValueIsSigned) {
int64_t SignedValue;
if (StrVal.getAsInteger(10, SignedValue))
return ErrorDiagnostic::get(SM, StrVal, IntegerParseErrorStr);
return ExpressionValue(SignedValue);
}
bool Hex = Value == Kind::HexUpper || Value == Kind::HexLower;
uint64_t UnsignedValue;
bool MissingFormPrefix = AlternateForm && !StrVal.consume_front("0x");
if (StrVal.getAsInteger(Hex ? 16 : 10, UnsignedValue))
return ErrorDiagnostic::get(SM, StrVal, IntegerParseErrorStr);
if (MissingFormPrefix)
return ErrorDiagnostic::get(SM, StrVal, "missing alternate form prefix");
return ExpressionValue(UnsignedValue);
}
static int64_t getAsSigned(uint64_t UnsignedValue) {
int64_t SignedValue;
memcpy(&SignedValue, &UnsignedValue, sizeof(SignedValue));
return SignedValue;
}
Expected<int64_t> ExpressionValue::getSignedValue() const {
if (Negative)
return getAsSigned(Value);
if (Value > (uint64_t)std::numeric_limits<int64_t>::max())
return make_error<OverflowError>();
return static_cast<int64_t>(Value);
}
Expected<uint64_t> ExpressionValue::getUnsignedValue() const {
if (Negative)
return make_error<OverflowError>();
return Value;
}
ExpressionValue ExpressionValue::getAbsolute() const {
if (!Negative)
return *this;
int64_t SignedValue = getAsSigned(Value);
int64_t MaxInt64 = std::numeric_limits<int64_t>::max();
if (SignedValue >= -MaxInt64)
return ExpressionValue(-getAsSigned(Value));
SignedValue += MaxInt64;
uint64_t RemainingValueAbsolute = -SignedValue;
return ExpressionValue(MaxInt64 + RemainingValueAbsolute);
}
Expected<ExpressionValue> llvm::operator+(const ExpressionValue &LeftOperand,
const ExpressionValue &RightOperand) {
if (LeftOperand.isNegative() && RightOperand.isNegative()) {
int64_t LeftValue = cantFail(LeftOperand.getSignedValue());
int64_t RightValue = cantFail(RightOperand.getSignedValue());
Optional<int64_t> Result = checkedAdd<int64_t>(LeftValue, RightValue);
if (!Result)
return make_error<OverflowError>();
return ExpressionValue(*Result);
}
if (LeftOperand.isNegative())
return RightOperand - LeftOperand.getAbsolute();
if (RightOperand.isNegative())
return LeftOperand - RightOperand.getAbsolute();
uint64_t LeftValue = cantFail(LeftOperand.getUnsignedValue());
uint64_t RightValue = cantFail(RightOperand.getUnsignedValue());
Optional<uint64_t> Result =
checkedAddUnsigned<uint64_t>(LeftValue, RightValue);
if (!Result)
return make_error<OverflowError>();
return ExpressionValue(*Result);
}
Expected<ExpressionValue> llvm::operator-(const ExpressionValue &LeftOperand,
const ExpressionValue &RightOperand) {
if (LeftOperand.isNegative() && !RightOperand.isNegative()) {
int64_t LeftValue = cantFail(LeftOperand.getSignedValue());
uint64_t RightValue = cantFail(RightOperand.getUnsignedValue());
if (RightValue > (uint64_t)std::numeric_limits<int64_t>::max())
return make_error<OverflowError>();
Optional<int64_t> Result =
checkedSub(LeftValue, static_cast<int64_t>(RightValue));
if (!Result)
return make_error<OverflowError>();
return ExpressionValue(*Result);
}
if (LeftOperand.isNegative())
return RightOperand.getAbsolute() - LeftOperand.getAbsolute();
if (RightOperand.isNegative())
return LeftOperand + RightOperand.getAbsolute();
uint64_t LeftValue = cantFail(LeftOperand.getUnsignedValue());
uint64_t RightValue = cantFail(RightOperand.getUnsignedValue());
if (LeftValue >= RightValue)
return ExpressionValue(LeftValue - RightValue);
else {
uint64_t AbsoluteDifference = RightValue - LeftValue;
uint64_t MaxInt64 = std::numeric_limits<int64_t>::max();
if (AbsoluteDifference > MaxInt64) {
AbsoluteDifference -= MaxInt64;
int64_t Result = -MaxInt64;
int64_t MinInt64 = std::numeric_limits<int64_t>::min();
if (AbsoluteDifference > static_cast<uint64_t>(-(MinInt64 - Result)))
return make_error<OverflowError>();
Result -= static_cast<int64_t>(AbsoluteDifference);
return ExpressionValue(Result);
}
return ExpressionValue(-static_cast<int64_t>(AbsoluteDifference));
}
}
Expected<ExpressionValue> llvm::operator*(const ExpressionValue &LeftOperand,
const ExpressionValue &RightOperand) {
if (LeftOperand.isNegative() && RightOperand.isNegative())
return LeftOperand.getAbsolute() * RightOperand.getAbsolute();
if (RightOperand.isNegative())
return RightOperand * LeftOperand;
assert(!RightOperand.isNegative() && "Unexpected negative operand!");
if (LeftOperand.isNegative()) {
auto Result = LeftOperand.getAbsolute() * RightOperand.getAbsolute();
if (!Result)
return Result;
return ExpressionValue(0) - *Result;
}
uint64_t LeftValue = cantFail(LeftOperand.getUnsignedValue());
uint64_t RightValue = cantFail(RightOperand.getUnsignedValue());
Optional<uint64_t> Result =
checkedMulUnsigned<uint64_t>(LeftValue, RightValue);
if (!Result)
return make_error<OverflowError>();
return ExpressionValue(*Result);
}
Expected<ExpressionValue> llvm::operator/(const ExpressionValue &LeftOperand,
const ExpressionValue &RightOperand) {
if (LeftOperand.isNegative() && RightOperand.isNegative())
return LeftOperand.getAbsolute() / RightOperand.getAbsolute();
if (RightOperand == ExpressionValue(0))
return make_error<OverflowError>();
if (LeftOperand.isNegative() || RightOperand.isNegative())
return ExpressionValue(0) -
cantFail(LeftOperand.getAbsolute() / RightOperand.getAbsolute());
uint64_t LeftValue = cantFail(LeftOperand.getUnsignedValue());
uint64_t RightValue = cantFail(RightOperand.getUnsignedValue());
return ExpressionValue(LeftValue / RightValue);
}
Expected<ExpressionValue> llvm::max(const ExpressionValue &LeftOperand,
const ExpressionValue &RightOperand) {
if (LeftOperand.isNegative() && RightOperand.isNegative()) {
int64_t LeftValue = cantFail(LeftOperand.getSignedValue());
int64_t RightValue = cantFail(RightOperand.getSignedValue());
return ExpressionValue(std::max(LeftValue, RightValue));
}
if (!LeftOperand.isNegative() && !RightOperand.isNegative()) {
uint64_t LeftValue = cantFail(LeftOperand.getUnsignedValue());
uint64_t RightValue = cantFail(RightOperand.getUnsignedValue());
return ExpressionValue(std::max(LeftValue, RightValue));
}
if (LeftOperand.isNegative())
return RightOperand;
return LeftOperand;
}
Expected<ExpressionValue> llvm::min(const ExpressionValue &LeftOperand,
const ExpressionValue &RightOperand) {
if (cantFail(max(LeftOperand, RightOperand)) == LeftOperand)
return RightOperand;
return LeftOperand;
}
Expected<ExpressionValue> NumericVariableUse::eval() const {
Optional<ExpressionValue> Value = Variable->getValue();
if (Value)
return *Value;
return make_error<UndefVarError>(getExpressionStr());
}
Expected<ExpressionValue> BinaryOperation::eval() const {
Expected<ExpressionValue> LeftOp = LeftOperand->eval();
Expected<ExpressionValue> RightOp = RightOperand->eval();
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<ExpressionFormat>
BinaryOperation::getImplicitFormat(const SourceMgr &SM) const {
Expected<ExpressionFormat> LeftFormat = LeftOperand->getImplicitFormat(SM);
Expected<ExpressionFormat> RightFormat = RightOperand->getImplicitFormat(SM);
if (!LeftFormat || !RightFormat) {
Error Err = Error::success();
if (!LeftFormat)
Err = joinErrors(std::move(Err), LeftFormat.takeError());
if (!RightFormat)
Err = joinErrors(std::move(Err), RightFormat.takeError());
return std::move(Err);
}
if (*LeftFormat != ExpressionFormat::Kind::NoFormat &&
*RightFormat != ExpressionFormat::Kind::NoFormat &&
*LeftFormat != *RightFormat)
return ErrorDiagnostic::get(
SM, getExpressionStr(),
"implicit format conflict between '" + LeftOperand->getExpressionStr() +
"' (" + LeftFormat->toString() + ") and '" +
RightOperand->getExpressionStr() + "' (" + RightFormat->toString() +
"), need an explicit format specifier");
return *LeftFormat != ExpressionFormat::Kind::NoFormat ? *LeftFormat
: *RightFormat;
}
Expected<std::string> NumericSubstitution::getResult() const {
assert(ExpressionPointer->getAST() != nullptr &&
"Substituting empty expression");
Expected<ExpressionValue> EvaluatedValue =
ExpressionPointer->getAST()->eval();
if (!EvaluatedValue)
return EvaluatedValue.takeError();
ExpressionFormat Format = ExpressionPointer->getFormat();
return Format.getMatchingString(*EvaluatedValue);
}
Expected<std::string> StringSubstitution::getResult() const {
Expected<StringRef> VarVal = Context->getPatternVarValue(FromStr);
if (!VarVal)
return VarVal.takeError();
return Regex::escape(*VarVal);
}
bool Pattern::isValidVarNameStart(char C) { return C == '_' || isAlpha(C); }
Expected<Pattern::VariableProperties>
Pattern::parseVariable(StringRef &Str, const SourceMgr &SM) {
if (Str.empty())
return ErrorDiagnostic::get(SM, Str, "empty variable name");
size_t I = 0;
bool IsPseudo = Str[0] == '@';
if (Str[0] == '$' || IsPseudo)
++I;
if (!isValidVarNameStart(Str[I++]))
return ErrorDiagnostic::get(SM, Str, "invalid variable name");
for (size_t E = Str.size(); I != E; ++I)
if (Str[I] != '_' && !isAlnum(Str[I]))
break;
StringRef Name = Str.take_front(I);
Str = Str.substr(I);
return VariableProperties {Name, IsPseudo};
}
constexpr StringLiteral SpaceChars = " \t";
static char popFront(StringRef &S) {
char C = S.front();
S = S.drop_front();
return C;
}
char OverflowError::ID = 0;
char UndefVarError::ID = 0;
char ErrorDiagnostic::ID = 0;
char NotFoundError::ID = 0;
char ErrorReported::ID = 0;
Expected<NumericVariable *> Pattern::parseNumericVariableDefinition(
StringRef &Expr, FileCheckPatternContext *Context,
Optional<size_t> LineNumber, ExpressionFormat ImplicitFormat,
const SourceMgr &SM) {
Expected<VariableProperties> ParseVarResult = parseVariable(Expr, SM);
if (!ParseVarResult)
return ParseVarResult.takeError();
StringRef Name = ParseVarResult->Name;
if (ParseVarResult->IsPseudo)
return ErrorDiagnostic::get(
SM, Name, "definition of pseudo numeric variable unsupported");
if (Context->DefinedVariableTable.find(Name) !=
Context->DefinedVariableTable.end())
return ErrorDiagnostic::get(
SM, Name, "string variable with name '" + Name + "' already exists");
Expr = Expr.ltrim(SpaceChars);
if (!Expr.empty())
return ErrorDiagnostic::get(
SM, Expr, "unexpected characters after numeric variable name");
NumericVariable *DefinedNumericVariable;
auto VarTableIter = Context->GlobalNumericVariableTable.find(Name);
if (VarTableIter != Context->GlobalNumericVariableTable.end()) {
DefinedNumericVariable = VarTableIter->second;
if (DefinedNumericVariable->getImplicitFormat() != ImplicitFormat)
return ErrorDiagnostic::get(
SM, Expr, "format different from previous variable definition");
} else
DefinedNumericVariable =
Context->makeNumericVariable(Name, ImplicitFormat, LineNumber);
return DefinedNumericVariable;
}
Expected<std::unique_ptr<NumericVariableUse>> Pattern::parseNumericVariableUse(
StringRef Name, bool IsPseudo, Optional<size_t> LineNumber,
FileCheckPatternContext *Context, const SourceMgr &SM) {
if (IsPseudo && !Name.equals("@LINE"))
return ErrorDiagnostic::get(
SM, Name, "invalid pseudo numeric variable '" + Name + "'");
auto VarTableIter = Context->GlobalNumericVariableTable.find(Name);
NumericVariable *NumericVariable;
if (VarTableIter != Context->GlobalNumericVariableTable.end())
NumericVariable = VarTableIter->second;
else {
NumericVariable = Context->makeNumericVariable(
Name, ExpressionFormat(ExpressionFormat::Kind::Unsigned));
Context->GlobalNumericVariableTable[Name] = NumericVariable;
}
Optional<size_t> DefLineNumber = NumericVariable->getDefLineNumber();
if (DefLineNumber && LineNumber && *DefLineNumber == *LineNumber)
return ErrorDiagnostic::get(
SM, Name,
"numeric variable '" + Name +
"' defined earlier in the same CHECK directive");
return std::make_unique<NumericVariableUse>(Name, NumericVariable);
}
Expected<std::unique_ptr<ExpressionAST>> Pattern::parseNumericOperand(
StringRef &Expr, AllowedOperand AO, bool MaybeInvalidConstraint,
Optional<size_t> LineNumber, FileCheckPatternContext *Context,
const SourceMgr &SM) {
if (Expr.startswith("(")) {
if (AO != AllowedOperand::Any)
return ErrorDiagnostic::get(
SM, Expr, "parenthesized expression not permitted here");
return parseParenExpr(Expr, LineNumber, Context, SM);
}
if (AO == AllowedOperand::LineVar || AO == AllowedOperand::Any) {
Expected<Pattern::VariableProperties> ParseVarResult =
parseVariable(Expr, SM);
if (ParseVarResult) {
if (Expr.ltrim(SpaceChars).startswith("(")) {
if (AO != AllowedOperand::Any)
return ErrorDiagnostic::get(SM, ParseVarResult->Name,
"unexpected function call");
return parseCallExpr(Expr, ParseVarResult->Name, LineNumber, Context,
SM);
}
return parseNumericVariableUse(ParseVarResult->Name,
ParseVarResult->IsPseudo, LineNumber,
Context, SM);
}
if (AO == AllowedOperand::LineVar)
return ParseVarResult.takeError();
consumeError(ParseVarResult.takeError());
}
int64_t SignedLiteralValue;
uint64_t UnsignedLiteralValue;
StringRef SaveExpr = Expr;
if (!Expr.consumeInteger((AO == AllowedOperand::LegacyLiteral) ? 10 : 0,
UnsignedLiteralValue))
return std::make_unique<ExpressionLiteral>(SaveExpr.drop_back(Expr.size()),
UnsignedLiteralValue);
Expr = SaveExpr;
if (AO == AllowedOperand::Any && !Expr.consumeInteger(0, SignedLiteralValue))
return std::make_unique<ExpressionLiteral>(SaveExpr.drop_back(Expr.size()),
SignedLiteralValue);
return ErrorDiagnostic::get(
SM, Expr,
Twine("invalid ") +
(MaybeInvalidConstraint ? "matching constraint or " : "") +
"operand format");
}
Expected<std::unique_ptr<ExpressionAST>>
Pattern::parseParenExpr(StringRef &Expr, Optional<size_t> LineNumber,
FileCheckPatternContext *Context, const SourceMgr &SM) {
Expr = Expr.ltrim(SpaceChars);
assert(Expr.startswith("("));
Expr.consume_front("(");
Expr = Expr.ltrim(SpaceChars);
if (Expr.empty())
return ErrorDiagnostic::get(SM, Expr, "missing operand in expression");
Expected<std::unique_ptr<ExpressionAST>> SubExprResult = parseNumericOperand(
Expr, AllowedOperand::Any, false, LineNumber,
Context, SM);
Expr = Expr.ltrim(SpaceChars);
while (SubExprResult && !Expr.empty() && !Expr.startswith(")")) {
StringRef OrigExpr = Expr;
SubExprResult = parseBinop(OrigExpr, Expr, std::move(*SubExprResult), false,
LineNumber, Context, SM);
Expr = Expr.ltrim(SpaceChars);
}
if (!SubExprResult)
return SubExprResult;
if (!Expr.consume_front(")")) {
return ErrorDiagnostic::get(SM, Expr,
"missing ')' at end of nested expression");
}
return SubExprResult;
}
Expected<std::unique_ptr<ExpressionAST>>
Pattern::parseBinop(StringRef Expr, StringRef &RemainingExpr,
std::unique_ptr<ExpressionAST> LeftOp,
bool IsLegacyLineExpr, Optional<size_t> LineNumber,
FileCheckPatternContext *Context, const SourceMgr &SM) {
RemainingExpr = RemainingExpr.ltrim(SpaceChars);
if (RemainingExpr.empty())
return std::move(LeftOp);
SMLoc OpLoc = SMLoc::getFromPointer(RemainingExpr.data());
char Operator = popFront(RemainingExpr);
binop_eval_t EvalBinop;
switch (Operator) {
case '+':
EvalBinop = operator+;
break;
case '-':
EvalBinop = operator-;
break;
default:
return ErrorDiagnostic::get(
SM, OpLoc, Twine("unsupported operation '") + Twine(Operator) + "'");
}
RemainingExpr = RemainingExpr.ltrim(SpaceChars);
if (RemainingExpr.empty())
return ErrorDiagnostic::get(SM, RemainingExpr,
"missing operand in expression");
AllowedOperand AO =
IsLegacyLineExpr ? AllowedOperand::LegacyLiteral : AllowedOperand::Any;
Expected<std::unique_ptr<ExpressionAST>> RightOpResult =
parseNumericOperand(RemainingExpr, AO, false,
LineNumber, Context, SM);
if (!RightOpResult)
return RightOpResult;
Expr = Expr.drop_back(RemainingExpr.size());
return std::make_unique<BinaryOperation>(Expr, EvalBinop, std::move(LeftOp),
std::move(*RightOpResult));
}
Expected<std::unique_ptr<ExpressionAST>>
Pattern::parseCallExpr(StringRef &Expr, StringRef FuncName,
Optional<size_t> LineNumber,
FileCheckPatternContext *Context, const SourceMgr &SM) {
Expr = Expr.ltrim(SpaceChars);
assert(Expr.startswith("("));
auto OptFunc = StringSwitch<Optional<binop_eval_t>>(FuncName)
.Case("add", operator+)
.Case("div", operator/)
.Case("max", max)
.Case("min", min)
.Case("mul", operator*)
.Case("sub", operator-)
.Default(None);
if (!OptFunc)
return ErrorDiagnostic::get(
SM, FuncName, Twine("call to undefined function '") + FuncName + "'");
Expr.consume_front("(");
Expr = Expr.ltrim(SpaceChars);
SmallVector<std::unique_ptr<ExpressionAST>, 4> Args;
while (!Expr.empty() && !Expr.startswith(")")) {
if (Expr.startswith(","))
return ErrorDiagnostic::get(SM, Expr, "missing argument");
StringRef OuterBinOpExpr = Expr;
Expected<std::unique_ptr<ExpressionAST>> Arg = parseNumericOperand(
Expr, AllowedOperand::Any, false, LineNumber,
Context, SM);
while (Arg && !Expr.empty()) {
Expr = Expr.ltrim(SpaceChars);
if (Expr.startswith(",") || Expr.startswith(")"))
break;
Arg = parseBinop(OuterBinOpExpr, Expr, std::move(*Arg), false, LineNumber,
Context, SM);
}
if (!Arg)
return Arg.takeError();
Args.push_back(std::move(*Arg));
Expr = Expr.ltrim(SpaceChars);
if (!Expr.consume_front(","))
break;
Expr = Expr.ltrim(SpaceChars);
if (Expr.startswith(")"))
return ErrorDiagnostic::get(SM, Expr, "missing argument");
}
if (!Expr.consume_front(")"))
return ErrorDiagnostic::get(SM, Expr,
"missing ')' at end of call expression");
const unsigned NumArgs = Args.size();
if (NumArgs == 2)
return std::make_unique<BinaryOperation>(Expr, *OptFunc, std::move(Args[0]),
std::move(Args[1]));
return ErrorDiagnostic::get(SM, FuncName,
Twine("function '") + FuncName +
Twine("' takes 2 arguments but ") +
Twine(NumArgs) + " given");
}
Expected<std::unique_ptr<Expression>> Pattern::parseNumericSubstitutionBlock(
StringRef Expr, Optional<NumericVariable *> &DefinedNumericVariable,
bool IsLegacyLineExpr, Optional<size_t> LineNumber,
FileCheckPatternContext *Context, const SourceMgr &SM) {
std::unique_ptr<ExpressionAST> ExpressionASTPointer = nullptr;
StringRef DefExpr = StringRef();
DefinedNumericVariable = None;
ExpressionFormat ExplicitFormat = ExpressionFormat();
unsigned Precision = 0;
size_t FormatSpecEnd = Expr.find(',');
size_t FunctionStart = Expr.find('(');
if (FormatSpecEnd != StringRef::npos && FormatSpecEnd < FunctionStart) {
StringRef FormatExpr = Expr.take_front(FormatSpecEnd);
Expr = Expr.drop_front(FormatSpecEnd + 1);
FormatExpr = FormatExpr.trim(SpaceChars);
if (!FormatExpr.consume_front("%"))
return ErrorDiagnostic::get(
SM, FormatExpr,
"invalid matching format specification in expression");
SMLoc AlternateFormFlagLoc = SMLoc::getFromPointer(FormatExpr.data());
bool AlternateForm = FormatExpr.consume_front("#");
if (FormatExpr.consume_front(".")) {
if (FormatExpr.consumeInteger(10, Precision))
return ErrorDiagnostic::get(SM, FormatExpr,
"invalid precision in format specifier");
}
if (!FormatExpr.empty()) {
SMLoc FmtLoc = SMLoc::getFromPointer(FormatExpr.data());
switch (popFront(FormatExpr)) {
case 'u':
ExplicitFormat =
ExpressionFormat(ExpressionFormat::Kind::Unsigned, Precision);
break;
case 'd':
ExplicitFormat =
ExpressionFormat(ExpressionFormat::Kind::Signed, Precision);
break;
case 'x':
ExplicitFormat = ExpressionFormat(ExpressionFormat::Kind::HexLower,
Precision, AlternateForm);
break;
case 'X':
ExplicitFormat = ExpressionFormat(ExpressionFormat::Kind::HexUpper,
Precision, AlternateForm);
break;
default:
return ErrorDiagnostic::get(SM, FmtLoc,
"invalid format specifier in expression");
}
}
if (AlternateForm && ExplicitFormat != ExpressionFormat::Kind::HexLower &&
ExplicitFormat != ExpressionFormat::Kind::HexUpper)
return ErrorDiagnostic::get(
SM, AlternateFormFlagLoc,
"alternate form only supported for hex values");
FormatExpr = FormatExpr.ltrim(SpaceChars);
if (!FormatExpr.empty())
return ErrorDiagnostic::get(
SM, FormatExpr,
"invalid matching format specification in expression");
}
size_t DefEnd = Expr.find(':');
if (DefEnd != StringRef::npos) {
DefExpr = Expr.substr(0, DefEnd);
Expr = Expr.substr(DefEnd + 1);
}
Expr = Expr.ltrim(SpaceChars);
bool HasParsedValidConstraint = false;
if (Expr.consume_front("=="))
HasParsedValidConstraint = true;
Expr = Expr.ltrim(SpaceChars);
if (Expr.empty()) {
if (HasParsedValidConstraint)
return ErrorDiagnostic::get(
SM, Expr, "empty numeric expression should not have a constraint");
} else {
Expr = Expr.rtrim(SpaceChars);
StringRef OuterBinOpExpr = Expr;
AllowedOperand AO =
IsLegacyLineExpr ? AllowedOperand::LineVar : AllowedOperand::Any;
Expected<std::unique_ptr<ExpressionAST>> ParseResult = parseNumericOperand(
Expr, AO, !HasParsedValidConstraint, LineNumber, Context, SM);
while (ParseResult && !Expr.empty()) {
ParseResult = parseBinop(OuterBinOpExpr, Expr, std::move(*ParseResult),
IsLegacyLineExpr, LineNumber, Context, SM);
if (ParseResult && IsLegacyLineExpr && !Expr.empty())
return ErrorDiagnostic::get(
SM, Expr,
"unexpected characters at end of expression '" + Expr + "'");
}
if (!ParseResult)
return ParseResult.takeError();
ExpressionASTPointer = std::move(*ParseResult);
}
ExpressionFormat Format;
if (ExplicitFormat)
Format = ExplicitFormat;
else if (ExpressionASTPointer) {
Expected<ExpressionFormat> ImplicitFormat =
ExpressionASTPointer->getImplicitFormat(SM);
if (!ImplicitFormat)
return ImplicitFormat.takeError();
Format = *ImplicitFormat;
}
if (!Format)
Format = ExpressionFormat(ExpressionFormat::Kind::Unsigned, Precision);
std::unique_ptr<Expression> ExpressionPointer =
std::make_unique<Expression>(std::move(ExpressionASTPointer), Format);
if (DefEnd != StringRef::npos) {
DefExpr = DefExpr.ltrim(SpaceChars);
Expected<NumericVariable *> ParseResult = parseNumericVariableDefinition(
DefExpr, Context, LineNumber, ExpressionPointer->getFormat(), SM);
if (!ParseResult)
return ParseResult.takeError();
DefinedNumericVariable = *ParseResult;
}
return std::move(ExpressionPointer);
}
bool Pattern::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))
while (!PatternStr.empty() &&
(PatternStr.back() == ' ' || PatternStr.back() == '\t'))
PatternStr = PatternStr.substr(0, PatternStr.size() - 1);
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;
}
if (CheckTy.isLiteralMatch()) {
FixedStr = PatternStr;
return false;
}
if (!MatchFullLinesHere &&
(PatternStr.size() < 2 ||
(!PatternStr.contains("{{") && !PatternStr.contains("[[")))) {
FixedStr = PatternStr;
return false;
}
if (MatchFullLinesHere) {
RegExStr += '^';
if (!Req.NoCanonicalizeWhiteSpace)
RegExStr += " *";
}
unsigned CurParen = 1;
while (!PatternStr.empty()) {
if (PatternStr.startswith("{{")) {
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;
}
RegExStr += '(';
++CurParen;
if (AddRegExToRegEx(PatternStr.substr(2, End - 2), CurParen, SM))
return true;
RegExStr += ')';
PatternStr = PatternStr.substr(End + 2);
continue;
}
if (PatternStr.startswith("[[") && !PatternStr.startswith("[[[")) {
StringRef UnparsedPatternStr = PatternStr.substr(2);
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;
}
PatternStr = UnparsedPatternStr.substr(End + 2);
bool IsDefinition = false;
bool SubstNeeded = false;
bool IsLegacyLineExpr = false;
StringRef DefName;
StringRef SubstStr;
StringRef MatchRegexp;
std::string WildcardRegexp;
size_t SubstInsertIdx = RegExStr.size();
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;
}
StringRef OrigMatchStr = MatchStr;
Expected<Pattern::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;
}
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 {
if (!MatchStr.empty()) {
SM.PrintMessage(SMLoc::getFromPointer(Name.data()),
SourceMgr::DK_Error,
"invalid name in string variable use");
return true;
}
SubstStr = Name;
}
}
}
std::unique_ptr<Expression> ExpressionPointer;
Optional<NumericVariable *> DefinedNumericVariable;
if (IsNumBlock) {
Expected<std::unique_ptr<Expression>> ParseResult =
parseNumericSubstitutionBlock(MatchStr, DefinedNumericVariable,
IsLegacyLineExpr, LineNumber, Context,
SM);
if (!ParseResult) {
logAllUnhandledErrors(ParseResult.takeError(), errs());
return true;
}
ExpressionPointer = std::move(*ParseResult);
SubstNeeded = ExpressionPointer->getAST() != nullptr;
if (DefinedNumericVariable) {
IsDefinition = true;
DefName = (*DefinedNumericVariable)->getName();
}
if (SubstNeeded)
SubstStr = MatchStr;
else {
ExpressionFormat Format = ExpressionPointer->getFormat();
WildcardRegexp = cantFail(Format.getWildcardRegex());
MatchRegexp = WildcardRegexp;
}
}
if (IsDefinition) {
RegExStr += '(';
++SubstInsertIdx;
if (IsNumBlock) {
NumericVariableMatch NumericVariableDefinition = {
*DefinedNumericVariable, CurParen};
NumericVariableDefs[DefName] = NumericVariableDefinition;
Context->GlobalNumericVariableTable[DefName] =
*DefinedNumericVariable;
} else {
VariableDefs[DefName] = CurParen;
Context->DefinedVariableTable[DefName] = true;
}
++CurParen;
}
if (!MatchRegexp.empty() && AddRegExToRegEx(MatchRegexp, CurParen, SM))
return true;
if (IsDefinition)
RegExStr += ')';
if (SubstNeeded) {
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 {
Substitution *Substitution =
IsNumBlock
? Context->makeNumericSubstitution(
SubstStr, std::move(ExpressionPointer), SubstInsertIdx)
: Context->makeStringSubstitution(SubstStr, SubstInsertIdx);
Substitutions.push_back(Substitution);
}
}
continue;
}
size_t FixedMatchEnd =
std::min(PatternStr.find("{{", 1), PatternStr.find("[[", 1));
RegExStr += Regex::escape(PatternStr.substr(0, FixedMatchEnd));
PatternStr = PatternStr.substr(FixedMatchEnd);
}
if (MatchFullLinesHere) {
if (!Req.NoCanonicalizeWhiteSpace)
RegExStr += " *";
RegExStr += '$';
}
return false;
}
bool Pattern::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 Pattern::AddBackrefToRegEx(unsigned BackrefNum) {
assert(BackrefNum >= 1 && BackrefNum <= 9 && "Invalid backref number");
std::string Backref = std::string("\\") + std::string(1, '0' + BackrefNum);
RegExStr += Backref;
}
Pattern::MatchResult Pattern::match(StringRef Buffer,
const SourceMgr &SM) const {
if (CheckTy == Check::CheckEOF)
return MatchResult(Buffer.size(), 0, Error::success());
if (!FixedStr.empty()) {
size_t Pos =
IgnoreCase ? Buffer.find_insensitive(FixedStr) : Buffer.find(FixedStr);
if (Pos == StringRef::npos)
return make_error<NotFoundError>();
return MatchResult(Pos, FixedStr.size(), Error::success());
}
StringRef RegExToMatch = RegExStr;
std::string TmpStr;
if (!Substitutions.empty()) {
TmpStr = RegExStr;
if (LineNumber)
Context->LineVariable->setValue(ExpressionValue(*LineNumber));
size_t InsertOffset = 0;
Error Errs = Error::success();
for (const auto &Substitution : Substitutions) {
Expected<std::string> Value = Substitution->getResult();
if (!Value) {
Errs = joinErrors(std::move(Errs),
handleErrors(
Value.takeError(),
[&](const OverflowError &E) {
return ErrorDiagnostic::get(
SM, Substitution->getFromString(),
"unable to substitute variable or "
"numeric expression: overflow error");
},
[&SM](const UndefVarError &E) {
return ErrorDiagnostic::get(SM, E.getVarName(),
E.message());
}));
continue;
}
TmpStr.insert(TmpStr.begin() + Substitution->getIndex() + InsertOffset,
Value->begin(), Value->end());
InsertOffset += Value->size();
}
if (Errs)
return std::move(Errs);
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<NotFoundError>();
assert(!MatchInfo.empty() && "Didn't get any match");
StringRef FullMatch = MatchInfo[0];
for (const auto &VariableDef : VariableDefs) {
assert(VariableDef.second < MatchInfo.size() && "Internal paren error");
Context->GlobalVariableTable[VariableDef.first] =
MatchInfo[VariableDef.second];
}
size_t MatchStartSkip = CheckTy == Check::CheckEmpty;
Match TheMatch;
TheMatch.Pos = FullMatch.data() - Buffer.data() + MatchStartSkip;
TheMatch.Len = FullMatch.size() - MatchStartSkip;
for (const auto &NumericVariableDef : NumericVariableDefs) {
const NumericVariableMatch &NumericVariableMatch =
NumericVariableDef.getValue();
unsigned CaptureParenGroup = NumericVariableMatch.CaptureParenGroup;
assert(CaptureParenGroup < MatchInfo.size() && "Internal paren error");
NumericVariable *DefinedNumericVariable =
NumericVariableMatch.DefinedNumericVariable;
StringRef MatchedValue = MatchInfo[CaptureParenGroup];
ExpressionFormat Format = DefinedNumericVariable->getImplicitFormat();
Expected<ExpressionValue> Value =
Format.valueFromStringRepr(MatchedValue, SM);
if (!Value)
return MatchResult(TheMatch, Value.takeError());
DefinedNumericVariable->setValue(*Value, MatchedValue);
}
return MatchResult(TheMatch, Error::success());
}
unsigned Pattern::computeMatchDistance(StringRef Buffer) const {
StringRef ExampleString(FixedStr);
if (ExampleString.empty())
ExampleString = RegExStr;
StringRef BufferPrefix = Buffer.substr(0, ExampleString.size());
BufferPrefix = BufferPrefix.split('\n').first;
return BufferPrefix.edit_distance(ExampleString);
}
void Pattern::printSubstitutions(const SourceMgr &SM, StringRef Buffer,
SMRange Range,
FileCheckDiag::MatchType MatchTy,
std::vector<FileCheckDiag> *Diags) const {
if (!Substitutions.empty()) {
for (const auto &Substitution : Substitutions) {
SmallString<256> Msg;
raw_svector_ostream OS(Msg);
Expected<std::string> MatchedValue = Substitution->getResult();
if (!MatchedValue) {
consumeError(MatchedValue.takeError());
continue;
}
OS << "with \"";
OS.write_escaped(Substitution->getFromString()) << "\" equal to \"";
OS.write_escaped(*MatchedValue) << "\"";
if (Diags)
Diags->emplace_back(SM, CheckTy, getLoc(), MatchTy,
SMRange(Range.Start, Range.Start), OS.str());
else
SM.PrintMessage(Range.Start, SourceMgr::DK_Note, OS.str());
}
}
}
void Pattern::printVariableDefs(const SourceMgr &SM,
FileCheckDiag::MatchType MatchTy,
std::vector<FileCheckDiag> *Diags) const {
if (VariableDefs.empty() && NumericVariableDefs.empty())
return;
struct VarCapture {
StringRef Name;
SMRange Range;
};
SmallVector<VarCapture, 2> VarCaptures;
for (const auto &VariableDef : VariableDefs) {
VarCapture VC;
VC.Name = VariableDef.first;
StringRef Value = Context->GlobalVariableTable[VC.Name];
SMLoc Start = SMLoc::getFromPointer(Value.data());
SMLoc End = SMLoc::getFromPointer(Value.data() + Value.size());
VC.Range = SMRange(Start, End);
VarCaptures.push_back(VC);
}
for (const auto &VariableDef : NumericVariableDefs) {
VarCapture VC;
VC.Name = VariableDef.getKey();
Optional<StringRef> StrValue =
VariableDef.getValue().DefinedNumericVariable->getStringValue();
if (!StrValue)
continue;
SMLoc Start = SMLoc::getFromPointer(StrValue->data());
SMLoc End = SMLoc::getFromPointer(StrValue->data() + StrValue->size());
VC.Range = SMRange(Start, End);
VarCaptures.push_back(VC);
}
llvm::sort(VarCaptures, [](const VarCapture &A, const VarCapture &B) {
if (&A == &B)
return false;
assert(A.Range.Start != B.Range.Start &&
"unexpected overlapping variable captures");
return A.Range.Start.getPointer() < B.Range.Start.getPointer();
});
for (const VarCapture &VC : VarCaptures) {
SmallString<256> Msg;
raw_svector_ostream OS(Msg);
OS << "captured var \"" << VC.Name << "\"";
if (Diags)
Diags->emplace_back(SM, CheckTy, getLoc(), MatchTy, VC.Range, OS.str());
else
SM.PrintMessage(VC.Range.Start, SourceMgr::DK_Note, OS.str(), VC.Range);
}
}
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 AdjustPrevDiags = false) {
SMLoc Start = SMLoc::getFromPointer(Buffer.data() + Pos);
SMLoc End = SMLoc::getFromPointer(Buffer.data() + Pos + Len);
SMRange Range(Start, End);
if (Diags) {
if (AdjustPrevDiags) {
SMLoc CheckLoc = Diags->rbegin()->CheckLoc;
for (auto I = Diags->rbegin(), E = Diags->rend();
I != E && I->CheckLoc == CheckLoc; ++I)
I->MatchTy = MatchTy;
} else
Diags->emplace_back(SM, CheckTy, Loc, MatchTy, Range);
}
return Range;
}
void Pattern::printFuzzyMatch(const SourceMgr &SM, StringRef Buffer,
std::vector<FileCheckDiag> *Diags) const {
size_t NumLinesForward = 0;
size_t Best = StringRef::npos;
double BestQuality = 0;
for (size_t i = 0, e = std::min(size_t(4096), Buffer.size()); i != e; ++i) {
if (Buffer[i] == '\n')
++NumLinesForward;
if (Buffer[i] == ' ' || Buffer[i] == '\t')
continue;
unsigned Distance = computeMatchDistance(Buffer.substr(i));
double Quality = Distance + (NumLinesForward / 100.);
if (Quality < BestQuality || Best == StringRef::npos) {
Best = i;
BestQuality = Quality;
}
}
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");
}
}
Expected<StringRef>
FileCheckPatternContext::getPatternVarValue(StringRef VarName) {
auto VarIter = GlobalVariableTable.find(VarName);
if (VarIter == GlobalVariableTable.end())
return make_error<UndefVarError>(VarName);
return VarIter->second;
}
template <class... Types>
NumericVariable *FileCheckPatternContext::makeNumericVariable(Types... args) {
NumericVariables.push_back(std::make_unique<NumericVariable>(args...));
return NumericVariables.back().get();
}
Substitution *
FileCheckPatternContext::makeStringSubstitution(StringRef VarName,
size_t InsertIdx) {
Substitutions.push_back(
std::make_unique<StringSubstitution>(this, VarName, InsertIdx));
return Substitutions.back().get();
}
Substitution *FileCheckPatternContext::makeNumericSubstitution(
StringRef ExpressionStr, std::unique_ptr<Expression> Expression,
size_t InsertIdx) {
Substitutions.push_back(std::make_unique<NumericSubstitution>(
this, ExpressionStr, std::move(Expression), InsertIdx));
return Substitutions.back().get();
}
size_t Pattern::FindRegexVarEnd(StringRef Str, SourceMgr &SM) {
size_t Offset = 0;
size_t BracketDepth = 0;
while (!Str.empty()) {
if (Str.startswith("]]") && BracketDepth == 0)
return Offset;
if (Str[0] == '\\') {
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) {
if (Ptr <= End - 2 && Ptr[0] == '\r' && Ptr[1] == '\n') {
continue;
}
if (Req.NoCanonicalizeWhiteSpace || (*Ptr != ' ' && *Ptr != '\t')) {
OutputBuffer.push_back(*Ptr);
continue;
}
OutputBuffer.push_back(' ');
while (Ptr + 1 != End && (Ptr[1] == ' ' || Ptr[1] == '\t'))
++Ptr;
}
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, StringRef Note)
: CheckTy(CheckTy), CheckLoc(CheckLoc), MatchTy(MatchTy), Note(Note) {
auto Start = SM.getLineAndColumn(InputRange.Start);
auto End = SM.getLineAndColumn(InputRange.End);
InputStartLine = Start.first;
InputStartCol = Start.second;
InputEndLine = End.first;
InputEndCol = End.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::getModifiersDescription() const {
if (Modifiers.none())
return "";
std::string Ret;
raw_string_ostream OS(Ret);
OS << '{';
if (isLiteralMatch())
OS << "LITERAL";
OS << '}';
return OS.str();
}
std::string Check::FileCheckType::getDescription(StringRef Prefix) const {
auto WithModifiers = [this, Prefix](StringRef Str) -> std::string {
return (Prefix + Str + getModifiersDescription()).str();
};
switch (Kind) {
case Check::CheckNone:
return "invalid";
case Check::CheckMisspelled:
return "misspelled";
case Check::CheckPlain:
if (Count > 1)
return WithModifiers("-COUNT");
return WithModifiers("");
case Check::CheckNext:
return WithModifiers("-NEXT");
case Check::CheckSame:
return WithModifiers("-SAME");
case Check::CheckNot:
return WithModifiers("-NOT");
case Check::CheckDAG:
return WithModifiers("-DAG");
case Check::CheckLabel:
return WithModifiers("-LABEL");
case Check::CheckEmpty:
return WithModifiers("-EMPTY");
case Check::CheckComment:
return std::string(Prefix);
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(const FileCheckRequest &Req, StringRef Buffer, StringRef Prefix,
bool &Misspelled) {
if (Buffer.size() <= Prefix.size())
return {Check::CheckNone, StringRef()};
StringRef Rest = Buffer.drop_front(Prefix.size());
if (llvm::is_contained(Req.CommentPrefixes, Prefix)) {
if (Rest.consume_front(":"))
return {Check::CheckComment, Rest};
return {Check::CheckNone, StringRef()};
}
auto ConsumeModifiers = [&](Check::FileCheckType Ret)
-> std::pair<Check::FileCheckType, StringRef> {
if (Rest.consume_front(":"))
return {Ret, Rest};
if (!Rest.consume_front("{"))
return {Check::CheckNone, StringRef()};
do {
Rest = Rest.ltrim();
if (Rest.consume_front("LITERAL"))
Ret.setLiteralMatch();
else
return {Check::CheckNone, Rest};
Rest = Rest.ltrim();
} while (Rest.consume_front(","));
if (!Rest.consume_front("}:"))
return {Check::CheckNone, Rest};
return {Ret, Rest};
};
if (Rest.consume_front(":"))
return {Check::CheckPlain, Rest};
if (Rest.front() == '{')
return ConsumeModifiers(Check::CheckPlain);
if (Rest.consume_front("_"))
Misspelled = true;
else if (!Rest.consume_front("-"))
return {Check::CheckNone, StringRef()};
if (Rest.consume_front("COUNT-")) {
int64_t Count;
if (Rest.consumeInteger(10, Count))
return {Check::CheckBadCount, Rest};
if (Count <= 0 || Count > INT32_MAX)
return {Check::CheckBadCount, Rest};
if (Rest.front() != ':' && Rest.front() != '{')
return {Check::CheckBadCount, Rest};
return ConsumeModifiers(
Check::FileCheckType(Check::CheckPlain).setCount(Count));
}
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};
if (Rest.consume_front("NEXT"))
return ConsumeModifiers(Check::CheckNext);
if (Rest.consume_front("SAME"))
return ConsumeModifiers(Check::CheckSame);
if (Rest.consume_front("NOT"))
return ConsumeModifiers(Check::CheckNot);
if (Rest.consume_front("DAG"))
return ConsumeModifiers(Check::CheckDAG);
if (Rest.consume_front("LABEL"))
return ConsumeModifiers(Check::CheckLabel);
if (Rest.consume_front("EMPTY"))
return ConsumeModifiers(Check::CheckEmpty);
return {Check::CheckNone, Rest};
}
static std::pair<Check::FileCheckType, StringRef>
FindCheckType(const FileCheckRequest &Req, StringRef Buffer, StringRef Prefix) {
bool Misspelled = false;
auto Res = FindCheckType(Req, Buffer, Prefix, Misspelled);
if (Res.first != Check::CheckNone && Misspelled)
return {Check::CheckMisspelled, Res.second};
return Res;
}
static size_t SkipWord(StringRef Str, size_t Loc) {
while (Loc < Str.size() && IsPartOfWord(Str[Loc]))
++Loc;
return Loc;
}
static std::pair<StringRef, StringRef>
FindFirstMatchingPrefix(const FileCheckRequest &Req, Regex &PrefixRE,
StringRef &Buffer, unsigned &LineNumber,
Check::FileCheckType &CheckTy) {
SmallVector<StringRef, 2> Matches;
while (!Buffer.empty()) {
if (!PrefixRE.match(Buffer, &Matches))
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');
if (Skipped.empty() || !IsPartOfWord(Skipped.back())) {
StringRef AfterSuffix;
std::tie(CheckTy, AfterSuffix) = FindCheckType(Req, Buffer, Prefix);
if (CheckTy != Check::CheckNone)
return {Prefix, AfterSuffix};
}
Buffer = Buffer.drop_front(SkipWord(Buffer, Prefix.size()));
}
return {StringRef(), StringRef()};
}
void FileCheckPatternContext::createLineVariable() {
assert(!LineVariable && "@LINE pseudo numeric variable already created");
StringRef LineName = "@LINE";
LineVariable = makeNumericVariable(
LineName, ExpressionFormat(ExpressionFormat::Kind::Unsigned));
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,
std::pair<unsigned, unsigned> *ImpPatBufferIDRange) {
if (ImpPatBufferIDRange)
ImpPatBufferIDRange->first = ImpPatBufferIDRange->second = 0;
Error DefineError =
PatternContext->defineCmdlineVariables(Req.GlobalDefines, SM);
if (DefineError) {
logAllUnhandledErrors(std::move(DefineError), errs());
return true;
}
PatternContext->createLineVariable();
std::vector<Pattern> ImplicitNegativeChecks;
for (StringRef PatternString : Req.ImplicitCheckNot) {
std::string Prefix = "-implicit-check-not='";
std::string Suffix = "'";
std::unique_ptr<MemoryBuffer> CmdLine = MemoryBuffer::getMemBufferCopy(
(Prefix + PatternString + Suffix).str(), "command line");
StringRef PatternInBuffer =
CmdLine->getBuffer().substr(Prefix.size(), PatternString.size());
unsigned BufferID = SM.AddNewSourceBuffer(std::move(CmdLine), SMLoc());
if (ImpPatBufferIDRange) {
if (ImpPatBufferIDRange->first == ImpPatBufferIDRange->second) {
ImpPatBufferIDRange->first = BufferID;
ImpPatBufferIDRange->second = BufferID + 1;
} else {
assert(BufferID == ImpPatBufferIDRange->second &&
"expected consecutive source buffer IDs");
++ImpPatBufferIDRange->second;
}
}
ImplicitNegativeChecks.push_back(
Pattern(Check::CheckNot, PatternContext.get()));
ImplicitNegativeChecks.back().parsePattern(PatternInBuffer,
"IMPLICIT-CHECK", SM, Req);
}
std::vector<Pattern> DagNotMatches = ImplicitNegativeChecks;
unsigned LineNumber = 1;
std::set<StringRef> PrefixesNotFound(Req.CheckPrefixes.begin(),
Req.CheckPrefixes.end());
const size_t DistinctPrefixes = PrefixesNotFound.size();
while (true) {
Check::FileCheckType CheckTy;
StringRef UsedPrefix;
StringRef AfterSuffix;
std::tie(UsedPrefix, AfterSuffix) =
FindFirstMatchingPrefix(Req, PrefixRE, Buffer, LineNumber, CheckTy);
if (UsedPrefix.empty())
break;
if (CheckTy != Check::CheckComment)
PrefixesNotFound.erase(UsedPrefix);
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!");
const char *UsedPrefixStart = UsedPrefix.data();
Buffer = AfterSuffix.empty() ? Buffer.drop_front(UsedPrefix.size())
: AfterSuffix;
if (CheckTy == Check::CheckMisspelled) {
StringRef UsedDirective(UsedPrefix.data(),
AfterSuffix.data() - UsedPrefix.data());
SM.PrintMessage(SMLoc::getFromPointer(UsedDirective.data()),
SourceMgr::DK_Error,
"misspelled directive '" + UsedDirective + "'");
return true;
}
if (CheckTy == Check::CheckBadNot) {
SM.PrintMessage(SMLoc::getFromPointer(Buffer.data()), SourceMgr::DK_Error,
"unsupported -NOT combo on prefix '" + UsedPrefix + "'");
return true;
}
if (CheckTy == Check::CheckBadCount) {
SM.PrintMessage(SMLoc::getFromPointer(Buffer.data()), SourceMgr::DK_Error,
"invalid count in -COUNT specification on prefix '" +
UsedPrefix + "'");
return true;
}
if (!(Req.NoCanonicalizeWhiteSpace && Req.MatchFullLines))
Buffer = Buffer.substr(Buffer.find_first_not_of(" \t"));
size_t EOL = Buffer.find_first_of("\n\r");
SMLoc PatternLoc = SMLoc::getFromPointer(Buffer.data());
StringRef PatternBuffer = Buffer.substr(0, EOL);
Buffer = Buffer.substr(EOL);
if (CheckTy == Check::CheckComment)
continue;
Pattern P(CheckTy, PatternContext.get(), LineNumber);
if (P.parsePattern(PatternBuffer, UsedPrefix, SM, Req))
return true;
if ((CheckTy == Check::CheckLabel) && P.hasVariable()) {
SM.PrintMessage(
SMLoc::getFromPointer(UsedPrefixStart), SourceMgr::DK_Error,
"found '" + UsedPrefix + "-LABEL:'"
" with variable definition or use");
return true;
}
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;
}
if (CheckTy == Check::CheckDAG || CheckTy == Check::CheckNot) {
DagNotMatches.push_back(P);
continue;
}
CheckStrings->emplace_back(P, UsedPrefix, PatternLoc);
std::swap(DagNotMatches, CheckStrings->back().DagNotStrings);
DagNotMatches = ImplicitNegativeChecks;
}
const bool NoPrefixesFound = PrefixesNotFound.size() == DistinctPrefixes;
const bool SomePrefixesUnexpectedlyNotUsed =
!Req.AllowUnusedPrefixes && !PrefixesNotFound.empty();
if ((NoPrefixesFound || SomePrefixesUnexpectedlyNotUsed) &&
(ImplicitNegativeChecks.empty() || !Req.IsDefaultCheckPrefix)) {
errs() << "error: no check strings found with prefix"
<< (PrefixesNotFound.size() > 1 ? "es " : " ");
bool First = true;
for (StringRef MissingPrefix : PrefixesNotFound) {
if (!First)
errs() << ", ";
errs() << "\'" << MissingPrefix << ":'";
First = false;
}
errs() << '\n';
return true;
}
if (!DagNotMatches.empty()) {
CheckStrings->emplace_back(
Pattern(Check::CheckEOF, PatternContext.get(), LineNumber + 1),
*Req.CheckPrefixes.begin(), SMLoc::getFromPointer(Buffer.data()));
std::swap(DagNotMatches, CheckStrings->back().DagNotStrings);
}
return false;
}
static Error printMatch(bool ExpectedMatch, const SourceMgr &SM,
StringRef Prefix, SMLoc Loc, const Pattern &Pat,
int MatchedCount, StringRef Buffer,
Pattern::MatchResult MatchResult,
const FileCheckRequest &Req,
std::vector<FileCheckDiag> *Diags) {
bool HasError = !ExpectedMatch || MatchResult.TheError;
bool PrintDiag = true;
if (!HasError) {
if (!Req.Verbose)
return ErrorReported::reportedOrSuccess(HasError);
if (!Req.VerboseVerbose && Pat.getCheckTy() == Check::CheckEOF)
return ErrorReported::reportedOrSuccess(HasError);
PrintDiag = !Diags;
}
FileCheckDiag::MatchType MatchTy = ExpectedMatch
? FileCheckDiag::MatchFoundAndExpected
: FileCheckDiag::MatchFoundButExcluded;
SMRange MatchRange = ProcessMatchResult(MatchTy, SM, Loc, Pat.getCheckTy(),
Buffer, MatchResult.TheMatch->Pos,
MatchResult.TheMatch->Len, Diags);
if (Diags) {
Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, Diags);
Pat.printVariableDefs(SM, MatchTy, Diags);
}
if (!PrintDiag) {
assert(!HasError && "expected to report more diagnostics for error");
return ErrorReported::reportedOrSuccess(HasError);
}
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, MatchTy, nullptr);
Pat.printVariableDefs(SM, MatchTy, nullptr);
handleAllErrors(std::move(MatchResult.TheError),
[&](const ErrorDiagnostic &E) {
E.log(errs());
if (Diags) {
Diags->emplace_back(SM, Pat.getCheckTy(), Loc,
FileCheckDiag::MatchFoundErrorNote,
E.getRange(), E.getMessage().str());
}
});
return ErrorReported::reportedOrSuccess(HasError);
}
static Error printNoMatch(bool ExpectedMatch, const SourceMgr &SM,
StringRef Prefix, SMLoc Loc, const Pattern &Pat,
int MatchedCount, StringRef Buffer, Error MatchError,
bool VerboseVerbose,
std::vector<FileCheckDiag> *Diags) {
bool HasError = ExpectedMatch;
bool HasPatternError = false;
FileCheckDiag::MatchType MatchTy = ExpectedMatch
? FileCheckDiag::MatchNoneButExpected
: FileCheckDiag::MatchNoneAndExcluded;
SmallVector<std::string, 4> ErrorMsgs;
handleAllErrors(
std::move(MatchError),
[&](const ErrorDiagnostic &E) {
HasError = HasPatternError = true;
MatchTy = FileCheckDiag::MatchNoneForInvalidPattern;
E.log(errs());
if (Diags)
ErrorMsgs.push_back(E.getMessage().str());
},
[](const NotFoundError &E) {});
bool PrintDiag = true;
if (!HasError) {
if (!VerboseVerbose)
return ErrorReported::reportedOrSuccess(HasError);
PrintDiag = !Diags;
}
SMRange SearchRange = ProcessMatchResult(MatchTy, SM, Loc, Pat.getCheckTy(),
Buffer, 0, Buffer.size(), Diags);
if (Diags) {
SMRange NoteRange = SMRange(SearchRange.Start, SearchRange.Start);
for (StringRef ErrorMsg : ErrorMsgs)
Diags->emplace_back(SM, Pat.getCheckTy(), Loc, MatchTy, NoteRange,
ErrorMsg);
Pat.printSubstitutions(SM, Buffer, SearchRange, MatchTy, Diags);
}
if (!PrintDiag) {
assert(!HasError && "expected to report more diagnostics for error");
return ErrorReported::reportedOrSuccess(HasError);
}
if (!HasPatternError) {
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);
SM.PrintMessage(SearchRange.Start, SourceMgr::DK_Note,
"scanning from here");
}
Pat.printSubstitutions(SM, Buffer, SearchRange, MatchTy, nullptr);
if (ExpectedMatch)
Pat.printFuzzyMatch(SM, Buffer, Diags);
return ErrorReported::reportedOrSuccess(HasError);
}
static Error reportMatchResult(bool ExpectedMatch, const SourceMgr &SM,
StringRef Prefix, SMLoc Loc, const Pattern &Pat,
int MatchedCount, StringRef Buffer,
Pattern::MatchResult MatchResult,
const FileCheckRequest &Req,
std::vector<FileCheckDiag> *Diags) {
if (MatchResult.TheMatch)
return printMatch(ExpectedMatch, SM, Prefix, Loc, Pat, MatchedCount, Buffer,
std::move(MatchResult), Req, Diags);
return printNoMatch(ExpectedMatch, SM, Prefix, Loc, Pat, MatchedCount, Buffer,
std::move(MatchResult.TheError), Req.VerboseVerbose,
Diags);
}
static unsigned CountNumNewlinesBetween(StringRef Range,
const char *&FirstNewLine) {
unsigned NumNewLines = 0;
while (true) {
Range = Range.substr(Range.find_first_of("\n\r"));
if (Range.empty())
return NumNewLines;
++NumNewLines;
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 Pattern *> NotStrings;
if (!IsLabelScanMode) {
LastPos = CheckDag(SM, Buffer, NotStrings, Req, Diags);
if (LastPos == StringRef::npos)
return StringRef::npos;
}
size_t LastMatchEnd = LastPos;
size_t FirstMatchPos = 0;
assert(Pat.getCount() != 0 && "pattern count can not be zero");
for (int i = 1; i <= Pat.getCount(); i++) {
StringRef MatchBuffer = Buffer.substr(LastMatchEnd);
Pattern::MatchResult MatchResult = Pat.match(MatchBuffer, SM);
if (Error Err = reportMatchResult(true, SM, Prefix, Loc,
Pat, i, MatchBuffer,
std::move(MatchResult), Req, Diags)) {
cantFail(handleErrors(std::move(Err), [&](const ErrorReported &E) {}));
return StringRef::npos;
}
size_t MatchPos = MatchResult.TheMatch->Pos;
if (i == 1)
FirstMatchPos = LastPos + MatchPos;
LastMatchEnd += MatchPos + MatchResult.TheMatch->Len;
}
MatchLen = LastMatchEnd - FirstMatchPos;
if (!IsLabelScanMode) {
size_t MatchPos = FirstMatchPos - LastPos;
StringRef MatchBuffer = Buffer.substr(LastPos);
StringRef SkippedRegion = Buffer.substr(LastPos, MatchPos);
if (CheckNext(SM, SkippedRegion)) {
ProcessMatchResult(FileCheckDiag::MatchFoundButWrongLine, SM, Loc,
Pat.getCheckTy(), MatchBuffer, MatchPos, MatchLen,
Diags, Req.Verbose);
return StringRef::npos;
}
if (CheckSame(SM, SkippedRegion)) {
ProcessMatchResult(FileCheckDiag::MatchFoundButWrongLine, SM, Loc,
Pat.getCheckTy(), MatchBuffer, MatchPos, MatchLen,
Diags, Req.Verbose);
return StringRef::npos;
}
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");
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;
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 Pattern *> &NotStrings,
const FileCheckRequest &Req,
std::vector<FileCheckDiag> *Diags) const {
bool DirectiveFail = false;
for (const Pattern *Pat : NotStrings) {
assert((Pat->getCheckTy() == Check::CheckNot) && "Expect CHECK-NOT!");
Pattern::MatchResult MatchResult = Pat->match(Buffer, SM);
if (Error Err = reportMatchResult(false, SM, Prefix,
Pat->getLoc(), *Pat, 1, Buffer,
std::move(MatchResult), Req, Diags)) {
cantFail(handleErrors(std::move(Err), [&](const ErrorReported &E) {}));
DirectiveFail = true;
continue;
}
}
return DirectiveFail;
}
size_t FileCheckString::CheckDag(const SourceMgr &SM, StringRef Buffer,
std::vector<const Pattern *> &NotStrings,
const FileCheckRequest &Req,
std::vector<FileCheckDiag> *Diags) const {
if (DagNotStrings.empty())
return 0;
size_t StartPos = 0;
struct MatchRange {
size_t Pos;
size_t End;
};
std::list<MatchRange> MatchRanges;
for (auto PatItr = DagNotStrings.begin(), PatEnd = DagNotStrings.end();
PatItr != PatEnd; ++PatItr) {
const Pattern &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!");
size_t MatchLen = 0, MatchPos = StartPos;
for (auto MI = MatchRanges.begin(), ME = MatchRanges.end(); true; ++MI) {
StringRef MatchBuffer = Buffer.substr(MatchPos);
Pattern::MatchResult MatchResult = Pat.match(MatchBuffer, SM);
if (MatchResult.TheError || Req.VerboseVerbose) {
if (Error Err = reportMatchResult(true, SM, Prefix,
Pat.getLoc(), Pat, 1, MatchBuffer,
std::move(MatchResult), Req, Diags)) {
cantFail(
handleErrors(std::move(Err), [&](const ErrorReported &E) {}));
return StringRef::npos;
}
}
MatchLen = MatchResult.TheMatch->Len;
MatchPos += MatchResult.TheMatch->Pos;
MatchRange M{MatchPos, MatchPos + MatchLen};
if (Req.AllowDeprecatedDagOverlap) {
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;
}
bool Overlap = false;
for (; MI != ME; ++MI) {
if (M.Pos < MI->End) {
Overlap = MI->Pos < M.End;
break;
}
}
if (!Overlap) {
MatchRanges.insert(MI, M);
break;
}
if (Req.VerboseVerbose) {
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 {
SMLoc CheckLoc = Diags->rbegin()->CheckLoc;
for (auto I = Diags->rbegin(), E = Diags->rend();
I != E && I->CheckLoc == CheckLoc; ++I)
I->MatchTy = FileCheckDiag::MatchFoundButDiscarded;
}
}
MatchPos = MI->End;
}
if (!Req.VerboseVerbose)
cantFail(printMatch(
true, SM, Prefix, Pat.getLoc(), Pat, 1, Buffer,
Pattern::MatchResult(MatchPos, MatchLen, Error::success()), Req,
Diags));
if (std::next(PatItr) == PatEnd ||
std::next(PatItr)->getCheckTy() == Check::CheckNot) {
if (!NotStrings.empty()) {
StringRef SkippedRegion =
Buffer.slice(StartPos, MatchRanges.begin()->Pos);
if (CheckNot(SM, SkippedRegion, NotStrings, Req, Diags))
return StringRef::npos;
NotStrings.clear();
}
StartPos = MatchRanges.rbegin()->End;
MatchRanges.clear();
}
}
return StartPos;
}
static bool ValidatePrefixes(StringRef Kind, StringSet<> &UniquePrefixes,
ArrayRef<StringRef> SuppliedPrefixes) {
for (StringRef Prefix : SuppliedPrefixes) {
if (Prefix.empty()) {
errs() << "error: supplied " << Kind << " prefix must not be the empty "
<< "string\n";
return false;
}
static const Regex Validator("^[a-zA-Z0-9_-]*$");
if (!Validator.match(Prefix)) {
errs() << "error: supplied " << Kind << " prefix must start with a "
<< "letter and contain only alphanumeric characters, hyphens, and "
<< "underscores: '" << Prefix << "'\n";
return false;
}
if (!UniquePrefixes.insert(Prefix).second) {
errs() << "error: supplied " << Kind << " prefix must be unique among "
<< "check and comment prefixes: '" << Prefix << "'\n";
return false;
}
}
return true;
}
static const char *DefaultCheckPrefixes[] = {"CHECK"};
static const char *DefaultCommentPrefixes[] = {"COM", "RUN"};
bool FileCheck::ValidateCheckPrefixes() {
StringSet<> UniquePrefixes;
if (Req.CheckPrefixes.empty()) {
for (const char *Prefix : DefaultCheckPrefixes)
UniquePrefixes.insert(Prefix);
}
if (Req.CommentPrefixes.empty()) {
for (const char *Prefix : DefaultCommentPrefixes)
UniquePrefixes.insert(Prefix);
}
if (!ValidatePrefixes("check", UniquePrefixes, Req.CheckPrefixes))
return false;
if (!ValidatePrefixes("comment", UniquePrefixes, Req.CommentPrefixes))
return false;
return true;
}
Regex FileCheck::buildCheckPrefixRegex() {
if (Req.CheckPrefixes.empty()) {
for (const char *Prefix : DefaultCheckPrefixes)
Req.CheckPrefixes.push_back(Prefix);
Req.IsDefaultCheckPrefix = true;
}
if (Req.CommentPrefixes.empty()) {
for (const char *Prefix : DefaultCommentPrefixes)
Req.CommentPrefixes.push_back(Prefix);
}
SmallString<32> PrefixRegexStr;
for (size_t I = 0, E = Req.CheckPrefixes.size(); I != E; ++I) {
if (I != 0)
PrefixRegexStr.push_back('|');
PrefixRegexStr.append(Req.CheckPrefixes[I]);
}
for (StringRef Prefix : Req.CommentPrefixes) {
PrefixRegexStr.push_back('|');
PrefixRegexStr.append(Prefix);
}
return Regex(PrefixRegexStr);
}
Error FileCheckPatternContext::defineCmdlineVariables(
ArrayRef<StringRef> CmdlineDefines, SourceMgr &SM) {
assert(GlobalVariableTable.empty() && GlobalNumericVariableTable.empty() &&
"Overriding defined variable with command-line variable definitions");
if (CmdlineDefines.empty())
return Error::success();
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;
}
if (CmdlineDef[0] == '#') {
CmdlineDefsDiag += (DefPrefix + CmdlineDef + " (parsed as: [[").str();
std::string SubstitutionStr = std::string(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();
}
}
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),
ErrorDiagnostic::get(SM, CmdlineDef,
"missing equal sign in global definition"));
continue;
}
if (CmdlineDef[0] == '#') {
StringRef CmdlineDefExpr = CmdlineDef.substr(1);
Optional<NumericVariable *> DefinedNumericVariable;
Expected<std::unique_ptr<Expression>> ExpressionResult =
Pattern::parseNumericSubstitutionBlock(
CmdlineDefExpr, DefinedNumericVariable, false, None, this, SM);
if (!ExpressionResult) {
Errs = joinErrors(std::move(Errs), ExpressionResult.takeError());
continue;
}
std::unique_ptr<Expression> Expression = std::move(*ExpressionResult);
Expected<ExpressionValue> Value = Expression->getAST()->eval();
if (!Value) {
Errs = joinErrors(std::move(Errs), Value.takeError());
continue;
}
assert(DefinedNumericVariable && "No variable defined");
(*DefinedNumericVariable)->setValue(*Value);
GlobalNumericVariableTable[(*DefinedNumericVariable)->getName()] =
*DefinedNumericVariable;
} else {
std::pair<StringRef, StringRef> CmdlineNameVal = CmdlineDef.split('=');
StringRef CmdlineName = CmdlineNameVal.first;
StringRef OrigCmdlineName = CmdlineName;
Expected<Pattern::VariableProperties> ParseVarResult =
Pattern::parseVariable(CmdlineName, SM);
if (!ParseVarResult) {
Errs = joinErrors(std::move(Errs), ParseVarResult.takeError());
continue;
}
if (ParseVarResult->IsPseudo || !CmdlineName.empty()) {
Errs = joinErrors(std::move(Errs),
ErrorDiagnostic::get(
SM, OrigCmdlineName,
"invalid name in string variable definition '" +
OrigCmdlineName + "'"));
continue;
}
StringRef Name = ParseVarResult->Name;
if (GlobalNumericVariableTable.find(Name) !=
GlobalNumericVariableTable.end()) {
Errs = joinErrors(std::move(Errs),
ErrorDiagnostic::get(SM, Name,
"numeric variable with name '" +
Name + "' already exists"));
continue;
}
GlobalVariableTable.insert(CmdlineNameVal);
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());
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;
}
size_t MatchLabelLen = 0;
size_t MatchLabelPos =
CheckLabelStr.Check(SM, Buffer, true, MatchLabelLen, Req, Diags);
if (MatchLabelPos == StringRef::npos)
return false;
CheckRegion = Buffer.substr(0, MatchLabelPos + MatchLabelLen);
Buffer = Buffer.substr(MatchLabelPos + MatchLabelLen);
++j;
}
if (i != 0 && Req.EnableVarScope)
PatternContext->clearLocalVars();
for (; i != j; ++i) {
const FileCheckString &CheckStr = (*CheckStrings)[i];
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;
}
return !ChecksFailed;
}