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 @@ -105,6 +105,11 @@ Sets a filecheck pattern variable ``VAR`` with value ``VALUE`` that can be used in ``CHECK:`` lines. +.. option:: -D#= + + Sets a filecheck numeric variable ``NUMVAR`` to ```` that can be used + in ``CHECK:`` lines. + .. option:: -version Show the version number of this program. @@ -560,8 +565,65 @@ This makes it easier to ensure that individual tests are not affected by variables set in preceding tests. -FileCheck Numeric Expressions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +FileCheck Numeric Variables and Expressions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:program:`FileCheck` also allows defining numeric variables and checking for +numeric values that satisfy a numeric expression constraint based on those +variables. This allows ``CHECK:`` directives to verify a numeric relation +between two numbers, such as the need for consecutive registers to be used. + +The syntax to define a numeric variable is ``[[#:]]`` where ``NUMVAR`` +is the name of the numeric variable to define to the matching value. + +For example: + +.. code-block:: llvm + + ; CHECK: mov r[[#REG:]], 42 + +would match ``mov r5, 42`` and set ``REG`` to the value ``5``. + +The syntax to check a numeric expression constraint is +``[[#]]`` where: + +*```` is the name of a numeric variable defined on a previous line. + +*```` is an optional numeric operation to perform on the value of +````. Currently supported numeric operations are ``+`` and ``-``. + +*```` is the immediate value that constitutes the second operand of +the numeric operation . It must be present if ```` is present, +absent otherwise. + +For example: + +.. code-block:: llvm + + ; CHECK: add r[[#REG:]], r[[#REG]], r[[#REG+1]] + +The above example would match the line: + +.. code-block:: llvm + + add r5, r5, r6 + +but would not match the line: + +.. code-block:: llvm + + add r5, r5, r7 + +due to ``7`` being unequal to ``5 + 1``. + +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 defined on the same line. + +FileCheck Pseudo Numeric Variables +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Sometimes there's a need to verify output that contains line numbers of the match file, e.g. when testing compiler diagnostics. This introduces a certain @@ -569,11 +631,9 @@ line numbers in the same file, which have to be updated whenever line numbers change due to text addition or deletion. -To support this case, FileCheck allows using ``[[#@LINE]]``, -``[[#@LINE+]]`` and ``[[#@LINE-]]`` numeric expressions in -patterns, with an arbitrary number of spaces between each element of the -expression. These expressions expand to the number of the line where a pattern -is located (with an optional integer offset). +To support this case, FileCheck understands the ``@LINE`` pseudo numeric +variable which evaluates to the line number of the CHECK pattern where it is +found. This way match patterns can be put near the relevant test lines and include relative line number references, for example: 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 @@ -40,20 +40,79 @@ // Numeric expression handling code. //===----------------------------------------------------------------------===// -/// Class representing a numeric expression. +/// Class representing a numeric variable with a given value in a numeric +/// expression. Each definition of a variable gets its own instance of this +/// class, variable uses share the same instance as the respective definition. +class FileCheckNumericVariable { +private: + /// Name of the numeric variable. + StringRef Name; + + /// Value of numeric variable, if defined, or None otherwise. + llvm::Optional Value; + + /// Line number where this variable is defined. Used to determine whether a + /// variable is defined on the same line as a given use. + unsigned DefLineNumber; + +public: + /// Constructor for a variable \p Name defined at line \p DefLineNumber. + FileCheckNumericVariable(StringRef Name, unsigned DefLineNumber) + : Name(Name), Value(llvm::None), DefLineNumber(DefLineNumber) {} + + /// Constructor for numeric variable \p Name with a known \p Value at parse + /// time (e.g. the @LINE numeric variable). + FileCheckNumericVariable(StringRef Name, uint64_t Value) + : Name(Name), Value(Value) {} + + /// \returns name of that numeric variable. + StringRef getName() const { return Name; } + + /// \returns value of this numeric variable. + llvm::Optional getValue() const { return Value; } + + /// Sets value of this numeric variable if not defined. \returns whether the + /// variable was already defined. + bool setValue(uint64_t Value); + + /// Clears value of this numeric variable. \returns whether the variable was + /// already undefined. + bool clearValue(); + + /// \returns the line number where this variable is defined. + unsigned getDefLineNumber() { return DefLineNumber; } +}; + +/// Type of functions evaluating a given binary operation. +using binop_eval_t = uint64_t (*)(uint64_t, uint64_t); + +/// Class representing a numeric expression consisting of either a single +/// numeric variable or a binary operation between a numeric variable and an +/// immediate. class FileCheckNumExpr { private: - /// Value of the numeric expression. - uint64_t Value; + /// Left operand. + FileCheckNumericVariable *LeftOp; + + /// Right operand. + uint64_t RightOp; + + /// Pointer to function that can evaluate this binary operation. + binop_eval_t EvalBinop; public: - /// Constructor for a numeric expression with a known value at parse time, - /// e.g. the implicit numeric expression defining the @LINE numeric pseudo - /// variable. - explicit FileCheckNumExpr(uint64_t Value) : Value(Value) {} + FileCheckNumExpr(binop_eval_t EvalBinop, + FileCheckNumericVariable *OperandLeft, uint64_t OperandRight) + : LeftOp(OperandLeft), RightOp(OperandRight), EvalBinop(EvalBinop) {} - /// \returns the value being matched against. - uint64_t getValue() const { return Value; } + /// Evaluates and \returns the value of this numeric expression. Uses + /// EvalBinop to perform the binary operation it consists of. \returns None + /// if the numeric variable used is undefined. + llvm::Optional eval() const; + + /// \returns the name of the undefined variable used in this expression if + /// any or an empty string otherwise. + StringRef getUndefVarName() const; }; class FileCheckPatternContext; @@ -105,9 +164,9 @@ size_t getIndex() const { return InsertIdx; } /// \returns the result of the substitution represented by this class - /// instance or None if substitution failed. For a numeric expression we - /// substitute it by its value. For a pattern variable we simply replace it - /// by the text its definition matched. + /// instance or None if substitution failed. Numeric expression are + /// substituted by their value. Pattern variables are simply replaced by the + /// text their definition matched. llvm::Optional getResult() const; /// \returns the name of the undefined variable used in this substitution, if @@ -162,6 +221,18 @@ 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 +/// representing the numeric variable whose value is being defined. +struct FileCheckNumExprMatch { + /// Pointer to class representing the numeric variable whose value is being + /// defined. + FileCheckNumericVariable *DefinedNumericVariable; + + /// Parenthesized capture number for this numeric variable definition. + unsigned CaptureParen; +}; + /// Class holding the FileCheckPattern global state, shared by all patterns: /// tables holding values of variables and whether they are defined or not at /// any given time in the matching process. @@ -175,19 +246,37 @@ /// Back-references are used for uses after any the other definition. StringMap GlobalVariableTable; + /// Map of all pattern variables defined so far. Used at parse time to detect + /// a name conflict between a numeric variable and a pattern variable when + /// the former is defined on a later line than the latter. + StringMap DefinedVariableTable; + + /// When matching a given pattern, this holds the pointers to the classes + /// representing the last definition of numeric variables defined in previous + /// patterns. Earlier definition of the variables, if any, have their own + /// class instance not referenced from this table. When matching a pattern + /// all definitions for that pattern are recorded in NumericVariableDefs + /// table. + StringMap GlobalNumericVariableTable; + /// Vector holding pointers to all parsed numeric expressions. Used to /// automatically free the numeric expressions once they are guaranteed to no /// longer be used. std::vector> NumExprs; + /// Vector holding pointers to all parsed numeric variables. Used to + /// automatically free them once they are guaranteed to no longer be used. + std::vector> NumericVariables; + public: /// \returns the value of pattern variable \p VarName or None if no such /// variable has been defined. llvm::Optional getPatternVarValue(StringRef VarName); - /// Defines pattern variables from definitions given on the command line, - /// passed as a vector of VAR=VAL strings in \p CmdlineDefines. Reports any - /// error to \p SM and \returns whether an error occured. + /// Defines pattern and numeric variables from definitions given on the + /// command line, passed as a vector of [#]VAR=VAL strings in + /// \p CmdlineDefines. Reports any error to \p SM and \returns whether an + /// error occured. bool defineCmdlineVariables(std::vector &CmdlineDefines, SourceMgr &SM); @@ -198,7 +287,14 @@ private: /// Makes a new numeric expression instance and registers it for destruction /// when the context is destroyed. - template FileCheckNumExpr *makeNumExpr(Types... Args); + FileCheckNumExpr *makeNumExpr(binop_eval_t EvalBinop, + FileCheckNumericVariable *OperandLeft, + uint64_t OperandRight); + + /// Makes a new numeric variable and registers it for destruction when the + /// context is destroyed. + template + FileCheckNumericVariable *makeNumericVariable(Types... args); }; class FileCheckPattern { @@ -214,12 +310,12 @@ /// Entries in this vector represent uses of a pattern variable or a numeric /// expression in the pattern that need to be substituted in the regexp - /// pattern at match time, e.g. "foo[[bar]]baz[[#@LINE+1]]". In this case, - /// the RegExStr will contain "foobaz" and we'll get two entries in this - /// vector that tells us to insert the value of pattern variable "bar" at - /// offset 3 and the value of numeric expression "@LINE+1" at offset 6. Uses - /// are represented by a FileCheckPatternSubstitution class to abstract - /// whether it is a pattern variable or a numeric expression. + /// pattern at match time, e.g. "foo[[bar]]baz[[#N+1]]". In this case, the + /// RegExStr will contain "foobaz" and we'll get two entries in this vector + /// that tells us to insert the value of pattern variable "bar" at offset 3 + /// and the value of numeric expression "N+1" at offset 6. Uses are + /// represented by a FileCheckPatternSubstitution class to abstract whether + /// it is a pattern variable or a numeric expression. std::vector Substitutions; /// Maps names of pattern variables defined in a pattern to the parenthesized @@ -233,19 +329,30 @@ /// iterating over values. std::map VariableDefs; - /// Pointer to the class instance shared by all patterns holding a table with - /// the values of live variables at the start of any given CHECK line. + /// 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; + + /// Pointer to a class instance holding the global state shared by all + /// patterns: + /// - tables with the values of live pattern and numeric variables at + /// the start of any given CHECK line; + /// - table holding whether a pattern variable has been defined at any given + /// point during the parsing phase. FileCheckPatternContext *Context; Check::FileCheckType CheckTy; - /// Contains the number of line this pattern is in. + /// Line number for this CHECK pattern. Used to determine whether a variable + /// definition is made on an earlier line to the one with this CHECK. unsigned LineNumber; public: explicit FileCheckPattern(Check::FileCheckType Ty, - FileCheckPatternContext *Context) - : Context(Context), CheckTy(Ty) {} + FileCheckPatternContext *Context, + unsigned LineNumber) + : Context(Context), CheckTy(Ty), LineNumber(LineNumber) {} /// \returns the location in source code. SMLoc getLoc() const { return PatternLoc; } @@ -262,12 +369,21 @@ /// character that is part of the variable name. Otherwise, only /// \returns true. static bool parseVariable(StringRef Str, bool &IsPseudo, unsigned &TrailIdx); - /// Parses a numeric expression involving pseudo variable \p Name with the - /// string corresponding to the operation being performed in \p Trailer. - /// \returns the class representing the numeric expression or nullptr if - /// parsing fails in which case errors are reported on \p SM. - FileCheckNumExpr *parseNumericExpression(StringRef Name, StringRef Trailer, - const SourceMgr &SM) const; + /// Parses \p Expr for use or definition (if \p IsDefinition is true) of a + /// numeric variable. \returns whether parsing fails in which case errors are + /// reported on \p SM. Otherwise, sets \p Name to the name of the parsed + /// numeric variable. + bool parseNumericVariable(StringRef &Expr, StringRef &Name, bool IsDefinition, + const SourceMgr &SM) const; + /// Parses \p Expr for a numeric expression. \returns the class representing + /// the AST of numeric expression or nullptr if parsing fails in which case + /// errors are reported on \p SM. Sets \p DefinedNumericVariable to the + /// pointer to the class representing the variable defined to this numeric + /// expression if any. + FileCheckNumExpr * + parseNumericExpression(StringRef Expr, + FileCheckNumericVariable *&DefinedNumericVariable, + const SourceMgr &SM) const; /// Parses the pattern in \p PatternStr and initialize this FileCheckPattern /// instance accordingly. /// @@ -278,17 +394,20 @@ /// pattern string was read. \returns true in case of an error, false /// otherwise. bool ParsePattern(StringRef PatternStr, StringRef Prefix, SourceMgr &SM, - unsigned LineNumber, const FileCheckRequest &Req); + const FileCheckRequest &Req); /// Matches the pattern string against the input buffer \p Buffer /// /// This returns the position that is matched or npos if there is no match. - /// If there is a match, \returns the size of the matched string in \p - /// MatchLen. + /// If there is a match, the size of the matched string is returned in + /// \p MatchLen. /// /// The GlobalVariableTable StringMap in the FileCheckPatternContext class /// instance provides the current values of FileCheck pattern variables and - /// is updated if this match defines new values. - size_t match(StringRef Buffer, size_t &MatchLen) const; + /// is updated if this match defines new values. Likewise, the + /// GlobalNumericVariableTable StringMap in the same class provides the + /// current values of FileCheck numeric variables and is updated if this + /// 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. void printSubstitutions(const SourceMgr &SM, StringRef Buffer, @@ -317,6 +436,13 @@ /// opening sequence). Returns the offset of the closing sequence within Str, /// or npos if it was not found. size_t FindRegexVarEnd(StringRef Str, SourceMgr &SM); + + /// Parses \p Expr for a binary operation. + /// \returns the class representing that binary operation of the numeric + /// expression or nullptr if parsing fails in which case errors are reported + /// on \p SM. + FileCheckNumExpr *parseFileCheckBinop(StringRef &Expr, + 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 @@ -24,24 +24,55 @@ using namespace llvm; +bool FileCheckNumericVariable::setValue(uint64_t NewValue) { + if (Value) + return true; + Value = NewValue; + return false; +} + +bool FileCheckNumericVariable::clearValue() { + if (!Value) + return true; + Value = llvm::None; + return false; +} + +llvm::Optional FileCheckNumExpr::eval() const { + assert(LeftOp != nullptr && "Evaluating an empty numeric expression"); + llvm::Optional LeftOpValue = LeftOp->getValue(); + // Variable is undefined. + if (!LeftOpValue) + return llvm::None; + return EvalBinop(*LeftOpValue, RightOp); +} + +StringRef FileCheckNumExpr::getUndefVarName() const { + if (!LeftOp->getValue()) + return LeftOp->getName(); + return StringRef(); +} + llvm::Optional FileCheckPatternSubstitution::getResult() const { if (IsNumExpr) { - return utostr(NumExpr->getValue()); - } else { - // Look up the value and escape it so that we can put it into the - // regex. - llvm::Optional VarVal = Context->getPatternVarValue(FromStr); - if (!VarVal) + llvm::Optional EvaluatedValue = NumExpr->eval(); + if (!EvaluatedValue) return llvm::None; - return Regex::escape(*VarVal); + return utostr(*EvaluatedValue); } + + // Look up the value and escape it so that we can put it into the regex. + llvm::Optional VarVal = Context->getPatternVarValue(FromStr); + if (!VarVal) + return llvm::None; + return Regex::escape(*VarVal); } StringRef FileCheckPatternSubstitution::getUndefVarName() const { - // Parsing guarantees only @LINE is ever referenced and it is not undefined - // by ClearLocalVars. if (IsNumExpr) - return StringRef(); + // Although a use of an undefined numeric variable is detected at parse + // time, a numeric variable can be undefined later by ClearLocalVariables. + return NumExpr->getUndefVarName(); if (!Context->getPatternVarValue(FromStr)) return FromStr; @@ -91,50 +122,103 @@ return C; } -FileCheckNumExpr * -FileCheckPattern::parseNumericExpression(StringRef Name, StringRef Trailer, - const SourceMgr &SM) const { - if (!Name.equals("@LINE")) { +bool FileCheckPattern::parseNumericVariable(StringRef &Expr, StringRef &Name, + bool IsDefinition, + const SourceMgr &SM) const { + bool IsPseudo; + unsigned TrailIdx; + + if (parseVariable(Expr, IsPseudo, TrailIdx)) { + SM.PrintMessage(SMLoc::getFromPointer(Expr.data()), SourceMgr::DK_Error, + "invalid variable name"); + return true; + } + Name = Expr.substr(0, TrailIdx); + Expr = Expr.substr(TrailIdx); + + if (IsPseudo && !Name.equals("@LINE")) { SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, "invalid pseudo numeric variable '" + Name + "'"); - return nullptr; + return true; + } + if (IsDefinition && IsPseudo) { + SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, + "definition of pseudo variable '" + Name + "' unsupported"); + return true; } - // Check if this is a supported operation and select function to perform it. - Trailer = Trailer.ltrim(SpaceChars); - if (Trailer.empty()) - return Context->makeNumExpr(LineNumber); - SMLoc OpLoc = SMLoc::getFromPointer(Trailer.data()); - char Operator = popFront(Trailer); + if (IsDefinition) { + // Detect collision between pattern and numeric variable when the latter is + // created later than the former. + if (Context->DefinedVariableTable.find(Name) != + Context->DefinedVariableTable.end()) { + SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, + "pattern variable with name '" + Name + + "' already exists"); + return true; + } - // Parse right operand. - Trailer = Trailer.ltrim(SpaceChars); - if (Trailer.empty()) { - SM.PrintMessage(SMLoc::getFromPointer(Trailer.data()), SourceMgr::DK_Error, - "missing operand in numeric expression '" + Trailer + "'"); - return nullptr; + return false; } - uint64_t Offset; - if (Trailer.consumeInteger(10, Offset)) { - SM.PrintMessage(SMLoc::getFromPointer(Trailer.data()), SourceMgr::DK_Error, - "invalid offset in numeric expression '" + Trailer + "'"); - return nullptr; + + // This method is indirectly called from ParsePattern for all numeric + // variable definitions and uses in the order in which they appear in the + // CHECK pattern. For each definition, the pointer to the corresponding + // class instance of the corresponding numeric variable definition is + // stored in GlobalNumericVariableTable. Therefore, the pointer we get + // below is for the class instance corresponding to the last definition of + // this variable use. + auto VarTableIter = Context->GlobalNumericVariableTable.find(Name); + if (VarTableIter == Context->GlobalNumericVariableTable.end()) { + SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, + "using undefined numeric variable '" + Name + "'"); + return true; } - Trailer = Trailer.ltrim(SpaceChars); - if (!Trailer.empty()) { - SM.PrintMessage(SMLoc::getFromPointer(Trailer.data()), SourceMgr::DK_Error, - "unexpected characters at end of numeric expression '" + - Trailer + "'"); - return nullptr; + + FileCheckNumericVariable *NumericVariable = VarTableIter->second; + if (!IsPseudo && NumericVariable->getDefLineNumber() == LineNumber) { + SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, + "numeric variable '" + NumericVariable->getName() + + "' defined on the same line"); + return true; } - uint64_t Value; + return false; +} + +static uint64_t add(uint64_t LeftOp, uint64_t RightOp) { + return LeftOp + RightOp; +} + +static uint64_t sub(uint64_t LeftOp, uint64_t RightOp) { + return LeftOp - RightOp; +} + +FileCheckNumExpr * +FileCheckPattern::parseFileCheckBinop(StringRef &Expr, + const SourceMgr &SM) const { + StringRef Name; + if (parseNumericVariable(Expr, Name, false, SM)) { + // Error reporting done in parseNumericVariable. + return nullptr; + } + FileCheckNumericVariable *LeftOp = + Context->GlobalNumericVariableTable.find(Name)->second; + + // Check if this is a supported operation and select a function to perform + // it. + Expr = Expr.ltrim(SpaceChars); + if (Expr.empty()) + return Context->makeNumExpr(add, LeftOp, 0); + SMLoc OpLoc = SMLoc::getFromPointer(Expr.data()); + char Operator = popFront(Expr); + binop_eval_t EvalBinop; switch (Operator) { case '+': - Value = LineNumber + Offset; + EvalBinop = add; break; case '-': - Value = LineNumber - Offset; + EvalBinop = sub; break; default: SM.PrintMessage(OpLoc, SourceMgr::DK_Error, @@ -142,17 +226,87 @@ "'"); return nullptr; } - return Context->makeNumExpr(Value); + + // Parse right operand. + Expr = Expr.ltrim(SpaceChars); + if (Expr.empty()) { + SM.PrintMessage(SMLoc::getFromPointer(Expr.data()), SourceMgr::DK_Error, + "missing operand in numeric expression"); + return nullptr; + } + uint64_t RightOp; + if (Expr.consumeInteger(10, RightOp)) { + SM.PrintMessage(SMLoc::getFromPointer(Expr.data()), SourceMgr::DK_Error, + "invalid offset in numeric expression '" + Expr + "'"); + return nullptr; + } + Expr = Expr.ltrim(SpaceChars); + if (!Expr.empty()) { + SM.PrintMessage(SMLoc::getFromPointer(Expr.data()), SourceMgr::DK_Error, + "unexpected characters at end of numeric expression '" + + Expr + "'"); + return nullptr; + } + + return Context->makeNumExpr(EvalBinop, LeftOp, RightOp); +} + +FileCheckNumExpr *FileCheckPattern::parseNumericExpression( + StringRef Expr, FileCheckNumericVariable *&DefinedNumericVariable, + const SourceMgr &SM) const { + + // Parse numeric variable definition. + DefinedNumericVariable = nullptr; + size_t DefEnd = Expr.find(':'); + if (DefEnd != StringRef::npos) { + StringRef DefExpr = Expr.substr(0, DefEnd); + StringRef UseExpr = Expr = Expr.substr(DefEnd + 1); + + DefExpr = DefExpr.ltrim(SpaceChars); + StringRef Name; + if (parseNumericVariable(DefExpr, Name, true /*IsDefinition*/, SM)) + // Invalid variable definition. Error reporting done in parsing function. + return nullptr; + + DefinedNumericVariable = + Context->makeNumericVariable(Name, this->LineNumber); + + DefExpr = DefExpr.ltrim(SpaceChars); + if (!DefExpr.empty()) { + SM.PrintMessage(SMLoc::getFromPointer(DefExpr.data()), + SourceMgr::DK_Error, + "invalid numeric variable definition"); + return nullptr; + } + UseExpr = UseExpr.ltrim(SpaceChars); + if (!UseExpr.empty()) { + SM.PrintMessage( + SMLoc::getFromPointer(UseExpr.data()), SourceMgr::DK_Error, + "unexpected string after variable definition: '" + UseExpr + "'"); + return nullptr; + } + return Context->makeNumExpr(add, nullptr, 0); + } else + + // Parse numeric expression itself. + Expr = Expr.ltrim(SpaceChars); + return parseFileCheckBinop(Expr, SM); } bool FileCheckPattern::ParsePattern(StringRef PatternStr, StringRef Prefix, - SourceMgr &SM, unsigned LineNumber, - const FileCheckRequest &Req) { + SourceMgr &SM, + const FileCheckRequest &Req) { bool MatchFullLinesHere = Req.MatchFullLines && CheckTy != Check::CheckNot; - this->LineNumber = LineNumber; PatternLoc = SMLoc::getFromPointer(PatternStr.data()); + // Create fake @LINE pseudo variable definition. + StringRef LinePseudo = "@LINE"; + uint64_t LineNumber64 = LineNumber; + FileCheckNumericVariable *LinePseudoVar = + Context->makeNumericVariable(LinePseudo, LineNumber64); + Context->GlobalNumericVariableTable[LinePseudo] = LinePseudoVar; + if (!(Req.NoCanonicalizeWhiteSpace && Req.MatchFullLines)) // Ignore trailing whitespace. while (!PatternStr.empty() && @@ -230,10 +384,10 @@ // forms: [[foo:.*]] and [[foo]]. The former matches .* (or some other // regex) and assigns it to the FileCheck variable 'foo'. The latter // substitutes foo's value. Numeric expressions start with a '#' sign after - // the double brackets and only have the substitution form. Pattern - // variables must satisfy the regular expression "[a-zA-Z_][0-9a-zA-Z_]*" - // to be valid, as this helps catch some common errors. Numeric expressions - // only support the @LINE pseudo numeric variable. + // the double brackets and also have the definition and substitution forms. + // Both pattern and numeric variables must satisfy the regular expression + // "[a-zA-Z_][0-9a-zA-Z_]*" to be valid, as this helps catch some common + // errors. if (PatternStr.startswith("[[")) { StringRef UnparsedPatternStr = PatternStr.substr(2); // Find the closing bracket pair ending the match. End is going to be an @@ -255,56 +409,84 @@ // of the first unparsed character. PatternStr = UnparsedPatternStr.substr(End + 2); - size_t VarEndIdx = MatchStr.find(":"); - if (IsNumExpr) - MatchStr = MatchStr.ltrim(SpaceChars); - else { + bool IsVarDef; + StringRef DefName; + StringRef SubstStr; + StringRef MatchRegexp; + size_t SubstInsertIdx = RegExStr.size(); + FileCheckNumericVariable *DefinedNumericVariable; + FileCheckNumExpr *NumExpr; + + // Parse pattern variable or legacy numeric expression. + if (!IsNumExpr) { + size_t VarEndIdx = MatchStr.find(":"); size_t SpacePos = MatchStr.substr(0, VarEndIdx).find_first_of(" \t"); if (SpacePos != StringRef::npos) { SM.PrintMessage(SMLoc::getFromPointer(MatchStr.data() + SpacePos), SourceMgr::DK_Error, "unexpected whitespace"); return true; } - } - // Get the regex name (e.g. "foo") and verify it is well formed. - bool IsPseudo; - unsigned TrailIdx; - if (parseVariable(MatchStr, IsPseudo, TrailIdx)) { - SM.PrintMessage(SMLoc::getFromPointer(MatchStr.data()), - SourceMgr::DK_Error, "invalid variable name"); - return true; - } + // Get the regex name (e.g. "foo") and verify it is well formed. + bool IsPseudo; + unsigned TrailIdx; + if (parseVariable(MatchStr, IsPseudo, TrailIdx)) { + SM.PrintMessage(SMLoc::getFromPointer(MatchStr.data()), + SourceMgr::DK_Error, "invalid variable name"); + return true; + } - size_t SubstInsertIdx = RegExStr.size(); - FileCheckNumExpr *NumExpr; + StringRef Name = MatchStr.substr(0, TrailIdx); + StringRef Trailer = MatchStr.substr(TrailIdx); + IsVarDef = (VarEndIdx != StringRef::npos); + IsNumExpr = IsPseudo; - StringRef Name = MatchStr.substr(0, TrailIdx); - StringRef Trailer = MatchStr.substr(TrailIdx); - bool IsVarDef = (VarEndIdx != StringRef::npos); + if (IsVarDef) { + if ((IsPseudo || !Trailer.consume_front(":"))) { + SM.PrintMessage(SMLoc::getFromPointer(MatchStr.data()), + SourceMgr::DK_Error, + "invalid name in pattern variable definition"); + return true; + } - if (IsVarDef && (IsPseudo || !Trailer.consume_front(":"))) { - SM.PrintMessage(SMLoc::getFromPointer(MatchStr.data()), - SourceMgr::DK_Error, - "invalid name in pattern variable definition"); - return true; + // Detect collision between pattern and numeric variable when the + // former is created later than the latter. + if (Context->GlobalNumericVariableTable.find(Name) != + Context->GlobalNumericVariableTable.end()) { + SM.PrintMessage( + SMLoc::getFromPointer(MatchStr.data()), SourceMgr::DK_Error, + "numeric variable with name '" + Name + "' already exists"); + return true; + } + DefName = Name; + MatchRegexp = Trailer; + } else + SubstStr = MatchStr; } - if (!IsVarDef && IsPseudo) { - NumExpr = parseNumericExpression(Name, Trailer, SM); + // Parse numeric expression. + if (IsNumExpr) { + NumExpr = parseNumericExpression(MatchStr, DefinedNumericVariable, SM); if (NumExpr == nullptr) return true; IsNumExpr = true; + if (DefinedNumericVariable != nullptr) { + IsVarDef = true; + DefName = DefinedNumericVariable->getName(); + MatchRegexp = StringRef("[0-9]+"); + } else + SubstStr = MatchStr; } - // Handle [[foo]]. + // Handle variable use: [[foo]] and [[#]]. if (!IsVarDef) { // Handle use of pattern variables that were defined earlier on the - // same line by emitting a backreference. - if (!IsNumExpr && VariableDefs.find(Name) != VariableDefs.end()) { - unsigned CaptureParen = VariableDefs[Name]; + // same line by emitting a backreference. Numeric expressions do not + // support using a numeric variable defined on the same line. + if (!IsNumExpr && VariableDefs.find(SubstStr) != VariableDefs.end()) { + unsigned CaptureParen = VariableDefs[SubstStr]; if (CaptureParen < 1 || CaptureParen > 9) { - SM.PrintMessage(SMLoc::getFromPointer(Name.data()), + SM.PrintMessage(SMLoc::getFromPointer(SubstStr.data()), SourceMgr::DK_Error, "Can't back-reference more than 9 variables"); return true; @@ -314,21 +496,39 @@ // Handle use of pattern variables ([[]]) defined in previous // CHECK pattern or use of a numeric expression. FileCheckPatternSubstitution Substitution = - IsNumExpr ? FileCheckPatternSubstitution(Context, MatchStr, + IsNumExpr ? FileCheckPatternSubstitution(Context, SubstStr, NumExpr, SubstInsertIdx) - : FileCheckPatternSubstitution(Context, MatchStr, + : FileCheckPatternSubstitution(Context, SubstStr, SubstInsertIdx); Substitutions.push_back(Substitution); } continue; } - // Handle [[foo:.*]]. - VariableDefs[Name] = CurParen; + // Handle variable definition: [[:(...)]] and [[#(...):(...)]]. + if (IsNumExpr) { + struct FileCheckNumExprMatch NumExprDef = {DefinedNumericVariable, + CurParen}; + NumericVariableDefs[DefName] = 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 + // variable use. + Context->GlobalNumericVariableTable[DefName] = DefinedNumericVariable; + } else { + VariableDefs[DefName] = CurParen; + // Mark pattern variable as defined to detect collision between pattern + // and numeric variable in parseNumericVariable and + // DefineCmdlineVariables when the latter is created later than the + // former. We cannot reuse GlobalVariableTable for that by populating + // it with an empty string since we would then loose the ability to + // detect use of undefined variable in match(). + Context->DefinedVariableTable[DefName] = true; + } RegExStr += '('; ++CurParen; - if (AddRegExToRegEx(Trailer, CurParen, SM)) + if (AddRegExToRegEx(MatchRegexp, CurParen, SM)) return true; RegExStr += ')'; @@ -371,7 +571,8 @@ RegExStr += Backref; } -size_t FileCheckPattern::match(StringRef Buffer, size_t &MatchLen) const { +size_t FileCheckPattern::match(StringRef Buffer, size_t &MatchLen, + const SourceMgr &SM) const { // If this is the EOF pattern, match it immediately. if (CheckTy == Check::CheckEOF) { MatchLen = 0; @@ -428,6 +629,24 @@ MatchInfo[VariableDef.second]; } + // 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]; + uint64_t Val; + if (MatchedValue.getAsInteger(10, Val)) { + SM.PrintMessage(SMLoc::getFromPointer(MatchedValue.data()), + SourceMgr::DK_Error, "Unable to represent numeric value"); + } + if (DefinedNumericVariable->setValue(Val)) + assert(false && "Numeric variable redefined"); + } + // 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. @@ -566,12 +785,22 @@ return VarIter->second; } -template -FileCheckNumExpr *FileCheckPatternContext::makeNumExpr(Types... Args) { - NumExprs.emplace_back(new FileCheckNumExpr(Args...)); +FileCheckNumExpr * +FileCheckPatternContext::makeNumExpr(binop_eval_t EvalBinop, + FileCheckNumericVariable *OperandLeft, + uint64_t OperandRight) { + NumExprs.emplace_back( + new FileCheckNumExpr(EvalBinop, OperandLeft, OperandRight)); return NumExprs.back().get(); } +template +FileCheckNumericVariable * +FileCheckPatternContext::makeNumericVariable(Types... args) { + NumericVariables.emplace_back(new FileCheckNumericVariable(args...)); + return NumericVariables.back().get(); +} + size_t FileCheckPattern::FindRegexVarEnd(StringRef Str, SourceMgr &SM) { // Offset keeps track of the current offset within the input Str size_t Offset = 0; @@ -850,9 +1079,9 @@ SM.AddNewSourceBuffer(std::move(CmdLine), SMLoc()); ImplicitNegativeChecks.push_back( - FileCheckPattern(Check::CheckNot, &PatternContext)); + FileCheckPattern(Check::CheckNot, &PatternContext, 0)); ImplicitNegativeChecks.back().ParsePattern(PatternInBuffer, - "IMPLICIT-CHECK", SM, 0, Req); + "IMPLICIT-CHECK", SM, Req); } std::vector DagNotMatches = ImplicitNegativeChecks; @@ -913,8 +1142,8 @@ SMLoc PatternLoc = SMLoc::getFromPointer(Buffer.data()); // Parse the pattern. - FileCheckPattern P(CheckTy, &PatternContext); - if (P.ParsePattern(Buffer.substr(0, EOL), UsedPrefix, SM, LineNumber, Req)) + FileCheckPattern P(CheckTy, &PatternContext, LineNumber); + if (P.ParsePattern(Buffer.substr(0, EOL), UsedPrefix, SM, Req)) return true; // Verify that CHECK-LABEL lines do not define or use variables @@ -958,7 +1187,7 @@ // prefix as a filler for the error message. if (!DagNotMatches.empty()) { CheckStrings.emplace_back( - FileCheckPattern(Check::CheckEOF, &PatternContext), + FileCheckPattern(Check::CheckEOF, &PatternContext, LineNumber + 1), *Req.CheckPrefixes.begin(), SMLoc::getFromPointer(Buffer.data())); std::swap(DagNotMatches, CheckStrings.back().DagNotStrings); } @@ -1132,7 +1361,7 @@ StringRef MatchBuffer = Buffer.substr(LastMatchEnd); size_t CurrentMatchLen; // get a match at current start point - size_t MatchPos = Pat.match(MatchBuffer, CurrentMatchLen); + size_t MatchPos = Pat.match(MatchBuffer, CurrentMatchLen, SM); if (i == 1) FirstMatchPos = LastPos + MatchPos; @@ -1253,7 +1482,7 @@ assert((Pat->getCheckTy() == Check::CheckNot) && "Expect CHECK-NOT!"); size_t MatchLen = 0; - size_t Pos = Pat->match(Buffer, MatchLen); + size_t Pos = Pat->match(Buffer, MatchLen, SM); if (Pos == StringRef::npos) { PrintNoMatch(false, SM, Prefix, Pat->getLoc(), *Pat, 1, Buffer, @@ -1313,7 +1542,7 @@ // CHECK-DAG group. for (auto MI = MatchRanges.begin(), ME = MatchRanges.end(); true; ++MI) { StringRef MatchBuffer = Buffer.substr(MatchPos); - size_t MatchPosBuf = Pat.match(MatchBuffer, MatchLen); + size_t MatchPosBuf = Pat.match(MatchBuffer, MatchLen, SM); // With a group of CHECK-DAGs, a single mismatching means the match on // that group of CHECK-DAGs fails immediately. if (MatchPosBuf == StringRef::npos) { @@ -1445,7 +1674,7 @@ bool FileCheckPatternContext::defineCmdlineVariables( std::vector &CmdlineDefines, SourceMgr &SM) { - assert(GlobalVariableTable.empty() && + assert(GlobalVariableTable.empty() && GlobalNumericVariableTable.empty() && "Overriding defined variable with command-line variable definitions"); if (CmdlineDefines.empty()) @@ -1463,36 +1692,108 @@ CmdlineDefsDiag += (Prefix1 + Twine(++I) + Prefix2 + CmdlineDef + "\n").str(); + // Create a buffer with fake command line content in order to display + // parsing diagnostic with location information and point to the + // global definition with invalid syntax. std::unique_ptr CmdLineDefsDiagBuffer = MemoryBuffer::getMemBufferCopy(CmdlineDefsDiag, "Global defines"); StringRef CmdlineDefsDiagRef = CmdLineDefsDiagBuffer->getBuffer(); SM.AddNewSourceBuffer(std::move(CmdLineDefsDiagBuffer), SMLoc()); + // Dummy pattern to call parseNumericVariable. + FileCheckPattern P(Check::CheckPlain, this, 0); + SmallVector CmdlineDefsDiagVec; CmdlineDefsDiagRef.split(CmdlineDefsDiagVec, '\n', -1 /*MaxSplit*/, false /*KeepEmpty*/); for (StringRef CmdlineDefDiag : CmdlineDefsDiagVec) { - unsigned NameStart = CmdlineDefDiag.find(Prefix2) + Prefix2.size(); - if (CmdlineDefDiag.substr(NameStart).find('=') == StringRef::npos) { - SM.PrintMessage(SMLoc::getFromPointer(CmdlineDefDiag.data()), + unsigned DefStart = CmdlineDefDiag.find(Prefix2) + Prefix2.size(); + StringRef CmdlineDef = CmdlineDefDiag.substr(DefStart); + size_t EqIdx = CmdlineDef.find('='); + if (EqIdx == StringRef::npos) { + SM.PrintMessage(SMLoc::getFromPointer(CmdlineDef.data()), SourceMgr::DK_Error, "Missing equal sign in global definition"); ErrorFound = true; continue; } - std::pair CmdlineNameVal = - CmdlineDefDiag.substr(NameStart).split('='); - StringRef Name = CmdlineNameVal.first; - bool IsPseudo; - unsigned TrailIdx; - if (FileCheckPattern::parseVariable(Name, IsPseudo, TrailIdx) || IsPseudo || - TrailIdx != Name.size() || Name.empty()) { - SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, - "invalid name for variable definition '" + Name + "'"); - ErrorFound = true; - continue; + + // Numeric variable definition. + if (CmdlineDef[0] == '#') { + StringRef Expr = CmdlineDef.substr(1, EqIdx - 1); + StringRef CmdlineName; + SMLoc CmdlineNameLoc = SMLoc::getFromPointer(Expr.data()); + if (P.parseNumericVariable(Expr, CmdlineName, true /*IsDefinition*/, + SM) || + !Expr.empty()) { + SM.PrintMessage(CmdlineNameLoc, SourceMgr::DK_Error, + "invalid variable name"); + ErrorFound = true; + continue; + } + + // Detect collision between pattern and numeric variable when the latter + // is created later than the former. + if (DefinedVariableTable.find(CmdlineName) != + DefinedVariableTable.end()) { + SM.PrintMessage( + SMLoc::getFromPointer(CmdlineName.data()), SourceMgr::DK_Error, + "pattern variable with name '" + CmdlineName + "' already exists"); + ErrorFound = true; + continue; + } + + StringRef CmdlineVal = CmdlineDef.substr(EqIdx + 1); + uint64_t Val; + if (CmdlineVal.getAsInteger(10, Val)) { + SM.PrintMessage(SMLoc::getFromPointer(CmdlineVal.data()), + SourceMgr::DK_Error, + "invalid value in numeric variable definition '" + + CmdlineVal + "'"); + ErrorFound = true; + continue; + } + auto DefinedNumericVariable = + makeNumericVariable(CmdlineName, (unsigned)0); + DefinedNumericVariable->setValue(Val); + + // Record this variable definition. + GlobalNumericVariableTable[DefinedNumericVariable->getName()] = + DefinedNumericVariable; + } else { + // Pattern variable definition. + std::pair CmdlineNameVal = CmdlineDef.split('='); + StringRef Name = CmdlineNameVal.first; + bool IsPseudo; + unsigned TrailIdx; + if (FileCheckPattern::parseVariable(Name, IsPseudo, TrailIdx) || + IsPseudo || TrailIdx != Name.size() || Name.empty()) { + SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, + "invalid name in pattern variable definition '" + Name + + "'"); + ErrorFound = true; + continue; + } + + // Detect collision between pattern and numeric variable when the former + // is created later than the latter. + if (GlobalNumericVariableTable.find(Name) != + GlobalNumericVariableTable.end()) { + SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, + "numeric variable with name '" + Name + + "' already exists"); + ErrorFound = true; + continue; + } + GlobalVariableTable.insert(CmdlineNameVal); + // Mark the pattern variable as defined to detect collisions between + // pattern and numeric variables in DefineCmdlineVariables when the + // latter is created later than the former. We cannot reuse + // GlobalVariableTable for that by populating it with an empty string + // since we would then lose the ability to detect use of undefined + // variable in match(). + DefinedVariableTable[Name] = true; } - GlobalVariableTable.insert(CmdlineNameVal); } return ErrorFound; @@ -1504,8 +1805,22 @@ if (Var.first()[0] != '$') LocalPatternVars.push_back(Var.first()); + // Numeric expression substitution reads the value of a variable directly, + // not via GlobalNumericVariableTable. Therefore, we clear local variables by + // clearing their value which will lead to a numeric expression substitution + // failure. We also mark the variable for removal from + // GlobalNumericVariableTable since this is what defineCmdlineVariables + // checks to decide that no global variable has been defined. + for (const auto &Var : GlobalNumericVariableTable) + if (Var.first()[0] != '$') { + Var.getValue()->clearValue(); + LocalNumericVars.push_back(Var.first()); + } + for (const auto &Var : LocalPatternVars) GlobalVariableTable.erase(Var); + for (const auto &Var : LocalNumericVars) + GlobalNumericVariableTable.erase(Var); } bool llvm::FileCheck::CheckInput(SourceMgr &SM, StringRef Buffer, @@ -1538,7 +1853,10 @@ ++j; } - if (Req.EnableVarScope) + // Do not clear the first region as it's the one before the first + // CHECK-LABEL and it would clear variables defined on the command-line + // before they get used. + if (i != 0 && Req.EnableVarScope) PatternContext.clearLocalVars(); for (; i != j; ++i) { diff --git a/llvm/test/FileCheck/numeric-defines.txt b/llvm/test/FileCheck/numeric-defines.txt new file mode 100644 --- /dev/null +++ b/llvm/test/FileCheck/numeric-defines.txt @@ -0,0 +1,47 @@ +; RUN: FileCheck -D#NUMVAL=12 -check-prefix CHECKNUM -input-file %s %s +; RUN: not FileCheck -D#NUMVAL=8 -check-prefix CHECKNUM -input-file %s %s 2>&1 \ +; RUN: | FileCheck %s --strict-whitespace -check-prefix NUMERRMSG +; RUN: not FileCheck -D#NUMVAL=12 -check-prefix NUMNOT -input-file %s %s 2>&1 \ +; RUN: | FileCheck %s --strict-whitespace -check-prefix NOT-NUMERRMSG +; RUN: FileCheck -D#NUMVAL=8 -check-prefixes NUMNOT -input-file %s %s + +Numeric value = 12 +; CHECKNUM: Numeric value = [[#NUMVAL]] +; NUMNOT-NOT: Numeric value = [[#NUMVAL]] + +; NUMERRMSG: defines.txt:[[#@LINE-3]]:13: error: CHECKNUM: expected string not found in input +; NUMERRMSG: defines.txt:1:1: note: scanning from here +; NUMERRMSG: defines.txt:1:1: note: with numeric expression "NUMVAL" equal to "8" +; NUMERRMSG: defines.txt:[[#@LINE-7]]:1: note: possible intended match here + +; NOT-NUMERRMSG: defines.txt:[[#@LINE-7]]:15: error: {{NUMNOT}}-NOT: excluded string found in input +; NOT-NUMERRMSG: defines.txt:[[#@LINE-10]]:1: note: found here +; NOT-NUMERRMSG: defines.txt:[[#@LINE-11]]:1: note: with numeric expression "NUMVAL" equal to "12" + +; RUN: not FileCheck -D#10VALUE=10 -input-file %s %s 2>&1 \ +; RUN: | FileCheck %s --strict-whitespace -check-prefix NUMERRCLIFMT + +; NUMERRCLIFMT: Global defines:1:20: error: invalid variable name +; NUMERRCLIFMT-NEXT: Global define #1: #10VALUE=10 +; NUMERRCLIFMT-NEXT: {{^ \^$}} + +; RUN: not FileCheck -D#@VALUE=10 -input-file %s %s 2>&1 \ +; RUN: | FileCheck %s --strict-whitespace -check-prefix NUMERRCLIPSEUDO + +; NUMERRCLIPSEUDO: Global defines:1:20: error: invalid pseudo numeric variable +; NUMERRCLIPSEUDO-NEXT: Global define #1: #@VALUE=10 +; NUMERRCLIPSEUDO-NEXT: {{^ \^$}} + +; RUN: not FileCheck -D#'VALUE + 2=10' -input-file %s %s 2>&1 \ +: RUN: | FileCheck %s --strict-whitespace -check-prefix NUMERRCLITRAIL + +; NUMERRCLITRAIL: Global defines:1:20: error: invalid variable name +; NUMERRCLITRAIL-NEXT: Global define #1: #VALUE + 2=10 +; NUMERRCLITRAIL-NEXT: {{^ \^$}} + +; RUN: not FileCheck -D#VALUE1=3 -D#VALUE2='VALUE1 + 2' -input-file %s %s 2>&1 \ +; RUN: | FileCheck %s --strict-whitespace -check-prefix NUMERRCLIEXPR + +; NUMERRCLIEXPR: Global defines:2:27: error: invalid value in numeric variable definition 'VALUE1 + 2' +; NUMERRCLIEXPR-NEXT: Global define #2: #VALUE2=VALUE1 + 2 +; NUMERRCLIEXPR-NEXT: {{^ \^$}} diff --git a/llvm/test/FileCheck/numeric-expression.txt b/llvm/test/FileCheck/numeric-expression.txt new file mode 100644 --- /dev/null +++ b/llvm/test/FileCheck/numeric-expression.txt @@ -0,0 +1,124 @@ +; RUN: FileCheck -input-file %s %s + +; We use CHECK-NEXT directives to force a match on all lines with digits. + +; Numeric variable definition without spaces +DEF NO SPC +11 +; CHECK-LABEL: DEF NO SPC +; CHECK-NEXT: [[#VAR1:]] + +; Numeric variable definition in alternate spacing +DEF ALT SPC +11 +11 +11 +; CHECK-LABEL: DEF ALT SPC +; CHECK-NEXT: [[# VAR1a:]] +; CHECK-NEXT: [[# VAR1b :]] +; CHECK-NEXT: [[# VAR1c : ]] + +; Numeric expressions using variables defined on other lines without spaces +USE NO SPC +11 +12 +10 +; CHECK-LABEL: USE +; CHECK-NEXT: [[#VAR1]] +; CHECK-NEXT: [[#VAR1+1]] +; CHECK-NEXT: [[#VAR1-1]] + +; Numeric expressions using variables defined on other lines in alternate +; spacing +USE ALT SPC +11 +11 +12 +12 +12 +12 +10 +10 +10 +10 +; CHECK-LABEL: USE ALT SPC +; CHECK-NEXT: [[# VAR1]] +; CHECK-NEXT: [[# VAR1 ]] +; CHECK-NEXT: [[# VAR1+1]] +; CHECK-NEXT: [[# VAR1 +1]] +; CHECK-NEXT: [[# VAR1 + 1]] +; CHECK-NEXT: [[# VAR1 + 1 ]] +; CHECK-NEXT: [[# VAR1-1]] +; CHECK-NEXT: [[# VAR1 -1]] +; CHECK-NEXT: [[# VAR1 - 1]] +; CHECK-NEXT: [[# VAR1 - 1 ]] + +; Numeric expressions using variables defined on the command-line and an +; immediate interpreted as an unsigned value +; Note: 9223372036854775819 = 0x8000000000000000 + 11 +; 9223372036854775808 = 0x8000000000000000 +USE UNSIGNED IMM +9223372036854775819 +; CHECK-LABEL: USE UNSIGNED IMM +; CHECK-NEXT: [[#VAR1+9223372036854775808]] + +; Numeric expression using undefined variable +; RUN: not FileCheck -check-prefix UNDEF-USE -input-file %s %s 2>&1 \ +; RUN: | FileCheck --strict-whitespace -check-prefix UNDEF-USE-MSG %s + +UNDEF VAR USE +UNDEFVAR: 11 +; UNDEF-USE-LABEL: UNDEF VAR USE +; UNDEF-USE-NEXT: UNDEFVAR: [[#UNDEFVAR]] +; UNDEF-USE-MSG: numeric-expression.txt:[[#@LINE-1]]:32: error: using undefined numeric variable 'UNDEFVAR' +; UNDEF-USE-MSG-NEXT: ; {{U}}NDEF-USE-NEXT: UNDEFVAR: {{\[\[#UNDEFVAR\]\]}} +; UNDEF-USE-MSG-NEXT: {{^ \^$}} + +; Numeric expression with unsupported operator +; RUN: not FileCheck -check-prefixes CHECK,INVAL-OP -input-file %s %s 2>&1 \ +; RUN: | FileCheck --strict-whitespace -check-prefix INVAL-OP-MSG %s + +INVALID OPERATOR +VAR1*2: 22 +; INVAL-OP-LABEL: INVALID OPERATOR +; INVAL-OP-NEXT: VAR1*2: [[#VAR1*2]] +; INVAL-OP-MSG: numeric-expression.txt:[[#@LINE-1]]:33: error: unsupported numeric operation '*' +; INVAL-OP-MSG-NEXT: ; {{I}}NVAL-OP-NEXT: VAR1*2: {{\[\[#VAR1\*2\]\]}} +; INVAL-OP-MSG-NEXT: {{^ \^$}} + +; Name conflict between Numeric variable definition and pattern variable +; definition whether from the command-line or input text +; RUN: not FileCheck -check-prefixes CONFLICT,CONFLICT1,CONFLICT2 -input-file %s %s 2>&1 \ +; RUN: | FileCheck --strict-whitespace -check-prefix INPUT-PAT-CONFLICT %s +; RUN: not FileCheck -D#NUMVAR=42 -check-prefixes CONFLICT,CONFLICT2 -input-file %s %s 2>&1 \ +; RUN: | FileCheck --strict-whitespace -check-prefix INPUT-PAT-CONFLICT %s +; RUN: not FileCheck -D#NUMVAR=42 -DNUMVAR=foobar -check-prefix CONFLICT -input-file %s %s 2>&1 \ +; RUN: | FileCheck --strict-whitespace -check-prefix CLI-PAT-CONFLICT %s +; RUN: not FileCheck -check-prefixes CONFLICT,CONFLICT3,CONFLICT4 -input-file %s %s 2>&1 \ +; RUN: | FileCheck --strict-whitespace -check-prefix INPUT-NUM-CONFLICT %s +; RUN: not FileCheck -DPATVAR=foobar -check-prefixes CONFLICT,CONFLICT4 -input-file %s %s 2>&1 \ +; RUN: | FileCheck --strict-whitespace -check-prefix INPUT-NUM-CONFLICT %s +; RUN: not FileCheck -DPATVAR=foobar -D#PATVAR=42 -check-prefix CONFLICT -input-file %s %s 2>&1 \ +; RUN: | FileCheck --strict-whitespace -check-prefix CLI-NUM-CONFLICT %s + +PATVAR NUMVAR CONFLICT +redef1 42 +foobar +redef2 42 +; CONFLICT-LABEL: PATVAR NUMVAR CONFLICT +; CONFLICT1-NEXT: redef1 [[#NUMVAR:]] +; CONFLICT2: [[NUMVAR:foo.*]] +; CONFLICT3: [[PATVAR:foo.*]] +; CONFLICT4: redef2 [[#PATVAR:]] +; INPUT-PAT-CONFLICT: numeric-expression.txt:[[#@LINE-3]]:16: error: numeric variable with name 'NUMVAR' already exists +; INPUT-PAT-CONFLICT-NEXT: ; {{C}}ONFLICT2: {{\[\[NUMVAR:foo\.\*\]\]}} +; INPUT-PAT-CONFLICT-NEXT: {{^ \^$}} +; CLI-PAT-CONFLICT: Global defines:2:19: error: numeric variable with name 'NUMVAR' already exists +; CLI-PAT-CONFLICT-NEXT: Global define #2: NUMVAR=foobar +; CLI-PAT-CONFLICT-NEXT: {{^ \^$}} +; INPUT-NUM-CONFLICT: numeric-expression.txt:[[#@LINE-7]]:24: error: pattern variable with name 'PATVAR' already exists +; INPUT-NUM-CONFLICT-NEXT: ; CONFLICT4: redef2 {{\[\[#PATVAR:\]\]}} +; INPUT-NUM-CONFLICT-NEXT: {{^ \^$}} +; CLI-NUM-CONFLICT: Global defines:2:20: error: pattern variable with name 'PATVAR' already exists +; CLI-NUM-CONFLICT-NEXT: Global define #2: #PATVAR=42 +; CLI-NUM-CONFLICT-NEXT: {{^ \^$}} diff --git a/llvm/test/FileCheck/defines.txt b/llvm/test/FileCheck/pattern-defines.txt rename from llvm/test/FileCheck/defines.txt rename to llvm/test/FileCheck/pattern-defines.txt --- a/llvm/test/FileCheck/defines.txt +++ b/llvm/test/FileCheck/pattern-defines.txt @@ -1,16 +1,10 @@ ; RUN: FileCheck -DVALUE=10 -input-file %s %s -; RUN: not FileCheck -DVALUE=20 -input-file %s %s 2>&1 | FileCheck %s -check-prefix ERRMSG -; RUN: not FileCheck -DVALUE=10 -check-prefix NOT -input-file %s %s 2>&1 | FileCheck %s -check-prefix NOT-ERRMSG +; RUN: not FileCheck -DVALUE=20 -input-file %s %s 2>&1 \ +; RUN: | FileCheck %s -check-prefix ERRMSG +; RUN: not FileCheck -DVALUE=10 -check-prefix NOT -input-file %s %s 2>&1 \ +; RUN: | FileCheck %s -check-prefix NOT-ERRMSG ; RUN: FileCheck -DVALUE=20 -check-prefix NOT -input-file %s %s -; RUN: not FileCheck -DVALUE10 -input-file %s %s 2>&1 | FileCheck %s -check-prefix ERRCLIEQ1 -; RUN: not FileCheck -D -input-file %s %s 2>&1 | FileCheck %s -check-prefix ERRCLIEQ2 -; RUN: not FileCheck -D=10 -input-file %s %s 2>&1 | FileCheck %s -check-prefix ERRCLIVAR1 -; RUN: not FileCheck -D= -input-file %s %s 2>&1 | FileCheck %s -check-prefix ERRCLIVAR2 -; RUN: FileCheck -DVALUE= -check-prefix EMPTY -input-file %s %s 2>&1 -; RUN: not FileCheck -D10VALUE=10 -input-file %s %s 2>&1 | FileCheck %s --strict-whitespace -check-prefix ERRCLIFMT -; RUN: not FileCheck -D@VALUE=10 -input-file %s %s 2>&1 | FileCheck %s --strict-whitespace -check-prefix ERRCLIPSEUDO -; RUN: not FileCheck -D'VALUE + 2=10' -input-file %s %s 2>&1 | FileCheck %s --strict-whitespace -check-prefix ERRCLITRAIL Value = 10 ; CHECK: Value = [[VALUE]] ; NOT-NOT: Value = [[VALUE]] @@ -24,25 +18,48 @@ ; NOT-ERRMSG: defines.txt:[[@LINE-10]]:1: note: found here ; NOT-ERRMSG: defines.txt:[[@LINE-11]]:1: note: with variable "VALUE" equal to "10" +; RUN: not FileCheck -DVALUE10 -input-file %s %s 2>&1 \ +; RUN: | FileCheck %s -check-prefix ERRCLIEQ1 + ; ERRCLIEQ1: Missing equal sign in command-line definition '-DVALUE10' +; RUN: not FileCheck -D -input-file %s %s 2>&1 \ +; RUN: | FileCheck %s -check-prefix ERRCLIEQ2 + ; ERRCLIEQ2: {{F|f}}ile{{C|c}}heck{{[^:]*}}: for the -D option: requires a value! +; RUN: not FileCheck -D=10 -input-file %s %s 2>&1 \ +; RUN: | FileCheck %s -check-prefix ERRCLIVAR1 + ; ERRCLIVAR1: Missing pattern variable name in command-line definition '-D=10' +; RUN: not FileCheck -D= -input-file %s %s 2>&1 \ +; RUN: | FileCheck %s -check-prefix ERRCLIVAR2 + ; ERRCLIVAR2: Missing pattern variable name in command-line definition '-D=' +; RUN: FileCheck -DVALUE= -check-prefix EMPTY -input-file %s %s 2>&1 + Empty value = @@ ; EMPTY: Empty value = @[[VALUE]]@ -; ERRCLIFMT: Global defines:1:19: error: invalid name for variable definition '10VALUE' +; RUN: not FileCheck -D10VALUE=10 -input-file %s %s 2>&1 \ +; RUN: | FileCheck %s --strict-whitespace -check-prefix ERRCLIFMT + +; ERRCLIFMT: Global defines:1:19: error: invalid name in pattern variable definition '10VALUE' ; ERRCLIFMT-NEXT: Global define #1: 10VALUE=10 ; ERRCLIFMT-NEXT: {{^ \^$}} -; ERRCLIPSEUDO: Global defines:1:19: error: invalid name for variable definition '@VALUE' +; RUN: not FileCheck -D@VALUE=10 -input-file %s %s 2>&1 \ +; RUN: | FileCheck %s --strict-whitespace -check-prefix ERRCLIPSEUDO + +; ERRCLIPSEUDO: Global defines:1:19: error: invalid name in pattern variable definition '@VALUE' ; ERRCLIPSEUDO-NEXT: Global define #1: @VALUE=10 ; ERRCLIPSEUDO-NEXT: {{^ \^$}} -; ERRCLITRAIL: Global defines:1:19: error: invalid name for variable definition 'VALUE + 2' +; RUN: not FileCheck -D'VALUE + 2=10' -input-file %s %s 2>&1 \ +; RUN: | FileCheck %s --strict-whitespace -check-prefix ERRCLITRAIL + +; ERRCLITRAIL: Global defines:1:19: error: invalid name in pattern variable definition 'VALUE + 2' ; ERRCLITRAIL-NEXT: Global define #1: VALUE + 2=10 ; ERRCLITRAIL-NEXT: {{^ \^$}} diff --git a/llvm/test/FileCheck/regex-scope.txt b/llvm/test/FileCheck/regex-scope.txt deleted file mode 100644 --- a/llvm/test/FileCheck/regex-scope.txt +++ /dev/null @@ -1,23 +0,0 @@ -// RUN: FileCheck -input-file %s %s -// RUN: FileCheck -check-prefixes CHECK,GLOBAL -input-file %s %s -// RUN: FileCheck -check-prefixes CHECK,LOCAL -input-file %s %s -// RUN: FileCheck -check-prefixes CHECK,GLOBAL --enable-var-scope -input-file %s %s -// RUN: not FileCheck -check-prefixes CHECK,LOCAL --enable-var-scope -input-file %s %s - -local -global -; CHECK: [[LOCAL:loc.*]] -; CHECK: [[$GLOBAL:glo.*]] - -local2 -global2 -; CHECK: [[LOCAL]]2 -; CHECK: [[$GLOBAL]]2 - -barrier: -; CHECK-LABEL: barrier - -local3 -global3 -; LOCAL: [[LOCAL]]3 -; GLOBAL: [[$GLOBAL]]3 diff --git a/llvm/test/FileCheck/var-scope.txt b/llvm/test/FileCheck/var-scope.txt new file mode 100644 --- /dev/null +++ b/llvm/test/FileCheck/var-scope.txt @@ -0,0 +1,27 @@ +// RUN: FileCheck -input-file %s %s +// RUN: FileCheck -check-prefixes CHECK,GLOBAL -input-file %s %s +// RUN: FileCheck -check-prefixes CHECK,LOCAL3 -input-file %s %s +// RUN: FileCheck -check-prefixes CHECK,GLOBAL --enable-var-scope -input-file %s %s +// RUN: not FileCheck -check-prefixes CHECK,LOCAL1 --enable-var-scope -input-file %s %s +// RUN: not FileCheck -check-prefixes CHECK,LOCAL2 --enable-var-scope -input-file %s %s +// RUN: not FileCheck -check-prefixes CHECK,LOCAL3 --enable-var-scope -input-file %s %s + +local1 +global1 +; CHECK: [[LOCAL:loc[^[:digit:]]*]][[#LOCNUM:]] +; CHECK: [[$GLOBAL:glo[^[:digit:]]*]][[#$GLOBNUM:]] + +local2 +global2 +; CHECK: [[LOCAL]][[#LOCNUM+1]] +; CHECK: [[$GLOBAL]][[#$GLOBNUM+1]] + +barrier: +; CHECK-LABEL: barrier + +local3 +global3 +; LOCAL1: [[LOCAL]]3 +; LOCAL2: local[[#LOCNUM+2]] +; LOCAL3: [[LOCAL]][[#LOCNUM+2]] +; GLOBAL: [[$GLOBAL]][[#$GLOBNUM+2]] 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 @@ -1,6 +1,6 @@ ; RUN: FileCheck -input-file %s %s 2>&1 | FileCheck -check-prefix QUIET --allow-empty %s -; RUN: FileCheck -v -input-file %s %s 2>&1 | FileCheck -check-prefix V %s -; RUN: FileCheck -vv -input-file %s %s 2>&1 | FileCheck -check-prefixes V,VV %s +; RUN: FileCheck -v -input-file %s %s 2>&1 | FileCheck --strict-whitespace -check-prefix V %s +; RUN: FileCheck -vv -input-file %s %s 2>&1 | FileCheck --strict-whitespace -check-prefixes V,VV %s foo bar @@ -29,6 +29,36 @@ VV-NEXT: {{^}}bar{{$}} VV-NEXT: {{^}}^{{$}} +NUMVAR=42 +NUMVAR - 1:41 +CHECK: NUMVAR=[[#NUMVAR:]] +CHECK-NOT: [[#NUMVAR + 1]] +CHECK-NEXT: NUMVAR - 1:[[#NUMVAR - 1]] + +V: verbose.txt:[[#@LINE-4]]:8: remark: {{C}}HECK: expected string found in input +V-NEXT: {{C}}HECK: {{NUMVAR=[[][[]#NUMVAR:[]][]]$}} +V-NEXT: {{^ \^$}} +V-NEXT: verbose.txt:[[#@LINE-9]]:1: note: found here +V-NEXT: {{^}}NUMVAR=42{{$}} +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: {{^}}^~~~~~~~~~~~~{{$}} + +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: {{^}}^{{$}} + before empty after empty @@ -99,7 +129,7 @@ VV-NEXT: verbose.txt:[[@LINE-9]]:19: remark: implicit EOF: expected string found in input VV-NEXT: {{C}}HECK-NOT: {{[{][{]z[}][}]yx$}} -VV-NEXT: {{^ \^$}} +VV-NEXT: {{^ \^$}} VV-NEXT: verbose.txt:[[@LINE+13]]:1: note: found here VV-NOT: {{.}} VV: {{^\^$}} 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 @@ -14,6 +14,50 @@ class FileCheckTest : public ::testing::Test {}; +TEST_F(FileCheckTest, NumericVariableValueGetClearSet) { + FileCheckNumericVariable FooVar = + FileCheckNumericVariable("FOO", (uint64_t)42); + + // Defined variable: getValue returns a value, setValue fails. + llvm::Optional Value = FooVar.getValue(); + EXPECT_TRUE(Value); + EXPECT_EQ((uint64_t)42, *Value); + EXPECT_TRUE(FooVar.setValue((uint64_t)43)); + + // Clearing variable: getValue fails, clearValue again fails. + EXPECT_FALSE(FooVar.clearValue()); + Value = FooVar.getValue(); + EXPECT_FALSE(Value); + EXPECT_TRUE(FooVar.clearValue()); + + // Undefined variable: setValue works, getValue returns value set. + EXPECT_FALSE(FooVar.setValue((uint64_t)43)); + Value = FooVar.getValue(); + EXPECT_TRUE(Value); + EXPECT_EQ((uint64_t)43, *Value); +} + +uint64_t doAdd(uint64_t OpL, uint64_t OpR) { return OpL + OpR; } + +TEST_F(FileCheckTest, NumExprEvalUndef) { + auto FooVar = new FileCheckNumericVariable("FOO", (uint64_t)42); + auto NumExpr = new FileCheckNumExpr(doAdd, FooVar, 18); + + // Defined variable: eval returns right value, no undef variable returned. + llvm::Optional Value = NumExpr->eval(); + EXPECT_TRUE(Value); + EXPECT_EQ((uint64_t)60, *Value); + StringRef UndefVar = NumExpr->getUndefVarName(); + EXPECT_EQ("", UndefVar); + + // Undefined variable: eval fails, undef variable returned. + FooVar->clearValue(); + Value = NumExpr->eval(); + EXPECT_FALSE(Value); + UndefVar = NumExpr->getUndefVarName(); + EXPECT_EQ("FOO", UndefVar); +} + TEST_F(FileCheckTest, ValidVarNameStart) { EXPECT_TRUE(FileCheckPattern::isValidVarNameStart('a')); EXPECT_TRUE(FileCheckPattern::isValidVarNameStart('G')); @@ -90,60 +134,195 @@ EXPECT_EQ(TrailIdx, VarName.size() - 1); } -class ExprTester { +static StringRef bufferize(SourceMgr &SM, StringRef Str) { + std::unique_ptr Buffer = + MemoryBuffer::getMemBufferCopy(Str, "TestBuffer"); + StringRef StrBufferRef = Buffer->getBuffer(); + SM.AddNewSourceBuffer(std::move(Buffer), SMLoc()); + return StrBufferRef; +} + +class PatternTester { private: + unsigned LineNumber = 1; SourceMgr SM; + struct FileCheckRequest Req; FileCheckPatternContext Context; - FileCheckPattern P = FileCheckPattern(Check::CheckPlain, &Context); + FileCheckPattern P = + FileCheckPattern(Check::CheckPlain, &Context, LineNumber++); + +public: + PatternTester() { + std::vector GlobalDefines; + GlobalDefines.emplace_back(std::string("#FOO=42")); + GlobalDefines.emplace_back(std::string("BAR=BAZ")); + Context.defineCmdlineVariables(GlobalDefines, SM); + // Call ParsePattern to have @LINE defined. + P.ParsePattern("N/A", "CHECK", SM, Req); + // ParsePattern does not expect to be called twice for the same line and + // will set FixedStr and RegExStr incorrectly if it is. Therefore prepare + // a pattern for a different line. + initNextPattern(); + } + + void initNextPattern() { + P = FileCheckPattern(Check::CheckPlain, &Context, LineNumber++); + } public: - bool parseExpect(std::string &VarName, std::string &Trailer) { - std::string NameTrailer = VarName + Trailer; - std::unique_ptr Buffer = - MemoryBuffer::getMemBufferCopy(NameTrailer, "TestBuffer"); - StringRef NameTrailerRef = Buffer->getBuffer(); - SM.AddNewSourceBuffer(std::move(Buffer), SMLoc()); - StringRef VarNameRef = NameTrailerRef.substr(0, VarName.size()); - StringRef TrailerRef = NameTrailerRef.substr(VarName.size()); - return P.parseNumericExpression(VarNameRef, TrailerRef, SM) == nullptr; + bool parseNumVarExpect(StringRef Expr, bool isDefinition) { + StringRef ExprBufferRef = bufferize(SM, Expr); + StringRef Name; + return P.parseNumericVariable(ExprBufferRef, Name, isDefinition, SM); + } + + bool parseExprExpect(StringRef Expr) { + StringRef ExprBufferRef = bufferize(SM, Expr); + FileCheckNumericVariable *DefinedNumericVariable; + return P.parseNumericExpression(ExprBufferRef, DefinedNumericVariable, + SM) == nullptr; + } + + bool parsePatternExpect(StringRef Pattern) { + StringRef PatBufferRef = bufferize(SM, Pattern); + return P.ParsePattern(PatBufferRef, "CHECK", SM, Req); + } + + bool matchExpect(StringRef Buffer) { + StringRef BufferRef = bufferize(SM, Buffer); + size_t MatchLen; + return P.match(BufferRef, MatchLen, SM); } }; +TEST_F(FileCheckTest, ParseNumericVariable) { + PatternTester Tester; + + // Invalid variable name. + EXPECT_TRUE(Tester.parseNumVarExpect("42INVALID", false /*isDefinition*/)); + + // Invalid pseudo variable. + EXPECT_TRUE(Tester.parseNumVarExpect("@FOO", false /*isDefinition*/)); + + // Valid pseudo variable. + EXPECT_FALSE(Tester.parseNumVarExpect("@LINE", false /*isDefinition*/)); + + // Invalid definition of pseudo. + EXPECT_TRUE(Tester.parseNumVarExpect("@LINE", true /*isDefinition*/)); + + // Conflict with pattern variable. + EXPECT_TRUE(Tester.parseNumVarExpect("BAR", true /*isDefinition*/)); + + // Undefined variable. + EXPECT_TRUE(Tester.parseNumVarExpect("UNDEF", false /*isDefinition*/)); + + // Use variable defined on same line. + EXPECT_FALSE(Tester.parseExprExpect("LINE1VAR:")); + EXPECT_TRUE(Tester.parseNumVarExpect("LINE1VAR", false /*isDefinition*/)); + + // Defined variable. + EXPECT_FALSE(Tester.parseNumVarExpect("FOO", false /*isDefinition*/)); +} + TEST_F(FileCheckTest, ParseExpr) { - ExprTester Tester; + PatternTester Tester; + + // Variable definition. + + // Definition of invalid variable. + EXPECT_TRUE(Tester.parseExprExpect("10VAR:")); + EXPECT_TRUE(Tester.parseExprExpect("@FOO:")); + EXPECT_TRUE(Tester.parseExprExpect("@LINE:")); + + // Garbage after name of variable being defined. + EXPECT_TRUE(Tester.parseExprExpect("VAR GARBAGE:")); + + // Variable defined to numeric expression. + EXPECT_TRUE(Tester.parseExprExpect("VAR1: FOO")); - // @LINE with offset. - std::string VarName = "@LINE"; - std::string Trailer = "+3"; - EXPECT_FALSE(Tester.parseExpect(VarName, Trailer)); + // Acceptable variable definition. + EXPECT_FALSE(Tester.parseExprExpect("VAR1:")); + EXPECT_FALSE(Tester.parseExprExpect(" VAR2:")); + EXPECT_FALSE(Tester.parseExprExpect("VAR3 :")); + EXPECT_FALSE(Tester.parseExprExpect("VAR3: ")); - // @LINE only. - Trailer = ""; - EXPECT_FALSE(Tester.parseExpect(VarName, Trailer)); + // Numeric expression. - // Wrong Pseudovar. - VarName = "@FOO"; - EXPECT_TRUE(Tester.parseExpect(VarName, Trailer)); + // Unacceptable variable. + EXPECT_TRUE(Tester.parseExprExpect("10VAR")); + EXPECT_TRUE(Tester.parseExprExpect("@FOO")); + EXPECT_TRUE(Tester.parseExprExpect("UNDEF")); + + // Only valid variable. + EXPECT_FALSE(Tester.parseExprExpect("@LINE")); + EXPECT_FALSE(Tester.parseExprExpect("FOO")); // Unsupported operator. - VarName = "@LINE"; - Trailer = "/2"; - EXPECT_TRUE(Tester.parseExpect(VarName, Trailer)); + EXPECT_TRUE(Tester.parseExprExpect("@LINE/2")); // Missing offset operand. - VarName = "@LINE"; - Trailer = "+"; - EXPECT_TRUE(Tester.parseExpect(VarName, Trailer)); + EXPECT_TRUE(Tester.parseExprExpect("@LINE+")); // Cannot parse offset operand. - VarName = "@LINE"; - Trailer = "+x"; - EXPECT_TRUE(Tester.parseExpect(VarName, Trailer)); + EXPECT_TRUE(Tester.parseExprExpect("@LINE+x")); // Unexpected string at end of numeric expression. - VarName = "@LINE"; - Trailer = "+5x"; - EXPECT_TRUE(Tester.parseExpect(VarName, Trailer)); + EXPECT_TRUE(Tester.parseExprExpect("@LINE+5x")); + + // Valid expression. + EXPECT_FALSE(Tester.parseExprExpect("@LINE+5")); + EXPECT_FALSE(Tester.parseExprExpect("FOO+4")); +} + +TEST_F(FileCheckTest, ParsePattern) { + PatternTester Tester; + + // Space in pattern variable expression. + EXPECT_TRUE(Tester.parsePatternExpect("[[ BAR]]")); + + // Invalid variable name. + EXPECT_TRUE(Tester.parsePatternExpect("[[42INVALID]]")); + + // Invalid pattern variable definition. + EXPECT_TRUE(Tester.parsePatternExpect("[[@PAT:]]")); + EXPECT_TRUE(Tester.parsePatternExpect("[[PAT+2:]]")); + + // Collision with numeric variable. + EXPECT_TRUE(Tester.parsePatternExpect("[[FOO:]]")); + + // Valid use of pattern variable. + EXPECT_FALSE(Tester.parsePatternExpect("[[BAR]]")); + + // Valid pattern variable definition. + EXPECT_FALSE(Tester.parsePatternExpect("[[PAT:[0-9]+]]")); + + // Invalid numeric expressions. + EXPECT_TRUE(Tester.parsePatternExpect("[[#42INVALID]]")); + EXPECT_TRUE(Tester.parsePatternExpect("[[#@FOO]]")); + EXPECT_TRUE(Tester.parsePatternExpect("[[#@LINE/2]]")); + EXPECT_TRUE(Tester.parsePatternExpect("[[#2+@LINE]]")); + EXPECT_TRUE(Tester.parsePatternExpect("[[#YUP:@LINE]]")); + + // Valid numeric expressions and numeric variable definition. + EXPECT_FALSE(Tester.parsePatternExpect("[[#FOO]]")); + EXPECT_FALSE(Tester.parsePatternExpect("[[#@LINE+2]]")); + EXPECT_FALSE(Tester.parsePatternExpect("[[#NUMVAR:]]")); +} + +TEST_F(FileCheckTest, Match) { + PatternTester Tester; + + // Check matching a definition only matches a number. + Tester.parsePatternExpect("[[#NUMVAR:]]"); + EXPECT_TRUE(Tester.matchExpect("FAIL")); + EXPECT_FALSE(Tester.matchExpect("18")); + + // Check matching the variable defined matches the correct number only + Tester.initNextPattern(); + Tester.parsePatternExpect("[[#NUMVAR]] [[#NUMVAR+2]]"); + EXPECT_TRUE(Tester.matchExpect("19 21")); + EXPECT_TRUE(Tester.matchExpect("18 21")); + EXPECT_FALSE(Tester.matchExpect("18 20")); } TEST_F(FileCheckTest, Substitution) { @@ -153,17 +332,25 @@ GlobalDefines.emplace_back(std::string("FOO=BAR")); Context.defineCmdlineVariables(GlobalDefines, SM); + // Substitution of undefined pattern variable fails. FileCheckPatternSubstitution Substitution = FileCheckPatternSubstitution(&Context, "VAR404", 42); EXPECT_FALSE(Substitution.getResult()); - FileCheckNumExpr NumExpr = FileCheckNumExpr(42); + // Substitution of defined numeric variable returns the right value. + auto LineVar = new FileCheckNumericVariable("@LINE", (uint64_t)42); + FileCheckNumExpr NumExpr = FileCheckNumExpr(doAdd, LineVar, 0); Substitution = FileCheckPatternSubstitution(&Context, "@LINE", &NumExpr, 12); llvm::Optional Value = Substitution.getResult(); EXPECT_TRUE(Value); EXPECT_EQ("42", *Value); - FileCheckPattern P = FileCheckPattern(Check::CheckPlain, &Context); + // Substitution of undefined numeric variable fails. + LineVar->clearValue(); + EXPECT_FALSE(Substitution.getResult()); + + // Substitution of defined pattern variable returns the right value. + FileCheckPattern P = FileCheckPattern(Check::CheckPlain, &Context, 1); Substitution = FileCheckPatternSubstitution(&Context, "FOO", 42); Value = Substitution.getResult(); EXPECT_TRUE(Value); @@ -177,19 +364,32 @@ GlobalDefines.emplace_back(std::string("FOO=BAR")); Context.defineCmdlineVariables(GlobalDefines, SM); + // Undef var in pattern variable substitution with undefined variable returns + // the variable. FileCheckPatternSubstitution Substitution = FileCheckPatternSubstitution(&Context, "VAR404", 42); StringRef UndefVar = Substitution.getUndefVarName(); EXPECT_EQ("VAR404", UndefVar); - FileCheckNumExpr NumExpr = FileCheckNumExpr(42); - Substitution = FileCheckPatternSubstitution(&Context, "@LINE", &NumExpr, 12); + // Undef var in pattern variable substitution with defined variable is empty. + Substitution = FileCheckPatternSubstitution(&Context, "FOO", 42); UndefVar = Substitution.getUndefVarName(); EXPECT_EQ("", UndefVar); - Substitution = FileCheckPatternSubstitution(&Context, "FOO", 42); + // Undef var in numeric expression substitution with defined variable is + // empty. + auto LineVar = new FileCheckNumericVariable("@LINE", (uint64_t)42); + FileCheckNumExpr NumExpr = FileCheckNumExpr(doAdd, LineVar, 0); + Substitution = FileCheckPatternSubstitution(&Context, "@LINE", &NumExpr, 12); UndefVar = Substitution.getUndefVarName(); EXPECT_EQ("", UndefVar); + + // Undef var in valid numeric expression substitution is empty. + // Undef var in numeric expression substitution with undefined variable + // returns the variable. + LineVar->clearValue(); + UndefVar = Substitution.getUndefVarName(); + EXPECT_EQ("@LINE", UndefVar); } TEST_F(FileCheckTest, FileCheckContext) { @@ -197,36 +397,72 @@ std::vector GlobalDefines; SourceMgr SM; - // Missing equal sign + // Missing equal sign. GlobalDefines.emplace_back(std::string("LocalVar")); EXPECT_TRUE(Cxt.defineCmdlineVariables(GlobalDefines, SM)); + GlobalDefines.clear(); + GlobalDefines.emplace_back(std::string("#LocalNumVar")); + EXPECT_TRUE(Cxt.defineCmdlineVariables(GlobalDefines, SM)); - // Empty variable + // Empty variable. GlobalDefines.clear(); GlobalDefines.emplace_back(std::string("=18")); EXPECT_TRUE(Cxt.defineCmdlineVariables(GlobalDefines, SM)); + GlobalDefines.clear(); + GlobalDefines.emplace_back(std::string("#=18")); + EXPECT_TRUE(Cxt.defineCmdlineVariables(GlobalDefines, SM)); - // Invalid variable + // Invalid variable. GlobalDefines.clear(); GlobalDefines.emplace_back(std::string("18LocalVar=18")); EXPECT_TRUE(Cxt.defineCmdlineVariables(GlobalDefines, SM)); + GlobalDefines.clear(); + GlobalDefines.emplace_back(std::string("#18LocalNumVar=18")); + EXPECT_TRUE(Cxt.defineCmdlineVariables(GlobalDefines, SM)); + + // Name conflict between pattern and numeric variable. + GlobalDefines.clear(); + GlobalDefines.emplace_back(std::string("LocalVar=18")); + GlobalDefines.emplace_back(std::string("#LocalVar=36")); + EXPECT_TRUE(Cxt.defineCmdlineVariables(GlobalDefines, SM)); + Cxt = FileCheckPatternContext(); + GlobalDefines.clear(); + GlobalDefines.emplace_back(std::string("#LocalNumVar=18")); + GlobalDefines.emplace_back(std::string("LocalNumVar=36")); + EXPECT_TRUE(Cxt.defineCmdlineVariables(GlobalDefines, SM)); + Cxt = FileCheckPatternContext(); + + // Invalid numeric value for numeric variable. + GlobalDefines.clear(); + GlobalDefines.emplace_back(std::string("#LocalNumVar=x")); + EXPECT_TRUE(Cxt.defineCmdlineVariables(GlobalDefines, SM)); // Define local variables from command-line. GlobalDefines.clear(); GlobalDefines.emplace_back(std::string("LocalVar=FOO")); GlobalDefines.emplace_back(std::string("EmptyVar=")); + GlobalDefines.emplace_back(std::string("#LocalNumVar=18")); bool GotError = Cxt.defineCmdlineVariables(GlobalDefines, SM); EXPECT_FALSE(GotError); // Check defined variables are present and undefined is absent. StringRef LocalVarStr = "LocalVar"; + StringRef LocalNumVarRef = bufferize(SM, "LocalNumVar"); StringRef EmptyVarStr = "EmptyVar"; StringRef UnknownVarStr = "UnknownVar"; llvm::Optional LocalVar = Cxt.getPatternVarValue(LocalVarStr); + FileCheckPattern P = FileCheckPattern(Check::CheckPlain, &Cxt, 1); + FileCheckNumericVariable *DefinedNumericVariable; + FileCheckNumExpr *NumExpr = + P.parseNumericExpression(LocalNumVarRef, DefinedNumericVariable, SM); llvm::Optional EmptyVar = Cxt.getPatternVarValue(EmptyVarStr); llvm::Optional UnknownVar = Cxt.getPatternVarValue(UnknownVarStr); EXPECT_TRUE(LocalVar); EXPECT_EQ(*LocalVar, "FOO"); + EXPECT_TRUE(NumExpr); + llvm::Optional NumExprVal = NumExpr->eval(); + EXPECT_TRUE(NumExprVal); + EXPECT_EQ(*NumExprVal, 18U); EXPECT_TRUE(EmptyVar); EXPECT_EQ(*EmptyVar, ""); EXPECT_FALSE(UnknownVar); @@ -235,21 +471,43 @@ Cxt.clearLocalVars(); LocalVar = Cxt.getPatternVarValue(LocalVarStr); EXPECT_FALSE(LocalVar); + // Check eval fails even if we kept a pointer to the numeric expression. + EXPECT_FALSE(NumExpr->eval()); + P = FileCheckPattern(Check::CheckPlain, &Cxt, 2); + NumExpr = + P.parseNumericExpression(LocalNumVarRef, DefinedNumericVariable, SM); + EXPECT_FALSE(NumExpr); EmptyVar = Cxt.getPatternVarValue(EmptyVarStr); EXPECT_FALSE(EmptyVar); // Redefine global variables and check variables are defined again. GlobalDefines.emplace_back(std::string("$GlobalVar=BAR")); + GlobalDefines.emplace_back(std::string("#$GlobalNumVar=36")); GotError = Cxt.defineCmdlineVariables(GlobalDefines, SM); EXPECT_FALSE(GotError); StringRef GlobalVarStr = "$GlobalVar"; + StringRef GlobalNumVarRef = bufferize(SM, "$GlobalNumVar"); llvm::Optional GlobalVar = Cxt.getPatternVarValue(GlobalVarStr); EXPECT_TRUE(GlobalVar); EXPECT_EQ(*GlobalVar, "BAR"); + P = FileCheckPattern(Check::CheckPlain, &Cxt, 3); + NumExpr = + P.parseNumericExpression(GlobalNumVarRef, DefinedNumericVariable, SM); + EXPECT_TRUE(NumExpr); + NumExprVal = NumExpr->eval(); + EXPECT_TRUE(NumExprVal); + EXPECT_EQ(*NumExprVal, 36U); // Clear local variables and check global variables remain defined. Cxt.clearLocalVars(); GlobalVar = Cxt.getPatternVarValue(GlobalVarStr); EXPECT_TRUE(GlobalVar); + P = FileCheckPattern(Check::CheckPlain, &Cxt, 4); + NumExpr = + P.parseNumericExpression(GlobalNumVarRef, DefinedNumericVariable, SM); + EXPECT_TRUE(NumExpr); + NumExprVal = NumExpr->eval(); + EXPECT_TRUE(NumExprVal); + EXPECT_EQ(*NumExprVal, 36U); } } // namespace