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 @@ -659,9 +659,15 @@ The ``--enable-var-scope`` option has the same effect on numeric variables as on string variables. -Important note: In its current implementation, a numeric expression cannot use -a numeric variable with a non-empty numeric expression constraint defined on -the same line. +Important note: In its current implementation, a numeric 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 numeric 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 @@ -106,23 +106,29 @@ /// which case it is stored in UnsignedValue). bool Signed; - /// Whether this holds an actual value. Examples of where this would be - /// false: - /// - underflow or overflow of one of the binary operation in the expression; - /// - unset variable's value. + /// Whether this holds an actual value. Examples of where this would be false + /// include: + /// - underflow or overflow of one of the binary operation in the expression + /// - value of undefined variable bool Valid; + /// 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 an invalid value. FileCheckNumExprVal() : Valid(false) {} /// Constructor for a signed value. explicit FileCheckNumExprVal(int64_t Val) - : SignedValue(Val), Signed(true), Valid(true) {} + : SignedValue(Val), Signed(true), Valid(true), Tentative(false) {} /// Constructor for an unsigned value. explicit FileCheckNumExprVal(uint64_t Val) - : UnsignedValue(Val), Signed(false), Valid(true) {} + : UnsignedValue(Val), Signed(false), Valid(true), Tentative(false) {} /// Define equality to be true only if both values are valid and they have /// the same signedness and corresponding value. Tentative bit is ignored to @@ -137,6 +143,12 @@ bool isValid() const { return Valid; } + /// 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 isTentative() const { return Tentative; } + /// Return the signed value. Must only be called if value is signed in the /// first place. int64_t getSignedValue() const { @@ -151,6 +163,9 @@ return UnsignedValue; } + /// Change whether this value is tentative or not. + bool toggleTentative() { return Tentative = !Tentative; } + /// Convert value to a signed value, or mark value invalid if not possible /// (original value was not within range for a signed integer). void convertSigned(); @@ -218,22 +233,37 @@ /// value when matching it. FileCheckNumExprFmt Fmt; + /// 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 a numeric expression whose equality constraint is - /// represented by \p AST and matching format is \p Fmt. If matching format - /// is unset (ie. no explicit or implicit matching format), set it to default - /// one (unsigned decimal integer). + /// represented by \p AST, whose matching format is \p Fmt 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). FileCheckNumExpr(std::shared_ptr AST, - FileCheckNumExprFmt Fmt); + FileCheckNumExprFmt Fmt, bool MatchTimeKnown); /// \returns pointer to AST of the numeric expression. Pointer is guaranteed /// to be valid as long as this object is. FileCheckNumExprAST *getAST() const { return AST.get(); } + /// \returns 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 isMatchTimeKnown() const { return MatchTimeKnown; } + /// \returns effective format of this numeric expression, ie (i) its explicit /// format if any, (ii) its implicit format if any or (iii) the default /// format. FileCheckNumExprFmt getEffectiveFmt() { return Fmt; } + + /// Verify that the matched value in \p MatchedValue satisfies the constraint + /// expressed by this expression. Return true if constraint is not satisfied. + bool verifyConstraint(FileCheckNumExprVal MatchedValue) const; }; /// Class representing a numeric variable with a given value in the AST of a @@ -293,9 +323,14 @@ /// Appends numeric variable's name to UndefVarNames if undefined. void appendUndefVarNames(std::vector &UndefVarNames) const; - /// Sets value of this numeric variable if not defined. \returns whether the - /// variable was already defined. - bool setValue(FileCheckNumExprVal Value); + /// Sets value of this numeric variable if not defined. Value is set as a + /// tentative value if \p Tentative is true. \returns whether variable was + /// already definitely (i.e. not tentatively) defined. + bool setValue(FileCheckNumExprVal Value, bool Tentative); + + /// Marks value as definitive (i.e. not tentative). Return whether value was + /// already definitive. + bool commitValue(); /// Clears value of this numeric variable. \returns whether the variable was /// already undefined. @@ -528,7 +563,7 @@ /// Makes a new numeric expression instance and registers it for destruction /// when the context is destroyed. FileCheckNumExpr *makeNumExpr(std::shared_ptr AST, - FileCheckNumExprFmt Fmt); + FileCheckNumExprFmt Fmt, bool MatchTimeKnown); /// Makes a new string substitution and registers it for destruction when the /// context is destroyed. @@ -572,11 +607,18 @@ /// 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 a numeric expression whose constraint needs to be + /// verified after matching (either a variable definition with empty numeric + /// expression or a numeric expression using directly or indirectly such a + /// variable. It holds a pointer to the class instance holding the numeric + /// 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 FileCheckNumExprMatch { + /// Pointer to class instance representing the numeric expression to + /// verify. + FileCheckNumExpr *NumExpr; + /// Pointer to class instance holding the value and matching format of the /// numeric variable being defined. FileCheckNumericVariable *DefinedNumericVariable; @@ -586,11 +628,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 + /// FileCheckNumExpr class instance of the numeric expression to verify and + /// pointer to the FileCheckNumericVariable class instance of the variable + /// defined from this numeric expression, if any, for all numeric expressions + /// that needs to be verified after matching. + std::vector CapturedNumericExpressions; /// Pointer to a class instance holding the global state shared by all /// patterns: @@ -663,6 +706,10 @@ size_t match(StringRef Buffer, size_t &MatchLen, 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, @@ -716,23 +763,26 @@ /// \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 numeric expression or nullptr if parsing fails in which case - /// errors are reported on \p SM. + /// errors are reported on \p SM. Also sets \p MatchTimeKnown to whether the + /// operand's value is known at match time. static std::shared_ptr parseNumericOperand(StringRef &Expr, enum AllowedOperand AO, - size_t LineNumber, FileCheckPatternContext *Context, - const SourceMgr &SM); + 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 Legacy indicates whether we are parsing a - /// legacy numeric 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 numeric - /// expression, or nullptr if parsing fails, in which case errors are - /// reported on \p SM. + /// indicates the command-line. If \p Legacy is true, parses \p Expr as a + /// legacy numeric 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 + /// numeric expression, or nullptr if parsing fails, in which case errors are + /// reported on \p SM. Also sets \p MatchTimeKnown to whether the binary + /// operation can be evaluated at match time. static std::shared_ptr parseBinop(StringRef &Expr, std::shared_ptr LeftOp, - bool Legacy, size_t LineNumber, FileCheckPatternContext *Context, - const SourceMgr &SM); + bool Legacy, 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 @@ -193,8 +193,8 @@ } FileCheckNumExpr::FileCheckNumExpr(std::shared_ptr AST, - FileCheckNumExprFmt Fmt) - : AST(AST) { + FileCheckNumExprFmt Fmt, bool MatchTimeKnown) + : AST(AST), MatchTimeKnown(MatchTimeKnown) { if (Fmt.Valid) this->Fmt = Fmt; else @@ -215,7 +215,7 @@ if (Value.isValid()) return true; - return NumExpr != nullptr && NumExpr->getAST() != nullptr; + return NumExpr != nullptr && NumExpr->isMatchTimeKnown(); } FileCheckNumExprFmt FileCheckNumericVariable::getImplicitFmt() const { @@ -231,11 +231,16 @@ UndefVarNames.emplace_back(Name); } -bool FileCheckNumericVariable::setValue(FileCheckNumExprVal NewValue) { - if (Value.isValid()) +bool FileCheckNumericVariable::setValue(FileCheckNumExprVal NewValue, + bool Tentative) { + if (Value.isValid() && !Value.isTentative()) return true; + assert(NumExpr != nullptr && "Setting value of parse time variable"); + assert(!NewValue.isTentative() && "Setting value from tentative value"); assert(NewValue.isValid() && "Setting invalid value"); + if (Tentative) + NewValue.toggleTentative(); if (NumExpr != nullptr && NumExpr->getAST() != nullptr) { FileCheckNumExprVal EvaluatedValue = NumExpr->getAST()->eval(); if (EvaluatedValue != NewValue) @@ -245,6 +250,15 @@ return false; } +bool FileCheckNumericVariable::commitValue() { + // Value is not a valid tentative value. + if (!Value.isValid() || !Value.isTentative()) + return true; + + Value.toggleTentative(); + return false; +} + bool FileCheckNumericVariable::clearValue() { if (!Value.isValid()) return true; @@ -266,7 +280,29 @@ if (!LeftValue.isValid() || !RightValue.isValid()) return FileCheckNumExprVal(); - return EvalBinop(LeftValue, RightValue); + FileCheckNumExprVal ResultValue = EvalBinop(LeftValue, RightValue); + + // 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.isValid() || !ResultValue.isTentative()) && + "Binary operation returned tentative value"); + if (ResultValue.isValid() && + (LeftValue.isTentative() || RightValue.isTentative())) + ResultValue.toggleTentative(); + return ResultValue; +} + +/// Verify that the matched value in \p MatchedValue satisfies the constraint +/// expressed by this expression. Return true if constraint is not satisfied. +bool FileCheckNumExpr::verifyConstraint( + FileCheckNumExprVal MatchedValue) const { + // Nothing to verify: numeric variable definition with empty numeric + // expression. + if (AST == nullptr) + return false; + + // Will fail if evaluated value or MatchedValue are invalid. + return AST->eval() != MatchedValue; } FileCheckNumExprFmt FileCheckASTBinop::getImplicitFmt() const { @@ -429,30 +465,23 @@ return nullptr; } - std::shared_ptr NumericVariable = - VarTableIter->second; - if (!IsPseudo && NumericVariable->getDefLineNumber() == LineNumber && - !NumericVariable->isMatchTimeKnown()) { - SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, - "numeric variable '" + Name + - "' defined from input on the same line as used"); - return nullptr; - } - - return NumericVariable; + return VarTableIter->second; } std::shared_ptr FileCheckPattern::parseNumericOperand( - StringRef &Expr, enum AllowedOperand AO, size_t LineNumber, - FileCheckPatternContext *Context, const SourceMgr &SM) { + StringRef &Expr, enum AllowedOperand AO, bool &MatchTimeKnown, + size_t LineNumber, FileCheckPatternContext *Context, const SourceMgr &SM) { // Try to parse as a numeric variable use. if (AO == LegacyVar || AO == Any) { // Error reporting done in ParseNumericVariableUse(). std::shared_ptr NumericVariable = parseNumericVariableUse(Expr, LineNumber, Context, SM); - if (NumericVariable) + if (NumericVariable) { + MatchTimeKnown = NumericVariable->getDefLineNumber() < LineNumber || + NumericVariable->isMatchTimeKnown(); return NumericVariable; + } } // Otherwise, parse it as a literal. @@ -464,10 +493,12 @@ StringRef SaveExpr = Expr; // Accept both signed and unsigned literal. if (AO != LegacyLiteral && !Expr.consumeInteger(Radix, SignedLiteralValue)) { + MatchTimeKnown = true; return std::make_shared(SignedLiteralValue); } else { Expr = SaveExpr; if (!Expr.consumeInteger(Radix, UnsignedLiteralValue)) { + MatchTimeKnown = true; return std::make_shared(UnsignedLiteralValue); } else return nullptr; @@ -476,7 +507,8 @@ std::shared_ptr FileCheckPattern::parseBinop( StringRef &Expr, std::shared_ptr LeftOp, bool Legacy, - size_t LineNumber, FileCheckPatternContext *Context, const SourceMgr &SM) { + bool &MatchTimeKnown, size_t LineNumber, FileCheckPatternContext *Context, + const SourceMgr &SM) { Expr = Expr.ltrim(SpaceChars); if (Expr.empty()) return LeftOp; @@ -509,14 +541,16 @@ } // Second operand in legacy numeric expression is a literal. enum AllowedOperand AO = Legacy ? LegacyLiteral : Any; + bool OprMatchTimeKnown; std::shared_ptr RightOp = - parseNumericOperand(Expr, AO, LineNumber, Context, SM); + parseNumericOperand(Expr, AO, OprMatchTimeKnown, LineNumber, Context, SM); if (RightOp == nullptr) { SM.PrintMessage(SMLoc::getFromPointer(Expr.data()), SourceMgr::DK_Error, "invalid operand format '" + Expr + "'"); return nullptr; } Expr = Expr.ltrim(SpaceChars); + MatchTimeKnown = MatchTimeKnown && OprMatchTimeKnown; return std::make_shared(EvalBinop, LeftOp, RightOp); } @@ -587,6 +621,7 @@ // Parse the numeric expression itself. Expr = Expr.ltrim(SpaceChars); StringRef UseExpr = Expr; + bool MatchTimeKnown = false; if (Expr.empty()) { if (Expr.size() != ConstraintExpr.size()) { SM.PrintMessage( @@ -597,10 +632,11 @@ } else { // First operand in legacy numeric expression is the @LINE pseudo variable. enum AllowedOperand AO = Legacy ? LegacyVar : Any; - NumExprAST = parseNumericOperand(Expr, AO, LineNumber, Context, SM); + NumExprAST = + parseNumericOperand(Expr, AO, MatchTimeKnown, LineNumber, Context, SM); while (NumExprAST != nullptr && !Expr.empty()) { - NumExprAST = - parseBinop(Expr, NumExprAST, Legacy, LineNumber, Context, SM); + NumExprAST = parseBinop(Expr, NumExprAST, Legacy, MatchTimeKnown, + LineNumber, Context, SM); // Legacy numeric expressions only allow 2 operands. if (Legacy && !Expr.empty()) { SM.PrintMessage(SMLoc::getFromPointer(Expr.data()), SourceMgr::DK_Error, @@ -630,7 +666,7 @@ } else Fmt = ImplicitFmt; - NumExpr = Context->makeNumExpr(NumExprAST, Fmt); + NumExpr = Context->makeNumExpr(NumExprAST, Fmt, MatchTimeKnown); // Parse the numeric variable definition. if (DefEnd != StringRef::npos) { @@ -775,6 +811,7 @@ bool IsDefinition = false; bool SubstNeeded = false; + bool CaptureNeeded = false; bool Legacy = false; StringRef DefName; StringRef SubstStr; @@ -801,7 +838,7 @@ return true; } - IsDefinition = (VarEndIdx != StringRef::npos); + CaptureNeeded = IsDefinition = (VarEndIdx != StringRef::npos); SubstNeeded = !IsDefinition; if (IsDefinition) { if ((IsPseudo || !MatchStr.consume_front(":"))) { @@ -840,9 +877,10 @@ LineNumber, Context, SM)) return true; IsDefinition = DefinedNumericVariable != nullptr; - SubstNeeded = NumExpr->getAST() != nullptr; if (IsDefinition) DefName = DefinedNumericVariable->getName(); + SubstNeeded = NumExpr->isMatchTimeKnown(); + CaptureNeeded = IsDefinition || !NumExpr->isMatchTimeKnown(); if (SubstNeeded) SubstStr = MatchStr; else { @@ -851,15 +889,16 @@ } } - // Handle variable definition: [[:(...)]] and [[#(...):(...)]]. - if (IsDefinition) { + // Handle variable definition: [[:(...)]] and [[#(...):(...)]] + // and numeric expression needing verifying. + if (CaptureNeeded) { RegExStr += '('; ++SubstInsertIdx; if (IsNumBlock) { - struct FileCheckNumExprMatch NumExprDef = {DefinedNumericVariable, - CurParen}; - NumericVariableDefs[DefName] = NumExprDef; + struct FileCheckNumExprMatch NumExprDef = { + NumExpr, DefinedNumericVariable, CurParen}; + CapturedNumericExpressions.emplace_back(NumExprDef); // 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 @@ -883,7 +922,7 @@ if (!MatchRegexp.empty() && AddRegExToRegEx(MatchRegexp, CurParen, SM)) return true; - if (IsDefinition) + if (CaptureNeeded) RegExStr += ')'; // Handle substitutions: [[foo]] and [[#]]. @@ -971,13 +1010,33 @@ // actual value. StringRef RegExToMatch = RegExStr; std::string TmpStr; + if (!Substitutions.empty()) { TmpStr = RegExStr; size_t InsertOffset = 0; // Substitute all string variables and numeric expressions whose values are // only now known. Use of string variables defined on the same line are - // handled by back-references. + // handled by back-references. For numeric expressions using variable + // with empty numeric 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 numeric expressions using + // 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 numeric expressions. This would work both for numeric + // expression using a numeric variable defined on the same line as well as + // numeric expressions with a comparison rather than equality constraint. for (const auto &Substitution : Substitutions) { // Substitute and check for failure (e.g. use of undefined variable). Optional Value = Substitution->getResult(); @@ -999,47 +1058,83 @@ RegExToMatch = TmpStr; } + bool AllNumExprSatisfied; SmallVector MatchInfo; - if (!Regex(RegExToMatch, Regex::Newline).match(Buffer, &MatchInfo)) - return StringRef::npos; + do { + AllNumExprSatisfied = true; + if (!Regex(RegExToMatch, Regex::Newline).match(Buffer, &MatchInfo)) + return StringRef::npos; - // 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]; + } + + // Check the matched value satisfies the numeric expression constraint and + // store it in the numeric variable being defined if any. + for (const auto &CapturedNumericExpression : CapturedNumericExpressions) { + unsigned CaptureParenGroup = CapturedNumericExpression.CaptureParenGroup; + assert(CaptureParenGroup < MatchInfo.size() && "Internal paren error"); + StringRef MatchedStr = MatchInfo[CaptureParenGroup]; + FileCheckNumExpr *NumExpr = CapturedNumericExpression.NumExpr; + FileCheckNumericVariable *DefinedNumericVariable = + CapturedNumericExpression.DefinedNumericVariable; + + StringRef MatchedValue = MatchInfo[CaptureParenGroup]; + FileCheckNumExprFmt Fmt = NumExpr->getEffectiveFmt(); + FileCheckNumExprVal Value = Fmt.valueFromStringRepr(MatchedValue); + + // Get numeric value from the matched string according to matching format + // of the numeric expression. Error out if this fails. + if (!Value.isValid()) { + SM.PrintMessage(SMLoc::getFromPointer(MatchedStr.data()), + SourceMgr::DK_Error, + "Unable to represent numeric value " + MatchedStr); + return StringRef::npos; + } - // If this defines any numeric variables, remember their values. - for (const auto &NumericVariableDef : NumericVariableDefs) { - const FileCheckNumExprMatch &NumericVariableMatch = - NumericVariableDef.getValue(); - unsigned CaptureParenGroup = NumericVariableMatch.CaptureParenGroup; - assert(CaptureParenGroup < MatchInfo.size() && "Internal paren error"); - FileCheckNumericVariable *DefinedNumericVariable = - NumericVariableMatch.DefinedNumericVariable; - - StringRef MatchedValue = MatchInfo[CaptureParenGroup]; - assert(DefinedNumericVariable->getNumExpr() != nullptr); - FileCheckNumExprFmt Fmt = - DefinedNumericVariable->getNumExpr()->getEffectiveFmt(); - FileCheckNumExprVal Value = Fmt.valueFromStringRepr(MatchedValue); - if (!Value.isValid()) { - SM.PrintMessage(SMLoc::getFromPointer(MatchedValue.data()), - SourceMgr::DK_Error, "Unable to represent numeric value"); + // Now verify the constraint of the numeric expression. + if (NumExpr->verifyConstraint(Value)) { + AllNumExprSatisfied = 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 + // numeric expression for all numeric values 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, true /*Tentative*/)) + assert(false && "Value of numeric variable already set"); } - if (DefinedNumericVariable->setValue(Value)) - assert(false && "Numeric variable redefined"); - } + } while (!AllNumExprSatisfied && !Buffer.empty()); + + // All constraints satisfied, make variable definitions definitive. + if (AllNumExprSatisfied) { + for (const auto &CapturedNumericExpression : CapturedNumericExpressions) { + FileCheckNumericVariable *DefinedNumericVariable = + CapturedNumericExpression.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; } @@ -1063,7 +1158,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; @@ -1082,7 +1179,8 @@ OS.write_escaped(UndefVarName) << "\""; } } else { - // Substitution succeeded. Print substituted value. + // Substitution succeeded and numeric expression satisfied its + // constraint. Print substituted value. OS << "with \""; OS.write_escaped(Substitution->getFromString()) << "\" equal to \""; OS.write_escaped(*MatchedValue) << "\""; @@ -1174,8 +1272,10 @@ FileCheckNumExpr * FileCheckPatternContext::makeNumExpr(std::shared_ptr AST, - FileCheckNumExprFmt Fmt) { - NumExprs.push_back(llvm::make_unique(AST, Fmt)); + FileCheckNumExprFmt Fmt, + bool MatchTimeKnown) { + NumExprs.push_back( + llvm::make_unique(AST, Fmt, MatchTimeKnown)); return NumExprs.back().get(); } @@ -2147,7 +2247,7 @@ ErrorFound = true; continue; } - DefinedNumericVariable->setValue(Value); + DefinedNumericVariable->setValue(Value, false /*Tentative*/); // 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 @@ -375,3 +439,14 @@ EMPTY-NUMDEF-CONSTRAINT-MSG: numeric-expression.txt:[[#@LINE-1]]:44: error: Empty numeric 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 @@ -46,7 +46,7 @@ TEST_F(FileCheckTest, NumericVariable) { // Undefined variable: isMatchTimeKnown returns false, eval and clearValue // fail, appendUndefVarNames returns the variable and setValue works. - auto NumVarExpr = FileCheckNumExpr(nullptr, FmtUnsigned); + auto NumVarExpr = FileCheckNumExpr(nullptr, FmtUnsigned, false); auto FooVar = std::make_shared(1, "FOO", &NumVarExpr); EXPECT_EQ("FOO", FooVar->getName()); @@ -60,7 +60,7 @@ EXPECT_EQ(1U, UndefVarNames.size()); EXPECT_EQ("FOO", UndefVarNames[0]); FileCheckNumExprVal LifeAnswer = FileCheckNumExprVal((uint64_t)42); - EXPECT_FALSE(FooVar->setValue(LifeAnswer)); + EXPECT_FALSE(FooVar->setValue(LifeAnswer, false)); // Defined variable: isMatchTimeKnown returns true, eval returns value set, // setValue fails and appendUndefVarNames is a nop. @@ -69,7 +69,7 @@ EXPECT_TRUE(Value.isValid()); EXPECT_EQ(42U, Value.getUnsignedValue()); FileCheckNumExprVal FourThree = FileCheckNumExprVal((uint64_t)43); - EXPECT_TRUE(FooVar->setValue(FourThree)); + EXPECT_TRUE(FooVar->setValue(FourThree, false)); Value = FooVar->eval(); EXPECT_TRUE(Value.isValid()); EXPECT_EQ(42U, Value.getUnsignedValue()); @@ -82,14 +82,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 = FileCheckNumExpr(Binop, FmtUnsigned); + auto FoobarExpr = FileCheckNumExpr(Binop, FmtUnsigned, true); // FileCheckNumericVariable FoobarVar = FileCheckNumericVariable(2, "FOOBAR", &FoobarExpr); EXPECT_TRUE(FoobarVar.isMatchTimeKnown()); Value = FoobarVar.eval(); EXPECT_TRUE(Value.isValid()); EXPECT_EQ(43U, Value.getUnsignedValue()); - EXPECT_FALSE(FoobarVar.setValue(FourThree)); + EXPECT_FALSE(FoobarVar.setValue(FourThree, false)); Value = FoobarVar.eval(); EXPECT_TRUE(Value.isValid()); EXPECT_EQ(43U, Value.getUnsignedValue()); @@ -110,15 +110,15 @@ } TEST_F(FileCheckTest, Binop) { - auto DefNumExpr = FileCheckNumExpr(nullptr, FmtUnsigned); + auto DefNumExpr = FileCheckNumExpr(nullptr, FmtUnsigned, false); auto FooVar = std::make_shared(1, "FOO", &DefNumExpr); FileCheckNumExprVal LifeAnswer = FileCheckNumExprVal((uint64_t)42); - FooVar->setValue(LifeAnswer); + FooVar->setValue(LifeAnswer, false); auto BarVar = std::make_shared(2, "BAR", &DefNumExpr); FileCheckNumExprVal OneEight = FileCheckNumExprVal((uint64_t)18); - BarVar->setValue(OneEight); + BarVar->setValue(OneEight, false); FileCheckASTBinop Binop = FileCheckASTBinop(doAdd, FooVar, BarVar); // Defined variable: eval returns right value, no undefined variable @@ -319,10 +319,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")); @@ -413,16 +409,16 @@ // Substitution of defined pseudo and non-pseudo numeric variable returns the // right value, getUndefVarNames does not return any variable. - auto DefNumExpr = FileCheckNumExpr(nullptr, FmtUnsigned); + auto DefNumExpr = FileCheckNumExpr(nullptr, FmtUnsigned, false); auto LineVar = std::make_shared(1, "@LINE", &DefNumExpr); auto NVar = std::make_shared(1, "N", &DefNumExpr); FileCheckNumExprVal LifeAnswer = FileCheckNumExprVal((uint64_t)42); - LineVar->setValue(LifeAnswer); + LineVar->setValue(LifeAnswer, false); FileCheckNumExprVal Ten = FileCheckNumExprVal((uint64_t)10); - NVar->setValue(Ten); - FileCheckNumExpr NumExprLine = FileCheckNumExpr(LineVar, FmtUnsigned); - FileCheckNumExpr NumExprN = FileCheckNumExpr(NVar, FmtUnsigned); + NVar->setValue(Ten, false); + FileCheckNumExpr NumExprLine = FileCheckNumExpr(LineVar, FmtUnsigned, true); + FileCheckNumExpr NumExprN = FileCheckNumExpr(NVar, FmtUnsigned, true); FileCheckNumericSubstitution SubstitutionLine = FileCheckNumericSubstitution(&Context, "@LINE", &NumExprLine, 12); FileCheckNumericSubstitution SubstitutionN = @@ -499,7 +495,7 @@ std::make_shared("@LINE", LifeAnswer); auto Zero = std::make_shared((uint64_t)0); auto Binop = std::make_shared(doAdd, LineVar, Zero); - FileCheckNumExpr NumExpr = FileCheckNumExpr(Binop, FmtUnsigned); + FileCheckNumExpr NumExpr = FileCheckNumExpr(Binop, FmtUnsigned, true); FileCheckNumericSubstitution NumericSubstitution = FileCheckNumericSubstitution(&Context, "@LINE", &NumExpr, 12); UndefVarNames.clear();