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 @@ -651,9 +651,15 @@ The ``--enable-var-scope`` option has the same effect on numeric variables as on pattern 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 @@ -107,23 +107,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 @@ -138,6 +144,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 { @@ -152,6 +164,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(); @@ -219,22 +234,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 @@ -294,9 +324,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. @@ -450,16 +485,23 @@ struct FileCheckDiag; -/// Structure representing the definition of a numeric variable in a pattern. -/// It holds the parenthesized capture number and the pointer to the class -/// instance holding the value and matching format of the numeric variable -/// being defined. +/// 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 the parenthesized capture number, a pointer to the class +/// instance holding the numeric expression to verify and in case of a numeric +/// variable definition a pointer to the class instance holding the value and +/// matching format of the numeric variable being defined. 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; - /// Parenthesized capture number for this numeric variable definition. + /// Parenthesized capture number for the numeric expression that needs + /// verifying. unsigned CaptureParen; }; @@ -515,7 +557,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); }; class FileCheckPattern { @@ -550,10 +592,12 @@ /// iterating over values. std::map VariableDefs; - /// Holds the capture parentheses number and pointer to corresponding - /// FileCheckNumericVariable class instance of all numeric variable - /// definitions. Used to set the matched value of all those variables. - std::map NumericVariableDefs; + /// Holds the capture parentheses number, pointer to corresponding + /// FileCheckNumExpr class instance of the numeric expression to verify and + /// pointer to FileCheckNumericVariable class instance of 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: @@ -625,7 +669,11 @@ /// match defines new numeric values. size_t match(StringRef Buffer, size_t &MatchLen, const SourceMgr &SM) const; /// Prints the value of successful substitutions or the name of the undefined - /// pattern or numeric variable preventing such a successful substitution. + /// pattern or numeric variables preventing such 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, @@ -666,18 +714,21 @@ /// and numeric variables, depending on the value of \p AllowedOperandFlag. /// \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. + /// on \p SM. Also return in MatchTimeKnown if the operand's value is known + /// at match time. std::shared_ptr parseNumericOperand(StringRef &Expr, enum AllowedOperand AO, - const SourceMgr &SM) const; - /// Parses \p Expr for a binary operation. The left operand of this binary - /// operation is given in \p LeftOp and \p Legacy indicates whether we are - /// parsing a legacy numeric expression. \returns the class representing this - /// binary operation in the AST of the numeric expression or nullptr if - /// parsing fails in which case errors are reported on \p SM. + bool &MatchTimeKnown, const SourceMgr &SM) const; + /// Parses \p Expr for a binary operation, as a legacy numeric expression if + /// \p Legacy is true. 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. \returns the class representing this 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 to whether + /// the binary operation can be evaluated at match time. std::shared_ptr - parseFileCheckBinop(StringRef &Expr, - std::shared_ptr LeftOp, bool Legacy, + parseFileCheckBinop(StringRef &Expr, std::shared_ptr Opl, + bool Legacy, bool &MatchTimeKnown, const SourceMgr &SM) const; }; diff --git a/llvm/lib/Support/FileCheck.cpp b/llvm/lib/Support/FileCheck.cpp --- a/llvm/lib/Support/FileCheck.cpp +++ b/llvm/lib/Support/FileCheck.cpp @@ -195,8 +195,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 @@ -217,7 +217,7 @@ if (Value.isValid()) return true; - return NumExpr != nullptr && NumExpr->getAST() != nullptr; + return NumExpr != nullptr && NumExpr->isMatchTimeKnown(); } FileCheckNumExprFmt FileCheckNumericVariable::getImplicitFmt() const { @@ -233,14 +233,29 @@ 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(); Value = NewValue; 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; @@ -262,7 +277,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 { @@ -418,20 +455,12 @@ return true; } - FileCheckNumericVariable *NumericVariable = VarTableIter->second.get(); - if (!IsPseudo && NumericVariable->getDefLineNumber() == LineNumber && - !NumericVariable->isMatchTimeKnown()) { - SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, - "numeric variable '" + NumericVariable->getName() + - "' defined on the same line"); - return true; - } - return false; } std::shared_ptr FileCheckPattern::parseNumericOperand(StringRef &Expr, enum AllowedOperand AO, + bool &MatchTimeKnown, const SourceMgr &SM) const { // Try to parse as a numeric variable use. @@ -442,6 +471,8 @@ true /*AcceptFail*/, SM)) { std::shared_ptr NumericVariable = Context->GlobalNumericVariableTable.find(Name)->second; + MatchTimeKnown = NumericVariable->getDefLineNumber() < LineNumber || + NumericVariable->isMatchTimeKnown(); return NumericVariable; } } @@ -455,10 +486,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; @@ -467,7 +500,7 @@ std::shared_ptr FileCheckPattern::parseFileCheckBinop( StringRef &Expr, std::shared_ptr LeftOp, bool Legacy, - const SourceMgr &SM) const { + bool &MatchTimeKnown, const SourceMgr &SM) const { Expr = Expr.ltrim(SpaceChars); if (Expr.empty()) return LeftOp; @@ -500,14 +533,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, SM); + parseNumericOperand(Expr, AO, OprMatchTimeKnown, 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); } @@ -577,6 +612,7 @@ // Parse numeric expression itself. Expr = Expr.ltrim(SpaceChars); StringRef UseExpr = Expr; + bool MatchTimeKnown = false; if (Expr.empty()) { if (Expr.size() != ConstraintExpr.size()) { SM.PrintMessage( @@ -587,9 +623,10 @@ } else { // First operand in legacy numeric expression is the @LINE pseudo variable. enum AllowedOperand AO = Legacy ? LegacyVar : Any; - NumExprAST = parseNumericOperand(Expr, AO, SM); + NumExprAST = parseNumericOperand(Expr, AO, MatchTimeKnown, SM); while (NumExprAST != nullptr && !Expr.empty()) { - NumExprAST = parseFileCheckBinop(Expr, NumExprAST, Legacy, SM); + NumExprAST = + parseFileCheckBinop(Expr, NumExprAST, Legacy, MatchTimeKnown, SM); // Legacy numeric expressions only allow 2 operands. if (Legacy && !Expr.empty()) { SM.PrintMessage(SMLoc::getFromPointer(Expr.data()), SourceMgr::DK_Error, @@ -619,7 +656,8 @@ } else Fmt = ImplicitFmt; - FileCheckNumExpr *NumExpr = Context->makeNumExpr(NumExprAST, Fmt); + FileCheckNumExpr *NumExpr = + Context->makeNumExpr(NumExprAST, Fmt, MatchTimeKnown); // Parse numeric variable definition. if (!DefExpr.empty()) { @@ -766,6 +804,7 @@ bool IsVarDef; bool SubstNeeded; + bool CaptureNeeded; bool Legacy = false; StringRef DefName; StringRef SubstStr; @@ -795,7 +834,7 @@ StringRef Name = MatchStr.substr(0, TrailIdx); StringRef Trailer = MatchStr.substr(TrailIdx); - IsVarDef = (VarEndIdx != StringRef::npos); + CaptureNeeded = IsVarDef = (VarEndIdx != StringRef::npos); Legacy = IsNumExpr = IsPseudo; SubstNeeded = !IsVarDef; @@ -830,9 +869,10 @@ return true; IsNumExpr = true; IsVarDef = DefinedNumericVariable != nullptr; - SubstNeeded = NumExpr->getAST() != nullptr; if (IsVarDef) DefName = DefinedNumericVariable->getName(); + SubstNeeded = NumExpr->isMatchTimeKnown(); + CaptureNeeded = IsVarDef || !NumExpr->isMatchTimeKnown(); if (SubstNeeded) SubstStr = MatchStr; else { @@ -841,15 +881,16 @@ } } - // Handle variable definition: [[:(...)]] and [[#(...):(...)]]. - if (IsVarDef) { + // Handle variable definition: [[:(...)]] and [[#(...):(...)]] + // and numeric expression needing verifying. + if (CaptureNeeded) { RegExStr += '('; ++SubstInsertIdx; if (IsNumExpr) { - 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 // ParseNumericVariable() to get the pointer to the class instance // of the right variable definition corresponding to a given numeric @@ -873,7 +914,7 @@ if (!MatchRegexp.empty() && AddRegExToRegEx(MatchRegexp, CurParen, SM)) return true; - if (IsVarDef) + if (CaptureNeeded) RegExStr += ')'; // Handle variable use: [[foo]] and [[#]]. @@ -960,13 +1001,33 @@ // actual value. StringRef RegExToMatch = RegExStr; std::string TmpStr; + if (!Substitutions.empty()) { TmpStr = RegExStr; size_t InsertOffset = 0; // Substitute all pattern variables and numeric expressions whose value is // known just now. Use of pattern 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). llvm::Optional Value = Substitution.getResult(); @@ -988,46 +1049,84 @@ 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 pattern 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 pattern 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) { + assert(CapturedNumericExpression.CaptureParen < MatchInfo.size() && + "Internal paren error"); + unsigned CaptureParen = CapturedNumericExpression.CaptureParen; + StringRef MatchedStr = MatchInfo[CaptureParen]; + FileCheckNumExpr *NumExpr = CapturedNumericExpression.NumExpr; + FileCheckNumericVariable *DefinedNumericVariable = + CapturedNumericExpression.DefinedNumericVariable; + + StringRef MatchedValue = MatchInfo[CaptureParen]; + 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) { - assert(NumericVariableDef.second.CaptureParen < MatchInfo.size() && - "Internal paren error"); - unsigned CaptureParen = NumericVariableDef.second.CaptureParen; - FileCheckNumericVariable *DefinedNumericVariable = - NumericVariableDef.second.DefinedNumericVariable; - - StringRef MatchedValue = MatchInfo[CaptureParen]; - 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; } @@ -1052,7 +1151,8 @@ void FileCheckPattern::printSubstitutions(const SourceMgr &SM, StringRef Buffer, SMRange MatchRange) const { // Print what we know about substitutions. This covers both uses of pattern - // variables and numeric subsitutions. + // variables and numeric subsitutions as long as they only use variables + // defined in earlier lines. if (!Substitutions.empty()) { for (const auto &Substitution : Substitutions) { SmallString<256> Msg; @@ -1072,7 +1172,8 @@ OS.write_escaped(UndefVarName) << "\""; } } else { - // Substitution succeeded. Print substituted value. + // Substitution succeeded and numeric expression satisfied its + // constraint. Print substituted value. if (IsNumExpr) OS << "with numeric expression \""; else @@ -1167,8 +1268,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(); } @@ -2129,7 +2232,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 @@ -193,6 +193,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 @@ -357,3 +421,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 numeric expression "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 numeric expression "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 @@ -31,7 +31,7 @@ TEST_F(FileCheckTest, NumericVariable) { // Undefined variable: eval and clearValue fails, appendUndefVarNames returns // the variable and setValue works. - auto NumVarExpr = FileCheckNumExpr(nullptr, FmtUnsigned); + auto NumVarExpr = FileCheckNumExpr(nullptr, FmtUnsigned, false); FileCheckNumericVariable FooVar = FileCheckNumericVariable("FOO", &NumVarExpr, 1U); EXPECT_EQ("FOO", FooVar.getName()); @@ -44,7 +44,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: eval returns value set, setValue fails and // appendUndefVarNames is a nop. @@ -52,7 +52,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()); @@ -89,15 +89,15 @@ } TEST_F(FileCheckTest, Binop) { - auto DefNumExpr = FileCheckNumExpr(nullptr, FmtUnsigned); + auto DefNumExpr = FileCheckNumExpr(nullptr, FmtUnsigned, false); auto FooVar = std::make_shared("FOO", &DefNumExpr, 1); FileCheckNumExprVal LifeAnswer = FileCheckNumExprVal((uint64_t)42); - FooVar->setValue(LifeAnswer); + FooVar->setValue(LifeAnswer, false); auto BarVar = std::make_shared("BAR", &DefNumExpr, 2); 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 @@ -387,20 +387,20 @@ // Substitution of defined 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("@LINE", &DefNumExpr, 1); auto NVar = std::make_shared("N", &DefNumExpr, 1); 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); FileCheckPatternSubstitution SubstitutionLine = FileCheckPatternSubstitution(&Context, "@LINE", &NumExprLine, 12); FileCheckPatternSubstitution SubstitutionN = - FileCheckPatternSubstitution(&Context, "N", &NumExprN, 30); + FileCheckPatternSubstitution(&Context, "N", &NumExprN, 12); llvm::Optional Value = SubstitutionLine.getResult(); EXPECT_TRUE(Value); EXPECT_EQ("42", *Value); @@ -473,7 +473,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); Substitution = FileCheckPatternSubstitution(&Context, "@LINE", &NumExpr, 12); UndefVarNames.clear(); Substitution.getUndefVarNames(UndefVarNames);