diff --git a/llvm/docs/CommandGuide/FileCheck.rst b/llvm/docs/CommandGuide/FileCheck.rst --- a/llvm/docs/CommandGuide/FileCheck.rst +++ b/llvm/docs/CommandGuide/FileCheck.rst @@ -662,9 +662,15 @@ The ``--enable-var-scope`` option has the same effect on numeric variables as on string variables. -Important note: In its current implementation, an expression cannot use a -numeric variable with a non-empty expression constraint defined on the same -line. +Important note: In its current implementation, an expression using numeric +variable defined on the same line can fail to match even though an input line +satisfies it. This happens when there is more than one possible match in the +line that should match when ignoring the constraint and considering only the +matching format (ie. matching an unsigned number with [[:digit:]]+ and the +first match does not satisfy the constraint. This is due to those expressions +constraints not being able to be expressed at the regular expression level and +the regular expression engine not offering the possibility of getting all +possible matches. FileCheck Pseudo Numeric Variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/llvm/include/llvm/Support/FileCheck.h b/llvm/include/llvm/Support/FileCheck.h --- a/llvm/include/llvm/Support/FileCheck.h +++ b/llvm/include/llvm/Support/FileCheck.h @@ -143,17 +143,25 @@ /// which case it is stored in UnsignedValue). bool Signed; + /// Whether this is a tentative value, ie other expressions on the same CHECK + /// line that might be using that value or other values that this value was + /// computed from have not yet been verified to match. True if any of the + /// operand involved in its computation had a tentative value. + bool Tentative; + public: /// Constructor for a signed value. explicit FileCheckExpressionValue(int64_t Val) - : SignedValue(Val), Signed(true) {} + : SignedValue(Val), Signed(true), Tentative(false) {} /// Constructor for an unsigned value. explicit FileCheckExpressionValue(uint64_t Val) - : UnsignedValue(Val), Signed(false) {} + : UnsignedValue(Val), Signed(false), Tentative(false) {} /// Define equality to be true only if both values are valid and they have - /// the same signedness and corresponding value. + /// the same signedness and corresponding value. Tentative bit is ignored to + /// allow the evaluation of an expression using variables with tentative + /// value to compare equal to a matched value. bool operator==(const FileCheckExpressionValue &other); bool operator!=(const FileCheckExpressionValue &other) { return !(*this == other); @@ -161,6 +169,12 @@ bool isSigned() const { return Signed; } + /// \returns whether this is a tentative value, i.e. other expressions on the + /// same CHECK line that might be using that value or other values that this + /// value was computed from have not yet been verified to match. True if any + /// of the operand involved in its computation had a tentative value. + bool isTentative() const { return Tentative; } + /// \returns the signed value. Must only be called if value is signed in the /// first place. int64_t getSignedValue() const { @@ -175,6 +189,10 @@ return UnsignedValue; } + /// Changes whether this value is tentative or not and return whether it is + /// now a tentative value. + bool toggleTentative() { return Tentative = !Tentative; } + /// Converts this value to a signed value. \returns an error if not possible /// (original value was not within range for a signed integer). Error convertSigned(); @@ -262,21 +280,37 @@ /// value when matching it. FileCheckExpressionFormat Format; + /// Whether the value of this numeric expression can be known at match time + /// (ie. when substituting it in the regular expression). If False, the + /// value is known after matching has happened. + bool MatchTimeKnown; + public: /// Generic constructor for an expression whose equality constraint is - /// represented by \p AST and matching format is \p Format. If matching format - /// is unset (ie. no explicit or implicit matching format), set it to default + /// represented by \p AST, whose matching format is \p Format and which is + /// known at match time if \p MatchTimeKnown is true. If matching format is + /// unset (i.e. no explicit or implicit matching format), set it to default /// one (unsigned decimal integer). FileCheckExpression(std::shared_ptr AST, - FileCheckExpressionFormat Format); + FileCheckExpressionFormat Format, bool MatchTimeKnown); /// \returns pointer to AST of the expression. Pointer is guaranteed to be /// valid as long as this object is. FileCheckExpressionAST *getAST() const { return AST.get(); } + /// \returns whether the value of this expression can be known at match time + /// (i.e. when substituting it in the regular expression). If false, the + /// value is known after matching has happened. + bool isMatchTimeKnown() const { return MatchTimeKnown; } + /// \returns effective format of this expression, ie (i) its explicit format /// if any, (ii) its implicit format if any or (iii) the default format. FileCheckExpressionFormat getEffectiveFormat() { return Format; } + + /// Verifies that the matched value in \p MatchedValue satisfies the + /// constraint expressed by this expression. \returns true if constraint is + /// not satisfied. + bool verifyConstraint(FileCheckExpressionValue MatchedValue) const; }; /// Class representing a numeric variable with a given value in the AST of an @@ -329,9 +363,14 @@ /// \returns implicit format of this numeric variable. FileCheckExpressionFormat getImplicitFormat() const; - /// Sets value of this numeric variable if not defined. \returns whether the - /// variable was already defined. - bool setValue(FileCheckExpressionValue Value); + /// Sets value of this numeric variable to \p Value if not defined. Value is + /// set as a tentative value if \p Tentative is true. \returns whether the + /// variable was already definitely (i.e. not tentatively) defined. + bool setValue(FileCheckExpressionValue Value, bool Tentative); + + /// Marks value as definitive (i.e. not tentative). \returns whether value + /// was already definitive. + bool commitValue(); /// Clears value of this numeric variable. \returns whether the variable was /// already undefined. @@ -549,7 +588,7 @@ /// context is destroyed. FileCheckExpression * makeExpression(std::shared_ptr AST, - FileCheckExpressionFormat Format); + FileCheckExpressionFormat Format, bool MatchTimeKnown); /// Makes a new string substitution and registers it for destruction when the /// context is destroyed. @@ -637,11 +676,17 @@ /// iterating over values. std::map VariableDefs; - /// Structure representing the definition of a numeric variable in a pattern. - /// It holds the pointer to the class instance holding the value and matching - /// format of the numeric variable whose value is being defined and the - /// number of the parenthesis group in RegExStr to capture that value. + /// Structure representing an expression whose constraint needs to be + /// verified after matching (either a variable definition with an empty + /// expression or an expression using directly or indirectly such a variable. + /// It holds a pointer to the class instance holding the expression to + /// verify, a pointer to the class instance holding the value and matching + /// format of the numeric variable being defined, if any, and the number of + /// the parenthesis group in RegExStr to capture that value. struct FileCheckNumericVariableMatch { + /// Pointer to class instance representing the expression to verify. + FileCheckExpression *Expression; + /// Pointer to class instance holding the value and matching format of the /// numeric variable being defined. FileCheckNumericVariable *DefinedNumericVariable; @@ -651,11 +696,12 @@ unsigned CaptureParenGroup; }; - /// Holds the number of the parenthesis group in RegExStr and pointer to the - /// corresponding FileCheckNumericVariable class instance of all numeric - /// variable definitions. Used to set the matched value of all those - /// variables. - StringMap NumericVariableDefs; + /// Holds the number of the parenthesis group in RegExStr, pointer to the + /// FileCheckExpression class instance of the expression to verify and + /// pointer to the FileCheckNumericVariable class instance of the variable + /// defined from this expression, if any, for all expressions that needs to + /// be verified after matching. + std::vector CapturedExpressions; /// Pointer to a class instance holding the global state shared by all /// patterns: @@ -732,6 +778,10 @@ const SourceMgr &SM) const; /// Prints the value of successful substitutions or the name of the undefined /// string or numeric variables preventing a successful substitution. + /// + /// Note: Numeric expression using numeric variables defined on the same line + /// are not shown because we only have substitutions for numeric expressions + /// whose value can be known at match time. void printSubstitutions(const SourceMgr &SM, StringRef Buffer, SMRange MatchRange = None) const; void printFuzzyMatch(const SourceMgr &SM, StringRef Buffer, @@ -782,20 +832,25 @@ /// variables, depending on the value of \p AO. Parameter \p Context points /// to the class instance holding the live string and numeric variables. /// \returns the class representing that operand in the AST of the expression - /// or an error holding a diagnostic against \p SM otherwise. + /// or an error holding a diagnostic against \p SM otherwise. Also sets + /// \p MatchTimeKnown to whether the operand's value is known at match time. static Expected> - parseNumericOperand(StringRef &Expr, AllowedOperand AO, size_t LineNumber, + parseNumericOperand(StringRef &Expr, enum AllowedOperand AO, + bool &MatchTimeKnown, size_t LineNumber, FileCheckPatternContext *Context, const SourceMgr &SM); /// Parses \p Expr for a binary operation at line \p LineNumber, where 0 - /// indicates the command-line. The left operand of this binary operation is - /// given in \p LeftOp and \p LegacyLineExpr indicates whether we are parsing - /// a legacy @LINE expression. Parameter \p Context points to the class - /// instance holding the live string and numeric variables. \returns the - /// class representing the binary operation in the AST of the expression, or - /// an error holding a diagnostic against \p SM otherwise. + /// indicates the command-line. If \p LegacyLineExpr is true, parses \p Expr + /// as a legacy @LINE expression. The left operand of this binary operation + /// is given in \p LeftOp with \p MatchTimeKnown holding whether the left + /// operand's value is known at match time. Parameter \p Context points to + /// the class instance holding the live string and numeric variables. + /// \returns the class representing the binary operation in the AST of the + /// expression, or an error holding a diagnostic against \p SM otherwise. + /// Also sets \p MatchTimeKnown to whether the binary operation can be + /// evaluated at match time. static Expected> parseBinop(StringRef &Expr, std::shared_ptr LeftOp, - bool LegacyLineExpr, size_t LineNumber, + bool LegacyLineExpr, bool &MatchTimeKnown, size_t LineNumber, FileCheckPatternContext *Context, const SourceMgr &SM); }; diff --git a/llvm/lib/Support/FileCheck.cpp b/llvm/lib/Support/FileCheck.cpp --- a/llvm/lib/Support/FileCheck.cpp +++ b/llvm/lib/Support/FileCheck.cpp @@ -183,8 +183,8 @@ FileCheckExpression::FileCheckExpression( std::shared_ptr AST, - FileCheckExpressionFormat Format) - : AST(AST) { + FileCheckExpressionFormat Format, bool MatchTimeKnown) + : AST(AST), MatchTimeKnown(MatchTimeKnown) { this->Format = Format.Valid ? Format : FormatUnsigned; } @@ -202,26 +202,45 @@ if (Value) return true; - return Expression != nullptr && Expression->getAST() != nullptr; + return Expression != nullptr && Expression->isMatchTimeKnown(); } FileCheckExpressionFormat FileCheckNumericVariable::getImplicitFormat() const { return Expression ? Expression->getEffectiveFormat() : FormatNone; } -bool FileCheckNumericVariable::setValue(FileCheckExpressionValue NewValue) { - if (Value) +bool FileCheckNumericVariable::setValue(FileCheckExpressionValue NewValue, + bool Tentative) { + if (Value && !Value->isTentative()) return true; + + assert(Expression != nullptr && "Setting value of parse time variable"); + assert(!NewValue.isTentative() && "Setting value from tentative value"); + if (Tentative) + NewValue.toggleTentative(); if (Expression != nullptr && Expression->getAST() != nullptr) { Expected EvaluatedValue = Expression->getAST()->eval(); - if (!EvaluatedValue || *EvaluatedValue != NewValue) + if (!EvaluatedValue) { + consumeError(EvaluatedValue.takeError()); + return true; + } + if (*EvaluatedValue != NewValue) return true; } Value = NewValue; return false; } +bool FileCheckNumericVariable::commitValue() { + // Value is not a valid tentative value. + if (!Value || !Value->isTentative()) + return true; + + Value->toggleTentative(); + return false; +} + bool FileCheckNumericVariable::clearValue() { if (!Value) return true; @@ -256,7 +275,32 @@ return std::move(ConvError); } - return EvalBinop(LeftValue, RightValue); + Expected ResultValue = + EvalBinop(LeftValue, RightValue); + if (!ResultValue) + return ResultValue; + + // Mark result as tentative if any of the operand was tentative. Assert + // result was not tentative beforehand to have that check only here. + assert(!ResultValue->isTentative() && + "Binary operation returned tentative value"); + if (LeftValue.isTentative() || RightValue.isTentative()) + ResultValue->toggleTentative(); + return *ResultValue; +} + +bool FileCheckExpression::verifyConstraint( + FileCheckExpressionValue MatchedValue) const { + // Nothing to verify: numeric variable definition with empty expression. + if (AST == nullptr) + return false; + + Expected EvaluatedValue = AST->eval(); + if (!EvaluatedValue) { + consumeError(EvaluatedValue.takeError()); + return true; + } + return *EvaluatedValue != MatchedValue; } FileCheckExpressionFormat FileCheckASTBinop::getImplicitFormat() const { @@ -389,21 +433,12 @@ return FileCheckErrorDiagnostic::get( SM, Name, "using undefined numeric variable '" + Name + "'"); - std::shared_ptr NumericVariable = - VarTableIter->second; - if (!IsPseudo && NumericVariable->getDefLineNumber() == LineNumber && - !NumericVariable->isMatchTimeKnown()) - return FileCheckErrorDiagnostic::get( - SM, Name, - "numeric variable '" + Name + - "' defined from input on the same line as used"); - - return NumericVariable; + return VarTableIter->second; } Expected> FileCheckPattern::parseNumericOperand(StringRef &Expr, AllowedOperand AO, - size_t LineNumber, + bool &MatchTimeKnown, size_t LineNumber, FileCheckPatternContext *Context, const SourceMgr &SM) { if (AO == LineVar || AO == Any) { @@ -412,7 +447,12 @@ Expected ParseVarResult = parseVariable(Expr, IsPseudo, SM); if (ParseVarResult) { StringRef Name = *ParseVarResult; - return parseNumericVariableUse(Name, IsPseudo, LineNumber, Context, SM); + Expected> NumericVariable = + parseNumericVariableUse(Name, IsPseudo, LineNumber, Context, SM); + if (NumericVariable) + MatchTimeKnown = (*NumericVariable)->getDefLineNumber() < LineNumber || + (*NumericVariable)->isMatchTimeKnown(); + return NumericVariable; } if (AO == LineVar) return ParseVarResult.takeError(); @@ -426,11 +466,15 @@ uint64_t UnsignedLiteralValue; StringRef SaveExpr = Expr; // Accept both signed and unsigned literal. - if (AO == Any && !Expr.consumeInteger(Radix, SignedLiteralValue)) + if (AO == Any && !Expr.consumeInteger(Radix, SignedLiteralValue)) { + MatchTimeKnown = true; return std::make_shared(SignedLiteralValue); + } Expr = SaveExpr; - if (!Expr.consumeInteger(Radix, UnsignedLiteralValue)) + if (!Expr.consumeInteger(Radix, UnsignedLiteralValue)) { + MatchTimeKnown = true; return std::make_shared(UnsignedLiteralValue); + } return FileCheckErrorDiagnostic::get(SM, Expr, "invalid operand format '" + Expr + "'"); @@ -438,8 +482,8 @@ Expected> FileCheckPattern::parseBinop( StringRef &Expr, std::shared_ptr LeftOp, - bool LegacyLineExpr, size_t LineNumber, FileCheckPatternContext *Context, - const SourceMgr &SM) { + bool LegacyLineExpr, bool &MatchTimeKnown, size_t LineNumber, + FileCheckPatternContext *Context, const SourceMgr &SM) { Expr = Expr.ltrim(SpaceChars); if (Expr.empty()) return LeftOp; @@ -468,13 +512,15 @@ "missing operand in expression"); // Second operand in legacy @LINE expression is a literal. AllowedOperand AO = LegacyLineExpr ? Literal : Any; + bool OprMatchTimeKnown; Expected> RightOpResult = - parseNumericOperand(Expr, AO, LineNumber, Context, SM); + parseNumericOperand(Expr, AO, OprMatchTimeKnown, LineNumber, Context, SM); if (!RightOpResult) return RightOpResult; std::shared_ptr RightOp = *RightOpResult; Expr = Expr.ltrim(SpaceChars); + MatchTimeKnown = MatchTimeKnown && OprMatchTimeKnown; return std::make_shared(EvalBinop, LeftOp, RightOp); } @@ -540,6 +586,7 @@ // Parse the expression itself. Expr = Expr.ltrim(SpaceChars); StringRef UseExpr = Expr; + bool MatchTimeKnown = false; if (Expr.empty()) { if (Expr.size() != ConstraintExpr.size()) return FileCheckErrorDiagnostic::get( @@ -548,10 +595,10 @@ // First operand in legacy @LINE expression is the @LINE pseudo variable. AllowedOperand AO = LegacyLineExpr ? LineVar : Any; Expected> ParseResult = - parseNumericOperand(Expr, AO, LineNumber, Context, SM); + parseNumericOperand(Expr, AO, MatchTimeKnown, LineNumber, Context, SM); while (ParseResult && !Expr.empty()) { - ParseResult = parseBinop(Expr, *ParseResult, LegacyLineExpr, LineNumber, - Context, SM); + ParseResult = parseBinop(Expr, *ParseResult, LegacyLineExpr, + MatchTimeKnown, LineNumber, Context, SM); // Legacy @LINE expressions only allow 2 operands. if (ParseResult && LegacyLineExpr && !Expr.empty()) return FileCheckErrorDiagnostic::get( @@ -578,7 +625,7 @@ Format = ImplicitFormat; FileCheckExpression *Expression = - Context->makeExpression(ExpressionAST, Format); + Context->makeExpression(ExpressionAST, Format, MatchTimeKnown); // Parse the numeric variable definition. if (DefEnd != StringRef::npos) { @@ -719,6 +766,7 @@ bool IsDefinition = false; bool SubstNeeded = false; + bool CaptureNeeded = false; bool IsLegacyLineExpr = false; StringRef DefName; StringRef SubstStr; @@ -746,7 +794,7 @@ } StringRef Name = *ParseVarResult; - IsDefinition = (VarEndIdx != StringRef::npos); + CaptureNeeded = IsDefinition = (VarEndIdx != StringRef::npos); SubstNeeded = !IsDefinition; if (IsDefinition) { if ((IsPseudo || !MatchStr.consume_front(":"))) { @@ -778,21 +826,25 @@ // Parse numeric substitution block. FileCheckExpression *Expression; - Optional DefinedNumericVariable; + FileCheckNumericVariable *DefinedNumericVariable = nullptr; if (IsNumBlock) { + Optional DefinedNumericVariableResult; Expected ParseResult = - parseNumericSubstitutionBlock(MatchStr, DefinedNumericVariable, - IsLegacyLineExpr, LineNumber, Context, - SM); + parseNumericSubstitutionBlock( + MatchStr, DefinedNumericVariableResult, IsLegacyLineExpr, + LineNumber, Context, SM); if (!ParseResult) { logAllUnhandledErrors(ParseResult.takeError(), errs()); return true; } Expression = *ParseResult; - IsDefinition = static_cast(DefinedNumericVariable); - SubstNeeded = Expression->getAST() != nullptr; - if (IsDefinition) - DefName = (*DefinedNumericVariable)->getName(); + IsDefinition = static_cast(DefinedNumericVariableResult); + SubstNeeded = Expression->isMatchTimeKnown(); + CaptureNeeded = IsDefinition || !Expression->isMatchTimeKnown(); + if (IsDefinition) { + DefinedNumericVariable = *DefinedNumericVariableResult; + DefName = DefinedNumericVariable->getName(); + } if (SubstNeeded) SubstStr = MatchStr; else { @@ -801,22 +853,22 @@ } } - // Handle variable definition: [[:(...)]] and [[#(...):(...)]]. - if (IsDefinition) { + // Handle variable definition: [[:(...)]] and [[#(...):(...)]] + // and expression needing verifying. + if (CaptureNeeded) { RegExStr += '('; ++SubstInsertIdx; if (IsNumBlock) { struct FileCheckNumericVariableMatch NumericVariableDefinition = { - *DefinedNumericVariable, CurParen}; - NumericVariableDefs[DefName] = NumericVariableDefinition; + Expression, DefinedNumericVariable, CurParen}; + CapturedExpressions.emplace_back(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] = - std::shared_ptr( - *DefinedNumericVariable); + std::shared_ptr(DefinedNumericVariable); } else { VariableDefs[DefName] = CurParen; // Mark string variable as defined to detect collisions between @@ -834,7 +886,7 @@ if (!MatchRegexp.empty() && AddRegExToRegEx(MatchRegexp, CurParen, SM)) return true; - if (IsDefinition) + if (CaptureNeeded) RegExStr += ')'; // Handle substitutions: [[foo]] and [[#]]. @@ -930,7 +982,25 @@ 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. + // by back-references. For expressions using variable with empty expression + // defined on the same line, RegExStr is filled with a suitable wildcard + // pattern and the constraint is then verified against the matched value. + // + // Known issue: Due to the constraints for expressions using variable + // variable defined in earlier line not being expressed at the regular + // expression level, the wrong values might be matched, leading to a + // constraint check failure. An example of this is what happens when + // checking the line "1 3 4" against the directive + // "CHECK: [[#N:]] [[#N+1]]" which gets substituted by + // "[[:digit:]]+ [[:digit:]]+". This would match 1 3 which then fails the + // constraint checks. + // + // One possible solution would be to augment the regular expression engine + // to be able to return all matches for a given pattern. FileCheck could + // then test all possibility and fail the check only if none of them + // satisfy all expressions. This would work both for expression using a + // numeric variable defined on the same line as well as expressions with a + // comparison rather than equality constraint. for (const auto &Substitution : Substitutions) { // Substitute and check for failure (e.g. use of undefined variable). Expected Value = Substitution->getResult(); @@ -961,46 +1031,78 @@ RegExToMatch = TmpStr; } + bool AllExpressionsSatisfied; SmallVector MatchInfo; - if (!Regex(RegExToMatch, Regex::Newline).match(Buffer, &MatchInfo)) - return make_error(); + do { + AllExpressionsSatisfied = true; + if (!Regex(RegExToMatch, Regex::Newline).match(Buffer, &MatchInfo)) + return make_error(); - // Successful regex match. - assert(!MatchInfo.empty() && "Didn't get any match"); - StringRef FullMatch = MatchInfo[0]; + // Successful regex match. + assert(!MatchInfo.empty() && "Didn't get any match"); - // 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 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]; - assert(DefinedNumericVariable->getExpression() != nullptr); - FileCheckExpressionFormat Format = - DefinedNumericVariable->getExpression()->getEffectiveFormat(); - Expected Value = - Format.valueFromStringRepr(MatchedValue, SM); - if (!Value) - return Value.takeError(); - if (DefinedNumericVariable->setValue(*Value)) - llvm_unreachable("Numeric variable redefined"); - } + // Check the matched value satisfies the expression constraint and store it + // in the numeric variable being defined if any. + for (const auto &CapturedExpression : CapturedExpressions) { + unsigned CaptureParenGroup = CapturedExpression.CaptureParenGroup; + assert(CaptureParenGroup < MatchInfo.size() && "Internal paren error"); + StringRef MatchedValue = MatchInfo[CaptureParenGroup]; + FileCheckExpression *Expression = CapturedExpression.Expression; + FileCheckNumericVariable *DefinedNumericVariable = + CapturedExpression.DefinedNumericVariable; + FileCheckExpressionFormat Format = Expression->getEffectiveFormat(); + Expected Value = + Format.valueFromStringRepr(MatchedValue, SM); + + // Get numeric value from the matched string according to matching format + // of the expression. Error out if this fails. + if (!Value) + return Value.takeError(); + + // Now verify the constraint of the expression. + if (Expression->verifyConstraint(*Value)) { + AllExpressionsSatisfied = false; + // Try matching again from the line following the one matched. There + // might be other possible matches in the current line but we have no + // way to ask for them to the regular expression engine. However this + // hazard can be alleviated if the user write a CHECK pattern that uses + // expression for all numeric values that the line to match contains. + Buffer = Buffer.substr(Buffer.find_first_of('\n')).substr(1); + break; + } + + // Store verified matched value. This will set variable defined by the + // expression if any. + if (DefinedNumericVariable != nullptr && + DefinedNumericVariable->setValue(*Value, /*Tentative=*/true)) + llvm_unreachable("Value of numeric variable already set"); + } + } while (!AllExpressionsSatisfied && !Buffer.empty()); + + // All constraints satisfied, make variable definitions definitive. + if (AllExpressionsSatisfied) { + for (const auto &CapturedExpression : CapturedExpressions) { + FileCheckNumericVariable *DefinedNumericVariable = + CapturedExpression.DefinedNumericVariable; + if (DefinedNumericVariable != nullptr) + DefinedNumericVariable->commitValue(); + } + // No line with all constraints satisfied found, return failure to match. + } else + return StringRef::npos; // 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; + StringRef FullMatch = MatchInfo[0]; MatchLen = FullMatch.size() - MatchStartSkip; return FullMatch.data() - Buffer.data() + MatchStartSkip; } @@ -1024,7 +1126,9 @@ void FileCheckPattern::printSubstitutions(const SourceMgr &SM, StringRef Buffer, SMRange MatchRange) const { - // Print what we know about substitutions. + // Print what we know about substitutions. This covers both string and + // numeric substitutions as long as they only use variables defined in + // earlier lines. if (!Substitutions.empty()) { for (const auto &Substitution : Substitutions) { SmallString<256> Msg; @@ -1057,7 +1161,8 @@ E.log(OS); }); } else { - // Substitution succeeded. Print substituted value. + // Substitution succeeded and expression satisfied its constraint. + // Print substituted value. OS << "with \""; OS.write_escaped(Substitution->getFromString()) << "\" equal to \""; OS.write_escaped(*MatchedValue) << "\""; @@ -1149,8 +1254,9 @@ FileCheckExpression *FileCheckPatternContext::makeExpression( std::shared_ptr AST, - FileCheckExpressionFormat Format) { - Expressions.push_back(llvm::make_unique(AST, Format)); + FileCheckExpressionFormat Format, bool MatchTimeKnown) { + Expressions.push_back( + llvm::make_unique(AST, Format, MatchTimeKnown)); return Expressions.back().get(); } @@ -2147,7 +2253,7 @@ Errs = joinErrors(std::move(Errs), Value.takeError()); continue; } - (*DefinedNumericVariable)->setValue(*Value); + (*DefinedNumericVariable)->setValue(*Value, /*Tentative=*/false); // Record this variable definition. GlobalNumericVariableTable[(*DefinedNumericVariable)->getName()] = diff --git a/llvm/test/FileCheck/numeric-expression.txt b/llvm/test/FileCheck/numeric-expression.txt --- a/llvm/test/FileCheck/numeric-expression.txt +++ b/llvm/test/FileCheck/numeric-expression.txt @@ -211,6 +211,70 @@ CHECK-NEXT: [[# %u, VAR4]] CHECK-NEXT: [[# %d, VAR4]] +; Numeric expression with explicit and default matching format and implicit +; matching rule using variables defined on same line +USE SAME EXPL FMT IMPL MATCH +11 10 +-11 C +b c +b 12 c +C B +B 10 +; CHECK-LABEL: USE SAME EXPL FMT IMPL MATCH +; CHECK-NEXT: [[#VAR5:]] [[#%u,VAR5-1]] +; CHECK-NEXT: [[#%d,VAR6:]] [[#%X,VAR6+23]] +; CHECK-NEXT: [[#%x,VAR7:]] [[#%x,VAR7+1]] +; CHECK-NEXT: [[#%x,VAR8:]] [[#%u,VAR8+1]] [[#%x,VAR8+1]] +; CHECK-NEXT: [[#%X,VAR9:]] [[#%X,VAR9-1]] +; CHECK-NEXT: [[#%X,VAR10:]] [[#%d,VAR10-1]] + +; Numeric expression with implicit matching format and implicit matching rule +; using variables defined on same line +USE SAME IMPL FMT IMPL MATCH +11 10 +-11 -4 +b c +b 12 c +C B +; CHECK-LABEL: USE SAME IMPL FMT IMPL MATCH +; CHECK-NEXT: [[#VAR5:]] [[#VAR5-1]] +; CHECK-NEXT: [[#%d,VAR6:]] [[#VAR6+7]] +; CHECK-NEXT: [[#%x,VAR7:]] [[#VAR7+1]] +; CHECK-NEXT: [[#%x,VAR8:]] [[#%u,VAR8+1]] [[#VAR8+1]] +; CHECK-NEXT: [[#%X,VAR9:]] [[#VAR9-1]] + +; Numeric expression with implicit matching format and implicit matching rule +; using variables defined on same line satisfying constraint is not the first +; line encountered. +REDEF SAME IMPL FMT IMPL MATCH NOT FIRST +SAME LINE USE IMPL FMT IMPL MATCH NOT FIRST +1 3 +1 2 +; CHECK-LABEL: SAME LINE USE IMPL FMT IMPL MATCH NOT FIRST +; CHECK: [[#N:]] [[#N+1]] + +; Numeric expression with explicit matching format and implicit matching rule +; using variables redefined on same line +REDEF EXPL FMT IMPL MATCH +21 +22 -31 -32 +AC/DC dc db +; CHECK-LABEL: REDEF EXPL FMT IMPL MATCH +; CHECK-NEXT: [[#VAR11:]] +; CHECK-NEXT: [[#%u,VAR11+1]] [[#%d,VAR11:]] [[#%d,VAR11-1]] +; CHECK-NEXT: [[#%X,VAR12:]]/[[#%X,VAR12+48]] [[#%x,VAR12:]] [[#%x,VAR12-1]] + +; Numeric expression with implicit matching format and implicit matching rule +; using variables redefined on same line +REDEF IMPL FMT IMPL MATCH +21 +22 -31 -32 +AC/DC dc db +; CHECK-LABEL: REDEF IMPL FMT IMPL MATCH +; CHECK-NEXT: [[#VAR11:]] +; CHECK-NEXT: [[#VAR11+1]] [[#%d,VAR11:]] [[#VAR11-1]] +; CHECK-NEXT: [[#%X,VAR12:]]/[[#VAR12+0x30]] [[#%x,VAR12:]] [[#VAR12-1]] + ; Numeric variable definition with unsupported matching format RUN: not FileCheck -check-prefixes ERR,INVALID-FMT-SPEC1 -input-file %s %s 2>&1 \ RUN: | FileCheck -check-prefix INVALID-FMT-SPEC-MSG1 %s @@ -387,3 +451,14 @@ EMPTY-NUMDEF-CONSTRAINT-MSG: numeric-expression.txt:[[#@LINE-1]]:44: error: Empty expression should not have a matching constraint EMPTY-NUMDEF-CONSTRAINT-MSG-NEXT: {{E}}MPTY-NUMDEF-CONSTRAINT-NEXT: {{\[\[#VARDEF: ==\]\]}} EMPTY-NUMDEF-CONSTRAINT-MSG-NEXT: {{^ \^$}} + +; Numeric expression with implicit matching format and implicit matching rule +; using variables defined on same line satisfying constraint is not the first +; line encountered. +RUN: not FileCheck -check-prefix SAME-LINE-USE-FAIL -input-file %s %s + +SAME LINE USE IMPL FMT IMPL MATCH FAIL CONSTRAINT +1 3 +1 4 +SAME-LINE-USE-FAIL-LABEL: SAME LINE USE IMPL FMT IMPL MATCH FAIL CONSTRAINT +SAME-LINE-USE-FAIL-NEXT: [[#N:]] [[#N+1]] diff --git a/llvm/test/FileCheck/verbose.txt b/llvm/test/FileCheck/verbose.txt --- a/llvm/test/FileCheck/verbose.txt +++ b/llvm/test/FileCheck/verbose.txt @@ -30,10 +30,10 @@ VV-NEXT: {{^}}^{{$}} NUMVAR=42 -NUMVAR - 1:41 +NUMVAR:42 NUMVAR=12 NUMVAR:12 CHECK: NUMVAR=[[#NUMVAR:]] CHECK-NOT: [[#NUMVAR + 1]] -CHECK-NEXT: NUMVAR - 1:[[#NUMVAR - 1]] +CHECK-NEXT: NUMVAR:[[#NUMVAR]] NUMVAR=[[#NUMVAR:]] NUMVAR:[[#NUMVAR]] V: verbose.txt:[[#@LINE-4]]:8: remark: {{C}}HECK: expected string found in input V-NEXT: {{C}}HECK: {{NUMVAR=[[][[]#NUMVAR:[]][]]$}} @@ -43,20 +43,20 @@ V-NEXT: {{^}}^~~~~~~~~{{$}} V-NEXT: verbose.txt:[[#@LINE-9]]:13: remark: {{C}}HECK-NEXT: expected string found in input -V-NEXT: {{C}}HECK-NEXT: {{NUMVAR - 1:[[][[]#NUMVAR - 1[]][]]$}} -V-NEXT: {{^ \^$}} -V-NEXT: verbose.txt:[[#@LINE-15]]:1: note: found here -V-NEXT: {{^}}NUMVAR - 1:41{{$}} -V-NEXT: {{^}}^~~~~~~~~~~~~{{$}} -V-NEXT: verbose.txt:[[#@LINE-18]]:1: note: with "NUMVAR - 1" equal to "41" -V-NEXT: {{^}}NUMVAR - 1:41{{$}} -V-NEXT: {{^}}^~~~~~~~~~~~~{{$}} +V-NEXT: {{C}}HECK-NEXT: {{NUMVAR:[[][[]#NUMVAR[]][]] NUMVAR=[[][[]#NUMVAR:[]][]] NUMVAR:[[][[]#NUMVAR[]][]]$}} +V-NEXT: {{^ \^$}} +V-NEXT: verbose.txt:[[#VARLIN1:@LINE-15]]:1: note: found here +V-NEXT: {{^}}NUMVAR:42 NUMVAR=12 NUMVAR:12{{$}} +V-NEXT: {{^}}^~~~~~~~~~~~~~~~~~~~~~~~~~~~~{{$}} +V-NEXT: verbose.txt:[[#VARLIN1]]:1: note: with "NUMVAR" equal to "42" +V-NEXT: {{^}}NUMVAR:42 NUMVAR=12 NUMVAR:12{{$}} +V-NEXT: {{^}}^~~~~~~~~~~~~~~~~~~~~~~~~~~~~{{$}} VV-NEXT: verbose.txt:[[#@LINE-20]]:12: remark: {{C}}HECK-NOT: excluded string not found in input VV-NEXT: {{C}}HECK-NOT: {{[[][[]#NUMVAR [+] 1[]][]]$}} VV-NEXT: {{^ \^$}} VV-NEXT: verbose.txt:[[#@LINE-25]]:1: note: scanning from here -VV-NEXT: {{^}}NUMVAR - 1:41{{$}} +VV-NEXT: {{^}}NUMVAR:42 NUMVAR=12 NUMVAR:12{{$}} VV-NEXT: {{^}}^{{$}} before empty diff --git a/llvm/unittests/Support/FileCheckTest.cpp b/llvm/unittests/Support/FileCheckTest.cpp --- a/llvm/unittests/Support/FileCheckTest.cpp +++ b/llvm/unittests/Support/FileCheckTest.cpp @@ -57,7 +57,7 @@ // Undefined variable: isMatchTimeKnown returns false, eval and clearValue // fail, error returned by eval holds the name of the undefined variable and // setValue works. - auto NumVarExpr = FileCheckExpression(nullptr, FormatUnsigned); + auto NumVarExpr = FileCheckExpression(nullptr, FormatUnsigned, false); auto FooVar = std::make_shared(1, "FOO", &NumVarExpr); EXPECT_EQ("FOO", FooVar->getName()); @@ -67,7 +67,7 @@ EXPECT_FALSE(expectUndefError("FOO", Value.takeError())); EXPECT_TRUE(FooVar->clearValue()); FileCheckExpressionValue LifeAnswer = FileCheckExpressionValue((uint64_t)42); - EXPECT_FALSE(FooVar->setValue(LifeAnswer)); + EXPECT_FALSE(FooVar->setValue(LifeAnswer, false)); // Defined variable: isMatchTimeKnown returns true, eval returns value set // and setValue fails. @@ -76,7 +76,7 @@ EXPECT_TRUE(static_cast(Value)); EXPECT_EQ(42U, Value->getUnsignedValue()); FileCheckExpressionValue FourThree = FileCheckExpressionValue((uint64_t)43); - EXPECT_TRUE(FooVar->setValue(FourThree)); + EXPECT_TRUE(FooVar->setValue(FourThree, false)); Value = FooVar->eval(); EXPECT_TRUE(static_cast(Value)); EXPECT_EQ(42U, Value->getUnsignedValue()); @@ -85,14 +85,14 @@ // true, eval returns value of expression, and setValue succeeds. auto One = std::make_shared((uint64_t)1); auto Binop = std::make_shared(doAdd, FooVar, One); - auto FoobarExpr = FileCheckExpression(Binop, FormatUnsigned); + auto FoobarExpr = FileCheckExpression(Binop, FormatUnsigned, true); FileCheckNumericVariable FoobarVar = FileCheckNumericVariable(2, "FOOBAR", &FoobarExpr); EXPECT_TRUE(FoobarVar.isMatchTimeKnown()); Value = FoobarVar.eval(); EXPECT_TRUE(static_cast(Value)); EXPECT_EQ(43U, Value->getUnsignedValue()); - EXPECT_FALSE(FoobarVar.setValue(FourThree)); + EXPECT_FALSE(FoobarVar.setValue(FourThree, false)); Value = FoobarVar.eval(); EXPECT_TRUE(static_cast(Value)); EXPECT_EQ(43U, Value->getUnsignedValue()); @@ -112,15 +112,15 @@ TEST_F(FileCheckTest, Binop) { FileCheckExpression DefExpression = - FileCheckExpression(nullptr, FormatUnsigned); + FileCheckExpression(nullptr, FormatUnsigned, false); auto FooVar = std::make_shared(1, "FOO", &DefExpression); FileCheckExpressionValue LifeAnswer = FileCheckExpressionValue((uint64_t)42); - FooVar->setValue(LifeAnswer); + FooVar->setValue(LifeAnswer, false); auto BarVar = std::make_shared(2, "BAR", &DefExpression); FileCheckExpressionValue OneEight = FileCheckExpressionValue((uint64_t)18); - BarVar->setValue(OneEight); + BarVar->setValue(OneEight, false); FileCheckASTBinop Binop = FileCheckASTBinop(doAdd, FooVar, BarVar); // Defined variable: eval returns right value. @@ -323,10 +323,6 @@ EXPECT_FALSE(Tester.parseSubstExpect("@LINE")); EXPECT_FALSE(Tester.parseSubstExpect("FOO")); - // Use variable defined on same line. - EXPECT_FALSE(Tester.parsePatternExpect("[[#LINE1VAR:]]")); - EXPECT_TRUE(Tester.parseSubstExpect("LINE1VAR")); - // Unsupported operator. EXPECT_TRUE(Tester.parseSubstExpect("@LINE/2")); @@ -414,18 +410,19 @@ // Substitutions of defined pseudo and non-pseudo numeric variables return // the right value. - auto DefExpression = FileCheckExpression(nullptr, FormatUnsigned); + auto DefExpression = FileCheckExpression(nullptr, FormatUnsigned, false); auto LineVar = std::make_shared(1, "@LINE", &DefExpression); auto NVar = std::make_shared(1, "N", &DefExpression); FileCheckExpressionValue LifeAnswer = FileCheckExpressionValue((uint64_t)42); - LineVar->setValue(LifeAnswer); + LineVar->setValue(LifeAnswer, false); FileCheckExpressionValue Ten = FileCheckExpressionValue((uint64_t)10); - NVar->setValue(Ten); + NVar->setValue(Ten, false); FileCheckExpression ExpressionLine = - FileCheckExpression(LineVar, FormatUnsigned); - FileCheckExpression ExpressionN = FileCheckExpression(NVar, FormatUnsigned); + FileCheckExpression(LineVar, FormatUnsigned, true); + FileCheckExpression ExpressionN = + FileCheckExpression(NVar, FormatUnsigned, true); FileCheckNumericSubstitution SubstitutionLine = FileCheckNumericSubstitution(&Context, "@LINE", &ExpressionLine, 12); FileCheckNumericSubstitution SubstitutionN =