Index: llvm/docs/CommandGuide/FileCheck.rst =================================================================== --- llvm/docs/CommandGuide/FileCheck.rst +++ llvm/docs/CommandGuide/FileCheck.rst @@ -102,8 +102,15 @@ .. option:: -D - Sets a filecheck variable ``VAR`` with value ``VALUE`` that can be used in - ``CHECK:`` lines. + Sets a filecheck pattern variable ``VAR`` with value ``VALUE`` that can be + used in ``CHECK:`` lines. + +.. option:: -D#,= + + Sets a filecheck numeric variable ``VAR`` of matching format ``FMT`` with the + value of evaluating ``VALUE EXPRESSION`` that can be used in ``CHECK:`` + lines. See section ``FileCheck Numeric Variables and Expressions`` for + details on the format and meaning of and . .. option:: -version @@ -548,14 +555,14 @@ braces explicitly from the input, you can use something ugly like ``{{[{][{]}}`` as your pattern. -FileCheck Variables -~~~~~~~~~~~~~~~~~~~ +FileCheck Pattern Variables +~~~~~~~~~~~~~~~~~~~~~~~~~~~ It is often useful to match a pattern and then verify that it occurs again later in the file. For codegen tests, this can be useful to allow any register, but verify that that register is used consistently later. To do this, -:program:`FileCheck` allows named variables to be defined and substituted into -patterns. Here is a simple example: +:program:`FileCheck` allows pattern variables to be defined and substituted +into patterns. Here is a simple example: .. code-block:: llvm @@ -588,31 +595,126 @@ This makes it easier to ensure that individual tests are not affected by variables set in preceding tests. -FileCheck Expressions -~~~~~~~~~~~~~~~~~~~~~ +FileCheck Numeric Variables and Expressions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:program:`FileCheck` also allows to define numeric variables and check for +values satisfying a numeric expression constraint based on those variables. +This allows to capture numeric relations between two numbers in a text to +check, such as the need for consecutive registers to be used. + +The syntax to define a numeric variable is ``[[#%,:]]`` where: + +* ``%`` is an optional scanf-style matching format specifier to + indicate what number format to match (e.g. hex number). Currently accepted + format specifier are ``%u``, ``%d``, ``%x`` and ``%X``. If absent, the + format specifier defaults to ``%u``. + +* ``NUMVAR`` is the name of the numeric variable to define to the matching + value. + +For example: + +.. code-block:: llvm + + ; CHECK: mov r[[#REG:]], 0x[[#%X,IMM:]] + +would match ``mov r5, 0xF0F0`` and set ``REG`` to the value ``5`` and ``IMM`` +to the value ``61680``. + +The syntax to check a numeric expression constraint is +``[[#%: ]]`` where: + +* ``%`` is the same matching format specifier as for defining numeric + variables but acting as a printf-style format to indicate how a numeric + expression value should be matched against. If absent, the format specifier + is inferred from the matching format of the numeric variable(s) used by the + expression constraint if any and they are all the same, and default to ``%u`` + if no numeric variable is used. In case of conflict between matching formats + of several numeric variables the format specifier is mandatory. + +* ```` is the matching constraint describing how the value to match + must relate to the value of the numeric expression. The only currently + accepted constraint is ``==`` for an exact match and is the default if + ```` is not provided. -Sometimes there's a need to verify output which refers line numbers of the +* ``>`` is a numeric expression whose operands are either numeric + variables previously defined or integer immediates. Currently supported + numeric operations are ``+`` and ``-``. + +For example: + +.. code-block:: llvm + + ; CHECK: add r[[#REG:]], r[[#REG]], r[[#REG+1]] + ; CHECK: copying from 0x[[#%x,ADDR:] to 0x[[#ADDR + 8]] + +The above example would match the lines: + +.. code-block:: llvm + + add r2, r2, r3 + copying from 0xa0463440 to 0xa0463448 + +but would not match the lines: + +.. code-block:: llvm + + add r2, r2, r4 + copying from 0xa0463440 to 0xa0463444 + +Due to ``4`` being unequal to ``2 + 1`` and ``a0463444`` being unequal to +``a0463440 + 8``. + +A numeric variable can also be defined to the result of a numeric expression, +in which case the numeric expression constraint is checked and if verified the +variable is assigned to the value. The unified syntax for both defining numeric +variables and checking a numeric expression is thus +``[[#%,: ]]`` with each element as +described previously. + +In the same way as for pattern variables, ``--enable-var-scope`` only consider +global numeric variables that start with ``$`` and undefined local variables at +the beginning of each CHECK-LABEL block. + +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 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 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes there's a need to verify output which contain line numbers of the match file, e.g. when testing compiler diagnostics. This introduces a certain fragility of the match file structure, as "``CHECK:``" lines contain absolute 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+]]``, ``[[@LINE-]]`` expressions in patterns. These -expressions expand to a 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 evaluate 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: .. code-block:: c++ - // CHECK: test.cpp:[[@LINE+4]]:6: error: expected ';' after top level declarator + // CHECK: test.cpp:[[# @LINE + 4]]:6: error: expected ';' after top level declarator // CHECK-NEXT: {{^int a}} // CHECK-NEXT: {{^ \^}} // CHECK-NEXT: {{^ ;}} int a +To support legacy uses of ``@LINE`` as a special pattern variable, +:program:`FileCheck` also accepts the following uses of ``@LINE`` with pattern +variable syntax: ``[[@LINE]]``, ``[[@LINE+]]`` and +``[[@LINE-]]`` where ``offset`` is an integer immediate. + Matching Newline Characters ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Index: llvm/include/llvm/Support/FileCheck.h =================================================================== --- llvm/include/llvm/Support/FileCheck.h +++ llvm/include/llvm/Support/FileCheck.h @@ -36,9 +36,418 @@ bool VerboseVerbose = false; }; +//===----------------------------------------------------------------------===// +// Numeric expression handling code. +//===----------------------------------------------------------------------===// + +/// Bitfield representing the format a numeric expression value should be +/// printed into for matching. Used to represent both explicit format +/// specifiers as well as implicit format from using numeric variables. +struct FileCheckNumExprFmtType { + unsigned Signed : 1; + /// Value should be printed as hex number. + unsigned Hex : 1; + /// Value should be printed using upper case letters, only used for hex + /// numbers. + unsigned Cap : 1; + + /// When unset, denote absence of format and thus that all of the other + /// fields are to be ignored. Used for implicit format of literals and empty + /// expressions. + unsigned Valid : 1; + + /// If set, there are several conflicting implicit formats in an expression. + unsigned Conflict : 1; + + /// Define format equality: formats are equal if all bits are identical. + bool operator==(const FileCheckNumExprFmtType &other); + bool operator!=(const FileCheckNumExprFmtType &other) { + return !(*this == other); + } +}; + +/// Initializer for numeric expression without format. +const FileCheckNumExprFmtType FmtNone = {0, 0, 0, 0, 0}; +/// Initializer for numeric expression matched as unsigned value. +const FileCheckNumExprFmtType FmtUnsigned = {0, 0, 0, 1, 0}; +/// Initializer for numeric expression matched as signed value. +const FileCheckNumExprFmtType FmtSigned = {1, 0, 0, 1, 0}; +/// Initializer for numeric expression matched as lower case hex value. +const FileCheckNumExprFmtType FmtLowHex = {0, 1, 0, 1, 0}; +/// Initializer for numeric expression matched as capital case hex value. +const FileCheckNumExprFmtType FmtCapHex = {0, 1, 1, 1, 0}; + +/// Phases of FileCheck. Used to determine at what phase a numeric expression +/// is available (and thus value of any potential variable defined by it). +enum class FileCheckPhase { + /// Parsing the CHECK pattern. + ParsePhase = 0, + /// Pattern substitution and actual regex matching + MatchPhase, + /// Constraint check done for numeric expression just after match phase. + CheckPhase +}; + +/// Class representing a numeric expression's value. +class FileCheckNumExprVal { +private: + union { + int64_t SignedValue; + uint64_t UnsignedValue; + }; + + /// Whether value is signed (and thus is stored in SignedValue) or not (in + /// which case it is stored in UnsignedValue). + bool Signed; + + /// 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 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. + FileCheckNumExprVal(int64_t Val) + : SignedValue(Val), Signed(true), Valid(true), Tentative(false) {} + + /// Constructor for an unsigned value. + FileCheckNumExprVal(uint64_t Val) + : 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. + bool operator==(const FileCheckNumExprVal &other); + bool operator!=(const FileCheckNumExprVal &other) { + return !(*this == other); + } + + bool IsSigned() const { return Signed; } + + 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 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 { + assert(Signed); + return SignedValue; + } + + /// Return the unsigned value. Must only be called if value is unsigned in + /// the first place. + uint64_t GetUnsignedValue() const { + assert(!Signed); + return UnsignedValue; + } + + 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(); + + /// Convert value to an unsigned value, or mark value invalid if not possible + /// (original value was not within range for an unsigned integer). + void ConvertUnsigned(); + + /// Return a string representation of this value given the matching format + /// \p Fmt or nothing if value had no such a string representation (e.g. if + /// value is invalid). + llvm::Optional GetStringRepr(FileCheckNumExprFmtType Fmt) const; + + /// Return an invalid value in case of underflow or overflow. + friend FileCheckNumExprVal operator+(const FileCheckNumExprVal &lhs, + const FileCheckNumExprVal &rhs); + friend FileCheckNumExprVal operator-(const FileCheckNumExprVal &lhs, + const FileCheckNumExprVal &rhs); +}; + +/// Base class representing the AST of a given numeric expression. +class FileCheckNumExprAST { +public: + virtual ~FileCheckNumExprAST() = default; + + /// Evaluate the value of the expression represented by this AST. Return in + /// \p Fmt the implicit conversion that applies from any variable used or + /// an invalid value if no variable is used by the expression. + virtual FileCheckNumExprVal Eval(FileCheckNumExprFmtType &Fmt) = 0; + + /// Append names of undefined variables used in the expression represented by + /// this AST. Must be overriden in any subclass representing an expression + /// that can contain a variable. + virtual void AddUndefVarNames(std::vector &UndefVarNames) const {} +}; + +/// Class representing a literal, either in the AST of a numeric expression or +/// in the result of the evaluation of such an expression. +class FileCheckNumExprLiteral : public FileCheckNumExprAST { +private: + /// Actual value of the literal. + FileCheckNumExprVal Value; + +public: + /// Constructor for a signed literal. + FileCheckNumExprLiteral(int64_t Val) : Value(Val) {} + + /// Constructor for an unsigned literal. + FileCheckNumExprLiteral(uint64_t Val) : Value(Val) {} + + /// Return the node itself and set \p Fmt to not valid since literals do not + /// carry any implicit conversion. + FileCheckNumExprVal Eval(FileCheckNumExprFmtType &Fmt); +}; + +/// Class representing a numeric expression and its matching format. +class FileCheckNumExpr { +private: + /// Pointer to AST of the numeric expression. + std::shared_ptr AST; + + /// Value of the numeric expression. Set either by evaluating the AST if any + /// and using only variable defined on previous CHECK line, or by the matched + /// value if constraint is satisfied. + FileCheckNumExprVal Value; + + /// Matching format, i.e format (eg. hex upper case letters) to use for the + /// value when matching it. + FileCheckNumExprFmtType Fmt; + + /// FileCheck phase when this numeric expression can be evaluated. + FileCheckPhase KnownPhase; + +public: + /// Generic constructor for a numeric expression whose equality constraint is + /// represented by \p AST, matching format is \p Fmt and whose AST can be + /// evaluated in phase \p KnownPhase. + FileCheckNumExpr(std::shared_ptr AST, + FileCheckNumExprFmtType Fmt, FileCheckPhase KnownPhase); + + /// Constructor for a numeric expression with a known value in parse phase, + /// e.g. the numeric expression defining the @LINE numeric variable + FileCheckNumExpr(FileCheckNumExprVal Value, FileCheckNumExprFmtType Fmt); + + /// Return 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(); } + + /// Return the value being matched against if invoked at or before match + /// phase and value successfully matched at check phase. + FileCheckNumExprVal GetValue() const { return Value; } + + /// Set Value to \p Val if currently invalid or tentative. Return whether + /// Value failed to be set (Value was already valid and final). + bool SetTentativeValue(FileCheckNumExprVal Val); + + /// Return matching format of the expression. + FileCheckNumExprFmtType GetFormat() const { return Fmt; } + + /// Return at what FileCheck phase this expression can be evaluated. + FileCheckPhase GetKnownPhase() { return KnownPhase; } + + /// Return the value corresponding to string representation \p StrVal + /// according to matching format of this numeric expression or nothing if + /// \p StrVal correspond to a valid and representable value. + llvm::Optional + ValueFromStringRepr(StringRef StrVal) const; + + /// Return the string representation of Value given the matching format of + /// this numeric expression or nothing if Value does not have a string + /// representation (e.g. Value is invalid). + llvm::Optional GetValueStringRepr() const { + return Value.GetStringRepr(Fmt); + } + + /// Return the regexp pattern to use to match this numeric expression given + /// its matching format or nothing if the function was unable to determine + /// the regexp pattern. + llvm::Optional GetMatchString(FileCheckPhase Phase); + + /// 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; + + /// Set Value to the evaluation of the constraint of this numeric expression. + /// Return whether evaluation failed. + bool SetValueFromEval(); + + /// Mark value as final. Return whether value was already final. + bool CommitValue(); +}; + +/// Class representing a numeric variable with a given value in the AST of a +/// numeric expression. Each definition of a variable gets its own instance of +/// this class, uses share the same instance as the respective definition. +class FileCheckNumExprVar : public FileCheckNumExprAST { +private: + /// Name of the numeric variable. + StringRef Name; + + /// Pointer to numeric expression defining this numeric variable. Only null + /// if variable used but not defined. If numeric expression is empty NumExpr + /// points to a FileCheckNumExpr with a null AST. + std::shared_ptr NumExpr; + + /// 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 an undefined variable \p Name. + FileCheckNumExprVar(StringRef Name) + : Name(Name), NumExpr(nullptr), DefLineNumber(0) {} + + /// Constructor for a variable \p Name defined at line \p DefLineNumber. + FileCheckNumExprVar(StringRef Name, unsigned DefLineNumber) + : Name(Name), NumExpr(nullptr), DefLineNumber(DefLineNumber) {} + + /// Constructor for numeric variable \p Name with a known \p Value at parse + /// time (e.g. the @LINE numeric variable). Matching format of the variable + /// is given in \p Fmt and \p DefLineNumber indicates when does this variabler + /// starts to be defined. + FileCheckNumExprVar(StringRef Name, FileCheckNumExprVal Value, + FileCheckNumExprFmtType Fmt, unsigned DefLineNumber); + + /// Return name of that numeric variable. + StringRef GetName() const { return Name; } + + /// Return pointer to the numeric expression defining this numeric variable. + FileCheckNumExpr *GetNumExpr() const { return NumExpr.get(); } + + /// Return line number where this variable is defined. + unsigned GetDefLineNumber() { return DefLineNumber; } + + /// Set pointer to numeric expression defining this numeric variable. + void SetNumExpr(std::shared_ptr NumExpr) { + this->NumExpr = NumExpr; + } + + /// Evaluate the value of this numeric variable. Therefore returns the value + /// and implicit conversion of this numeric variable stored in its + /// corresponding numeric expression class. + FileCheckNumExprVal Eval(FileCheckNumExprFmtType &Fmt); + + /// Append numeric variable's name to UndefVarNames if undefined. + void AddUndefVarNames(std::vector &UndefVarNames) const; +}; + +/// Type of functions evaluating a given binary operation. +using binop_eval_t = FileCheckNumExprVal (*)(const FileCheckNumExprVal &, + const FileCheckNumExprVal &); + +/// Class representing a single binary operation in the AST of a numeric +/// expression. +class FileCheckASTBinop : public FileCheckNumExprAST { +private: + /// Left operand. + std::shared_ptr Opl; + + /// Right operand. + std::shared_ptr Opr; + + /// Pointer to function that can evaluate this binary operation. + binop_eval_t EvalBinop; + +public: + FileCheckASTBinop(binop_eval_t EvalBinop, + std::shared_ptr OperandLeft, + std::shared_ptr OperandRight) + : Opl(OperandLeft), Opr(OperandRight), EvalBinop(EvalBinop) {} + + /// Evaluate the value of the binary operation represented by this AST. Uses + /// EvalBinop to perform the binary operation on the values of recursively + /// evaluating the left and right operands. + FileCheckNumExprVal Eval(FileCheckNumExprFmtType &Fmt); + + /// Append to UnderVarNames the names of undefined numeric variables used in + /// any of the operands. + void AddUndefVarNames(std::vector &UndefVarNames) const; +}; + +class FileCheckPatternContext; + +/// Class representing a substitution to perform in the string to match, either +/// a pattern variable to replace by its value or a numeric expression to +/// replace by an appropriate wildcard pattern. +class FileCheckPatternSubst { +private: + /// Pointer to a class instance holding among other thing the table with the + /// values of live pattern variables at the start of any given CHECK line. + /// Used for substituting pattern variables (numeric variables have their + /// value in the FileCheckNumExpr class instance pointed to by NumExpr). + FileCheckPatternContext *Context; + + /// Whether this represents a numeric expression substitution. + bool isNumExpr; + + /// The string that needs to be substituted for something else. For a + /// pattern variable this is its name, otherwise this is the whole numeric + /// expression. + StringRef SubstStr; + + /// If this is a numeric expression substitution, this is the pointer to the + /// class representing that numeric expression. + std::shared_ptr NumExpr; + + // Index in RegExStr of where to do the substitution. + unsigned InsertIdx; + +public: + /// Constructor for a pattern variable substitution. + FileCheckPatternSubst(FileCheckPatternContext *Context, StringRef VarName, + unsigned InsertIdx) + : Context(Context), isNumExpr(false), SubstStr(VarName), + InsertIdx(InsertIdx) {} + + /// Constructor for a numeric expression substitution. + FileCheckPatternSubst(FileCheckPatternContext *Context, StringRef Expr, + std::shared_ptr NumExpr, + unsigned InsertIdx) + : Context(Context), isNumExpr(true), SubstStr(Expr), NumExpr(NumExpr), + InsertIdx(InsertIdx) {} + + /// Return whether this is a numeric expression substitution. + bool IsNumExpr() const { return isNumExpr; } + + /// Return the string to be substituted. + StringRef GetSubstString() const { return SubstStr; } + + /// Return the index where the substitution is to be performed. + unsigned GetIndex() const { return InsertIdx; } + + /// Return the result of the substitution represented by this class instance + /// or nothing if substitution failed. For a numeric variable we substitute + /// it by its value if known at match time or a suitable wildcard pattern + /// otherwise. For a pattern variable we simply replace it by the text its + /// definition matched. + llvm::Optional GetSubstitute() const; + + /// Return the value successfully matched (ie. constraint is verified for a + /// numeric expression) by the substitution or nothing if match failed for + /// that substitution. + llvm::Optional MatchedValue() const; + + /// Append to UnderVarNames the names of undefined pattern or numeric + /// variables used in this substitution. + void GetUndefVarNames(std::vector &UndefVarNames) const; +}; //===----------------------------------------------------------------------===// -// Pattern Handling Code. +// Pattern handling code. //===----------------------------------------------------------------------===// namespace Check { @@ -79,7 +488,61 @@ std::string getDescription(StringRef Prefix) const; }; -} +} // namespace Check + +/// 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 expression and its matching format. +struct FileCheckNumExprMatch { + /// Pointer to class representing the numeric expression and its matching + /// format. + std::shared_ptr NumExpr; + + /// Parenthesized capture number for this numeric expression + 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. +class FileCheckPatternContext { + friend class FileCheckPattern; + +private: + /// When matching a given pattern, this holds the value of all the FileCheck + /// pattern variables defined in previous patterns. In a pattern only the + /// last definition for a given variable is recorded in this table, + /// 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 AST of all numeric variables defined in previous + /// patterns along with their values. In a pattern only the last definition + /// for a given variable is recorded in this table. When matching a pattern + /// all definitions for that pattern are recorded in NumericVariableDefs + /// table. + StringMap> GlobalNumericVariableTable; + +public: + /// Return the value of pattern variable \p VarName or nothing if no such + /// variable has been defined. + llvm::Optional GetPatternVarValue(StringRef VarName); + + /// Define pattern and numeric variables from definitions given on the + /// command line passed as a vector of VAR=VAL strings in \p CmdlineDefines. + /// Report any error to \p SM. + bool DefineCmdlineVariables(std::vector &CmdlineDefines, + SourceMgr &SM); + + /// Undefine local variables (variables whose name does not start with a '$' + /// sign), ie remove them from GlobalVariableTable. + void ClearLocalVars(void); +}; struct FileCheckDiag; @@ -94,43 +557,81 @@ /// a fixed string to match. std::string RegExStr; - /// Entries in this vector map to uses of a variable in the pattern, e.g. - /// "foo[[bar]]baz". In this case, the RegExStr will contain "foobaz" and - /// we'll get an entry in this vector that tells us to insert the value of - /// bar at offset 3. - std::vector> VariableUses; - - /// Maps definitions of variables to their parenthesized capture numbers. + /// 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[[#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 FileCheckPatternSubst class to abstract whether it is a + /// pattern variable or a numeric expression. + std::vector MatchSubsts; + + /// Entries in this vector represent any use of a pattern variable or a + /// numeric expression in the pattern. Substitution are represented in the + /// same way as in MatchSubsts above. + std::vector AllSubsts; + + /// Maps names of pattern variables defined in a pattern to the parenthesized + /// capture numbers of their last definition. + /// + /// E.g. for the pattern "foo[[bar:.*]]baz[[bar]]quux[[bar:.*]]", + /// VariableDefs will map "bar" to 2 corresponding to the second definition + /// of "bar". /// - /// E.g. for the pattern "foo[[bar:.*]]baz", VariableDefs will map "bar" to - /// 1. + /// Note: uses std::map rather than StringMap to be able to get the key when + /// iterating over values. std::map VariableDefs; + /// Holds the capture parentheses number and pointer to corresponding + /// FileCheckNumExpr class instance of all numeric expressions whose matching + /// result must be captured, that is numeric expressions with a variable + /// definition or using numeric variable defined on the same line. Used to + /// record the matched value of all those expressions and for those with a + /// constraint to check that the matched value satisfies it. + std::vector CapturedNumericExpressions; + + /// 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) - : CheckTy(Ty) {} + explicit FileCheckPattern(Check::FileCheckType Ty, + FileCheckPatternContext *Context) + : Context(Context), CheckTy(Ty) {} /// Returns the location in source code. SMLoc getLoc() const { return PatternLoc; } + /// Returns the pointer to the global state for all patterns in this + /// FileCheck instance. + FileCheckPatternContext *GetContext() const { return Context; } + + bool ParseVariable(StringRef Str, bool &IsPseudo, unsigned &TrailIdx) const; + std::shared_ptr + ParseNumericExpression(StringRef Expr, + std::shared_ptr &NumVarDef, + const SourceMgr &SM) const; bool ParsePattern(StringRef PatternStr, StringRef Prefix, SourceMgr &SM, unsigned LineNumber, const FileCheckRequest &Req); - size_t Match(StringRef Buffer, size_t &MatchLen, - StringMap &VariableTable) const; - void PrintVariableUses(const SourceMgr &SM, StringRef Buffer, - const StringMap &VariableTable, - SMRange MatchRange = None) const; + size_t Match(StringRef Buffer, size_t &MatchLen, const SourceMgr &SM) const; + void PrintSubsts(const SourceMgr &SM, StringRef Buffer, + SMRange MatchRange = None) const; void PrintFuzzyMatch(const SourceMgr &SM, StringRef Buffer, - const StringMap &VariableTable, std::vector *Diags) const; bool hasVariable() const { - return !(VariableUses.empty() && VariableDefs.empty()); + return !(AllSubsts.empty() && VariableDefs.empty()); } Check::FileCheckType getCheckTy() const { return CheckTy; } @@ -140,11 +641,24 @@ private: bool AddRegExToRegEx(StringRef RS, unsigned &CurParen, SourceMgr &SM); void AddBackrefToRegEx(unsigned BackrefNum); - unsigned - ComputeMatchDistance(StringRef Buffer, - const StringMap &VariableTable) const; - bool EvaluateExpression(StringRef Expr, std::string &Value) const; + unsigned ComputeMatchDistance(StringRef Buffer) const; size_t FindRegexVarEnd(StringRef Str, SourceMgr &SM); + + /// Numeric expression parsing helpers. + std::shared_ptr + ParseNumericVariable(StringRef &Expr, bool IsDefinition, + const SourceMgr &SM) const; + std::shared_ptr + ParseNumericOperand(StringRef &Expr, const SourceMgr &SM, + FileCheckNumExprFmtType &ImplicitFmt, + FileCheckPhase &KnownPhase) const; + std::shared_ptr + ParseFileCheckBinop(StringRef &Expr, std::shared_ptr Opl, + const SourceMgr &SM, FileCheckNumExprFmtType &ImplicitFmt, + FileCheckPhase &KnownPhase) const; + std::shared_ptr + ParseLegacyNumericExpression(StringRef Expr, StringRef Name, + StringRef Trailer, const SourceMgr &SM) const; }; //===----------------------------------------------------------------------===// @@ -223,19 +737,17 @@ : Pat(P), Prefix(S), Loc(L) {} size_t Check(const SourceMgr &SM, StringRef Buffer, bool IsLabelScanMode, - size_t &MatchLen, StringMap &VariableTable, - FileCheckRequest &Req, std::vector *Diags) const; + size_t &MatchLen, FileCheckRequest &Req, + std::vector *Diags) const; bool CheckNext(const SourceMgr &SM, StringRef Buffer) const; bool CheckSame(const SourceMgr &SM, StringRef Buffer) const; bool CheckNot(const SourceMgr &SM, StringRef Buffer, const std::vector &NotStrings, - StringMap &VariableTable, const FileCheckRequest &Req, std::vector *Diags) const; size_t CheckDag(const SourceMgr &SM, StringRef Buffer, std::vector &NotStrings, - StringMap &VariableTable, const FileCheckRequest &Req, std::vector *Diags) const; }; Index: llvm/lib/Support/FileCheck.cpp =================================================================== --- llvm/lib/Support/FileCheck.cpp +++ llvm/lib/Support/FileCheck.cpp @@ -24,6 +24,806 @@ using namespace llvm; +/// Define format equality: formats are equal if all bits are identical. +bool FileCheckNumExprFmtType::operator==(const FileCheckNumExprFmtType &other) { + return Valid == other.Valid && Conflict == other.Conflict && + Signed == other.Signed && Hex == other.Hex && Cap == other.Cap; +} + +/// Equality operator: 2 values are equal if both are valid and have same +/// signedness and corresponding value. +bool FileCheckNumExprVal::operator==(const FileCheckNumExprVal &other) { + if (!Valid || !other.Valid) + return false; + + if (Signed) + return SignedValue == other.SignedValue; + else + return UnsignedValue == other.UnsignedValue; +} + +/// Convert value to a signed value, or mark value invalid if not possible +/// (original value was not within range for a signed integer). +void FileCheckNumExprVal::ConvertSigned() { + if (!Valid || Signed) + return; + + if (UnsignedValue > std::numeric_limits::max()) { + Valid = false; + return; + } + + SignedValue = UnsignedValue; + Signed = true; +} + +/// Convert value to an unsigned value, or mark value invalid if not possible +/// (original value was not within range for an unsigned integer). +void FileCheckNumExprVal::ConvertUnsigned() { + if (!Valid || !Signed) + return; + + if (SignedValue < 0) { + Valid = false; + return; + } + + UnsignedValue = SignedValue; + Signed = false; + return; +} + +/// Return a string representation of this value given the matching format +/// \p Fmt or nothing if value had no such a string representation (e.g. if +/// value is invalid). +llvm::Optional +FileCheckNumExprVal::GetStringRepr(FileCheckNumExprFmtType Fmt) const { + if (!Valid) + return llvm::None; + + if (Fmt.Hex) { + return utohexstr(GetUnsignedValue(), !Fmt.Cap); + } else if (Fmt.Signed) + return itostr(GetSignedValue()); + else + return utostr(GetUnsignedValue()); +} + +/// Return an invalid value in case of underflow or overflow. +llvm::FileCheckNumExprVal llvm:: +operator+(const llvm::FileCheckNumExprVal &Op1, + const llvm::FileCheckNumExprVal &Op2) { + // Operands must be valid. + if (!Op1.Valid || !Op2.Valid) + return FileCheckNumExprVal(); + + // Operands must have same sign. + if (Op1.Signed != Op2.Signed) + return FileCheckNumExprVal(); + + if (Op1.Signed) { + int64_t Val1 = Op1.SignedValue; + int64_t Val2 = Op2.SignedValue; + + // Op1 + Op2 > max int64_t. + if (Val1 > 0 && Val2 > 0 && + Val1 > (std::numeric_limits::max() - Val2)) + return FileCheckNumExprVal(); + + // Op1 + Op2 < min int64_t. + if (Val1 < 0 && Val2 < 0 && + Val1 < (std::numeric_limits::min() - Val2)) + return FileCheckNumExprVal(); + + return FileCheckNumExprVal(Val1 + Val2); + } else { + uint64_t Val1 = Op1.UnsignedValue; + uint64_t Val2 = Op2.UnsignedValue; + + // Op1 + Op2 > max uint64_t. + if (Val1 > std::numeric_limits::max() - Val2) + return FileCheckNumExprVal(); + + return FileCheckNumExprVal(Val1 + Val2); + } +} + +/// Return an invalid value in case of underflow or overflow. +llvm::FileCheckNumExprVal llvm:: +operator-(const llvm::FileCheckNumExprVal &Op1, + const llvm::FileCheckNumExprVal &Op2) { + // Operands must be valid. + if (!Op1.Valid || !Op2.Valid) + return FileCheckNumExprVal(); + + // Operands must have same sign. + if (Op1.Signed != Op2.Signed) + return FileCheckNumExprVal(); + + if (Op1.Signed) { + int64_t Val1 = Op1.SignedValue; + int64_t Val2 = Op2.SignedValue; + + // Op1 - Op2 > max int64_t. + if (Val1 > 0 && Val2 < 0 && + Val1 > (std::numeric_limits::max() + Val2)) + return FileCheckNumExprVal(); + + // Op1 - Op2 < min int64_t. + if (Val1 < 0 && Val2 > 0 && + Val1 < (std::numeric_limits::min() + Val2)) + return FileCheckNumExprVal(); + + return FileCheckNumExprVal(Val1 - Val2); + } else { + uint64_t Val1 = Op1.UnsignedValue; + uint64_t Val2 = Op2.UnsignedValue; + + // Op1 < Op2. + if (Val1 < Val2) + return FileCheckNumExprVal(); + return FileCheckNumExprVal(Val1 - Val2); + } +} + +/// Evaluate the value of this literal. Therefore returns this node itself and +/// set \p Fmt to null since literals do not carry any implicit conversion. +FileCheckNumExprVal +FileCheckNumExprLiteral::Eval(FileCheckNumExprFmtType &Fmt) { + Fmt = FmtNone; + return Value; +} + +/// Generic constructor for a numeric expression whose equality constraint is +/// represented by \p AST, matching format is \p Fmt and whose AST can be +/// evaluated in phase \p KnownPhase. If matching format is unset (ie. no +/// explicit or implicit matching format), set it to default one (unsigned +/// decimal integer). +FileCheckNumExpr::FileCheckNumExpr(std::shared_ptr AST, + FileCheckNumExprFmtType Fmt, + FileCheckPhase KnownPhase) + : AST(AST), KnownPhase(KnownPhase) { + if (Fmt.Valid) + this->Fmt = Fmt; + else + this->Fmt = FmtUnsigned; +} + +/// Constructor for numeric expression with a known value in parse phase, +/// e.g. the numeric expression defining the @LINE numeric variable (and +/// currently only used for that). +FileCheckNumExpr::FileCheckNumExpr(FileCheckNumExprVal Value, + FileCheckNumExprFmtType Fmt) + : FileCheckNumExpr(nullptr, Fmt, FileCheckPhase::ParsePhase) { + this->Value = Value; + this->KnownPhase = FileCheckPhase::ParsePhase; +} + +/// Set Value to \p Val if currently invalid or tentative. Return whether +/// Value failed to be set (Value was already valid and final). +bool FileCheckNumExpr::SetTentativeValue(FileCheckNumExprVal Val) { + // Allow a value to be tentatively set several time. + if (Value.IsValid() && !Value.IsTentative()) + return true; + + // Value should be set from evaluating the AST if any. + if (AST != nullptr) + return true; + + assert(!Val.IsTentative() && "Setting from already tentative matched value"); + Val.ToggleTentative(); + Value = Val; + return false; +} + +/// Return the value corresponding to string representation \p StrVal according +/// to matching format of this numeric expression or nothing if \p StrVal +/// correspond to a valid and representable value. +llvm::Optional +FileCheckNumExpr::ValueFromStringRepr(StringRef StrVal) const { + unsigned Radix = Fmt.Hex ? 16 : 10; + if (Fmt.Signed) { + int64_t SignedValue; + + if (StrVal.getAsInteger(Radix, SignedValue)) + return llvm::None; + + return FileCheckNumExprVal(SignedValue); + } else { + uint64_t UnsignedValue; + + if (StrVal.getAsInteger(Radix, UnsignedValue)) + return llvm::None; + + return FileCheckNumExprVal(UnsignedValue); + } +} + +/// Return the regexp pattern to use to match this numeric expression given its +/// matching format or nothing if the function was unable to determine the +/// regexp pattern. +llvm::Optional +FileCheckNumExpr::GetMatchString(FileCheckPhase Phase) { + assert(Phase <= FileCheckPhase::MatchPhase && + "Asking match string after match done"); + + // We know the value, return its string representation. If supporting other + // constraints than equality this should be restricted to equality only. + if (Phase >= KnownPhase) { + if (!Value.IsValid()) + return llvm::None; + + return Value.GetStringRepr(Fmt); + // We do not know the value but we will at match time. + } else if (KnownPhase <= FileCheckPhase::MatchPhase) { + return std::string(); + // We do not know the value and will not know it at match time. Output + // appropriate wildcard pattern. + } else { + assert(Fmt.Valid && !(Fmt.Hex && Fmt.Signed) && + "Numeric expression with unexpected conversion"); + if (Fmt.Hex) { + if (Fmt.Cap) + return std::string("[[:digit:]A-F]+"); + else + return std::string("[[:digit:]a-f]+"); + } else if (Fmt.Signed) + return std::string("-?[[:digit:]]+"); + else + return std::string("[[:digit:]]+"); + } +} + +/// 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 Value or MatchedValue are invalid. + return MatchedValue != Value; +} + +/// Set Value to the evaluation of the constraint of this numeric expression. +/// Return whether evaluation failed. +bool FileCheckNumExpr::SetValueFromEval() { + // No need to evaluate, we already have a final value. + if (Value.IsValid() && !Value.IsTentative()) + return true; + + // Nothing to evaluate: numeric variable definition with empty numeric + // expression. + if (AST == nullptr) + return true; + + FileCheckNumExprFmtType ImplicitFmt; + FileCheckNumExprVal EvaluatedValue = AST->Eval(ImplicitFmt); + if (!EvaluatedValue.IsValid()) + return true; + + // If expression comes with an explicit printing format different from the + // implicit one, signedness of EvaluatedValue could be different from the + // matched value. Convert to signedness of expression to ensure value + // compare equally. + if (Fmt.Signed) + EvaluatedValue.ConvertSigned(); + else + EvaluatedValue.ConvertUnsigned(); + if (!EvaluatedValue.IsValid()) + return true; + + Value = EvaluatedValue; + return false; +} + +/// Mark value as final. Return whether value was already final. +bool FileCheckNumExpr::CommitValue() { + // Value is not a valid tentative value. + if (!Value.IsValid() || !Value.IsTentative()) + return true; + + Value.ToggleTentative(); + return false; +} + +/// Constructor for numeric variable \p Name with a known \p Value at parse +/// time (e.g. the @LINE numeric variable). Matching format of the variable is +/// given in \p Fmt and \p DefLineNumber indicates when does this variable +/// starts to be defined. +FileCheckNumExprVar::FileCheckNumExprVar(StringRef Name, + FileCheckNumExprVal Value, + FileCheckNumExprFmtType Fmt, + unsigned DefLineNumber) + : Name(Name), DefLineNumber(DefLineNumber) { + this->NumExpr = std::make_shared(Value, Fmt); +} + +/// Evaluate the value of this numeric variable. Therefore returns the value +/// and implicit conversion of this numeric variable stored in its +/// corresponding numeric expression class. +FileCheckNumExprVal FileCheckNumExprVar::Eval(FileCheckNumExprFmtType &Fmt) { + // Undefined variable. + if (NumExpr == nullptr) + return FileCheckNumExprVal(); + Fmt = NumExpr->GetFormat(); + return NumExpr->GetValue(); +} + +/// Append numeric variable's name to UndefVarNames if undefined. +void FileCheckNumExprVar::AddUndefVarNames( + std::vector &UndefVarNames) const { + if (NumExpr == nullptr) + UndefVarNames.emplace_back(Name); +} + +/// Evaluate the value of the binary operation represented by this AST. Uses +/// EvalBinop to perform the binary operation on the values of recursively +/// evaluating the left and right operands. +FileCheckNumExprVal FileCheckASTBinop::Eval(FileCheckNumExprFmtType &Fmt) { + FileCheckNumExprFmtType LFmt, RFmt; + FileCheckNumExprVal LVal = Opl->Eval(LFmt); + FileCheckNumExprVal RVal = Opr->Eval(RFmt); + + // Integer promotion. + if (!LVal.IsSigned() || !RVal.IsSigned()) { + LVal.ConvertUnsigned(); + RVal.ConvertUnsigned(); + } + + if (!LVal.IsValid() || !RVal.IsValid()) + return FileCheckNumExprVal(); + + Fmt = (LFmt.Valid) ? LFmt : RFmt; + if (LFmt.Valid && RFmt.Valid && LFmt != RFmt) + Fmt.Conflict = 1; + + FileCheckNumExprVal Val = EvalBinop(LVal, RVal); + + // Mark result as tentative if any of the operand was tentative. Assert + // result was not tentative beforehand to have that check only here. + assert((!Val.IsValid() || !Val.IsTentative()) && + "Binary operation returned tentative value"); + if (Val.IsValid() && (LVal.IsTentative() || RVal.IsTentative())) + Val.ToggleTentative(); + return Val; +} + +/// Append to UnderVarNames the names of undefined numeric variables used in +/// any of the operands. +void FileCheckASTBinop::AddUndefVarNames( + std::vector &UndefVarNames) const { + Opl->AddUndefVarNames(UndefVarNames); + Opr->AddUndefVarNames(UndefVarNames); +} + +/// Return the result of the substitution represented by this class instance or +/// nothing if substitution failed. For a numeric variable we substitute it by +/// its value if known at match time or a suitable wildcard pattern otherwise. +/// For a pattern variable we simply replace it by the text its definition +/// matched. +llvm::Optional FileCheckPatternSubst::GetSubstitute() const { + if (isNumExpr) { + if (NumExpr->GetKnownPhase() == FileCheckPhase::MatchPhase) { + if (NumExpr->SetValueFromEval()) + return llvm::None; + } + return NumExpr->GetMatchString(FileCheckPhase::MatchPhase); + } else { + // Look up the value and escape it so that we can put it into the + // regex. + llvm::Optional OptVarVal; + OptVarVal = Context->GetPatternVarValue(SubstStr); + if (!OptVarVal.hasValue()) + return llvm::None; + return Regex::escape(OptVarVal.getValue()); + } +} + +/// Return the value successfully matched (ie. constraint is verified for a +/// numeric expression) by the substitution or nothing if match failed for that +/// substitution. +llvm::Optional FileCheckPatternSubst::MatchedValue() const { + if (isNumExpr) { + // This will return true if NumExpr's value is invalid, which it will be if + // NumExpr did not match or the matched value did not satisfy the + // expression constraint. + return NumExpr->GetValueStringRepr(); + } else { + // Look up the value and escape it so that we can put it into the + // regex. + llvm::Optional OptVarVal; + OptVarVal = Context->GetPatternVarValue(SubstStr); + if (!OptVarVal.hasValue()) + return llvm::None; + return Regex::escape(OptVarVal.getValue()); + } +} + +/// Append to UnderVarNames the names of undefined pattern or numeric variables +/// used in this substitution. +void FileCheckPatternSubst::GetUndefVarNames( + std::vector &UndefVarNames) const { + if (isNumExpr) + NumExpr->GetAST()->AddUndefVarNames(UndefVarNames); + else { + if (!Context->GetPatternVarValue(SubstStr).hasValue()) + UndefVarNames.emplace_back(SubstStr); + } +} + +/// Parsing helper function that strips all leading whitespace from \p s. +static inline void SkipWhitespace(StringRef &s) { s = s.ltrim(" \t"); } + +/// Parsing helper function that strips the string in \p SkipStr from \p s. +/// Returns true if string \p SkipStr was not in \p s and \p Optional was +/// false. Returns false otherwise. +static bool Skip(StringRef &s, const StringRef &SkipStr, bool Optional) { + if (!s.consume_front(SkipStr) && !Optional) + return true; + return false; +} + +/// Parsing helper function that strips the first character in \p s and returns +/// it. +static char Next(StringRef &s) { + char c = s.front(); + s = s.drop_front(); + return c; +} + +// Verify that the string in \p Str or at the start of \p Str (if \p +// AllowTrailer is true) is a well formed variable name. Also set IsPseudo to +// true if it is a pseudo variable. +bool FileCheckPattern::ParseVariable(StringRef Str, bool &IsPseudo, + unsigned &TrailIdx) const { + bool ParsedOneChar = false; + unsigned i = 0; + IsPseudo = false; + for (unsigned e = Str.size(); i != e; ++i) { + if (i == 0) { + // Global vars start with '$'. + if (Str[i] == '$') + continue; + else if (Str[i] == '@') { + IsPseudo = true; + continue; + } + } + // Variable names are composed of alphanumeric characters and underscores. + if (Str[i] != '_' && !isalnum(Str[i])) + break; + ParsedOneChar = true; + } + + // Empty name. + if (!ParsedOneChar) + return true; + + TrailIdx = i; + StringRef Name = Str.substr(0, i); + + // Name can't start with a digit. + if (isdigit(static_cast(Name[0]))) + return true; + + return false; +} + +/// Parse \p Expr for use or definition of a numeric variable. Return the class +/// instance representing the corresponding variable definition to be used in +/// the numeric expression AST in the case of a use, or nullptr if parsing +/// fails in which case errors are reported on \p SM. If \p IsDefinitions is +/// true, a new instance is created. Likewise in case of use if the variable is +/// undefined at that point in the pattern. +std::shared_ptr +FileCheckPattern::ParseNumericVariable(StringRef &Expr, bool IsDefinition, + const SourceMgr &SM) const { + bool IsPseudo; + unsigned TrailIdx; + + if (ParseVariable(Expr, IsPseudo, TrailIdx)) + return nullptr; + StringRef Name = Expr.substr(0, TrailIdx); + Expr = Expr.substr(TrailIdx); + + if (IsPseudo && !Name.equals("@LINE")) { + SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, + "Unsupported pseudo variable '" + Name + "'"); + return nullptr; + } + if (IsDefinition && IsPseudo) { + SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, + "Definition of pseudo variable '" + Name + "' unsupported"); + return nullptr; + } + + if (IsDefinition) { + // Detect collision between pattern and numeric variable when the latter + // is created on a later line than the former. + if (Context->DefinedVariableTable.find(Name) != + Context->DefinedVariableTable.end()) { + SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, + "Pattern variable with same name '" + Name + "' exists"); + return nullptr; + } + return std::make_shared(Name, this->LineNumber); + } else { + // This method is indirectly called from ParsePattern for all numeric + // variable definition and uses in the order in which they appear in the + // CHECK pattern. For each definition, the pointer to the corresponding AST + // class instance is stored in GlobalNumericVariableTable. Therefore the + // pointer we get below is for the AST class instance corresponding to the + // last definition of the variable before this use. + auto git = Context->GlobalNumericVariableTable.find(Name); + if (git != Context->GlobalNumericVariableTable.end()) + return git->second; + + SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, + "Using undefined numeric variable '" + Name + "'"); + return std::make_shared(Name, 0); + } +} + +/// Parse \p Expr for use of a numeric operand. Accept both literal values and +/// numeric variables. Return 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. Also return in \p ImplicitFmt the implicit matching +/// format of the operand and in \p KnownPhase the FileCheck phase when the +/// value of that operand is known. +std::shared_ptr +FileCheckPattern::ParseNumericOperand(StringRef &Expr, const SourceMgr &SM, + FileCheckNumExprFmtType &ImplicitFmt, + FileCheckPhase &KnownPhase) const { + + // Try to parse as a numeric variable use. + std::shared_ptr NumVar = + ParseNumericVariable(Expr, false, SM); + if (NumVar != nullptr) { + FileCheckNumExpr *NumExpr = NumVar->GetNumExpr(); + // Variable is not undefined. + if (NumExpr != nullptr) { + ImplicitFmt = NumExpr->GetFormat(); + if (NumExpr->GetKnownPhase() == FileCheckPhase::ParsePhase) + KnownPhase = FileCheckPhase::ParsePhase; + // Variable defined on earlier line. Its value will thus be known when + // matching this use. + else if (NumVar->GetDefLineNumber() < LineNumber) + KnownPhase = FileCheckPhase::MatchPhase; + else + KnownPhase = FileCheckPhase::CheckPhase; + } + return NumVar; + } + + // Otherwise, parse it as a literal. + int64_t SignedLiteralValue; + uint64_t UnsignedLiteralValue; + StringRef SaveExpr = Expr; + // Accept both signed and unsigned literal. + if (!Expr.consumeInteger(0, SignedLiteralValue)) { + ImplicitFmt = FmtNone; + KnownPhase = FileCheckPhase::ParsePhase; + return std::make_shared(SignedLiteralValue); + } else { + Expr = SaveExpr; + if (!Expr.consumeInteger(0, UnsignedLiteralValue)) { + ImplicitFmt = FmtNone; + KnownPhase = FileCheckPhase::ParsePhase; + return std::make_shared(UnsignedLiteralValue); + } else + return nullptr; + } +} + +/// Parse \p Expr for a binary operation. The left operand of the operand is +/// given in \p Opl. Return the class representing that binary operation in the +/// AST of the numeric expression or nullptr if parsing in which case errors +/// are reported on \p SM. Also return in \p ImplicitFmt the implicit matching +/// format of the binary operation and in \p KnownPhase the FileCheck phase +/// when the value of that operand is known. +std::shared_ptr FileCheckPattern::ParseFileCheckBinop( + StringRef &Expr, std::shared_ptr Opl, + const SourceMgr &SM, FileCheckNumExprFmtType &ImplicitFmt, + FileCheckPhase &KnownPhase) const { + SkipWhitespace(Expr); + if (Expr.empty()) + return Opl; + + // Check if this is a supported operation and selection function to perform + // it. + SMLoc oploc = SMLoc::getFromPointer(Expr.data()); + char Operator = Next(Expr); + binop_eval_t EvalBinop; + switch (Operator) { + case '+': + EvalBinop = operator+; + break; + case '-': + EvalBinop = operator-; + break; + default: + SM.PrintMessage(oploc, SourceMgr::DK_Error, + std::string("Unsupported numeric operation '") + Operator + + "'"); + return nullptr; + } + + SkipWhitespace(Expr); + if (Expr.empty()) + return nullptr; + + // Parse right operand. + FileCheckNumExprFmtType OplImplicitFmt = ImplicitFmt; + FileCheckPhase OplKnownPhase = KnownPhase; + std::shared_ptr Opr = + ParseNumericOperand(Expr, SM, ImplicitFmt, KnownPhase); + if (Opr == nullptr) + return nullptr; + + // Compute implicit matching format of the binary operation and indicate + // whether the two operands have conflicting implicit format. + if (OplImplicitFmt.Valid) { + if (ImplicitFmt.Valid && OplImplicitFmt != ImplicitFmt) + ImplicitFmt.Conflict = 1; + else + ImplicitFmt = OplImplicitFmt; + } + + KnownPhase = OplKnownPhase > KnownPhase ? OplKnownPhase : KnownPhase; + return std::make_shared(EvalBinop, Opl, Opr); +} + +/// Parse \p Expr for a numeric expression. Return the class representing the +/// AST of numeric expression or nullptr if parsing fails in which case errors +/// are reported on \p SM. Set \p NumVarDef to the pointer to the class +/// representing the variable defined to this numeric expression if any. +std::shared_ptr FileCheckPattern::ParseNumericExpression( + StringRef Expr, std::shared_ptr &NumVarDef, + const SourceMgr &SM) const { + FileCheckNumExprFmtType Fmt, ExplicitFmt = FmtNone; + + // Parse format specifier. + size_t FmtSpecEnd = Expr.find(','); + if (FmtSpecEnd != StringRef::npos) { + SkipWhitespace(Expr); + if (Skip(Expr, "%", false /*Optional*/)) { + SM.PrintMessage( + SMLoc::getFromPointer(Expr.data()), SourceMgr::DK_Error, + "Invalid matching format specification in numeric expression"); + return nullptr; + } + + // Check for unknown matching format specifier and set matching format in + // class instance representing this numeric expression. + SMLoc fmtloc = SMLoc::getFromPointer(Expr.data()); + switch (Next(Expr)) { + case 'u': + ExplicitFmt = FmtUnsigned; + break; + case 'd': + ExplicitFmt = FmtSigned; + break; + case 'x': + ExplicitFmt = FmtLowHex; + break; + case 'X': + ExplicitFmt = FmtCapHex; + break; + default: + SM.PrintMessage(fmtloc, SourceMgr::DK_Error, + "Invalid format specifier in numeric expression"); + return nullptr; + } + + SkipWhitespace(Expr); + if (Skip(Expr, ",", false /*Optional*/)) { + SM.PrintMessage( + SMLoc::getFromPointer(Expr.data()), SourceMgr::DK_Error, + "Invalid matching format specification in numeric expression"); + return nullptr; + } + } + + // Parse numeric variable definition. + NumVarDef.reset(); + size_t DefEnd = Expr.find(':'); + if (DefEnd != StringRef::npos) { + SkipWhitespace(Expr); + + NumVarDef = ParseNumericVariable(Expr, true /*IsDefinition*/, SM); + // Invalid variable definition. Error reporting done in parsing function. + if (NumVarDef == nullptr) + return nullptr; + + SkipWhitespace(Expr); + if (Skip(Expr, ":", false /*Optional*/)) + return nullptr; + SkipWhitespace(Expr); + } + + // Parse matching constraint. + SkipWhitespace(Expr); + if (Skip(Expr, "==", true /*Optional*/)) + return nullptr; + + // Parse numeric expression itself. + std::shared_ptr NumExprAST; + FileCheckNumExprFmtType ImplicitFmt = FmtNone; + FileCheckPhase KnownPhase; + if (Expr.empty()) + KnownPhase = FileCheckPhase::CheckPhase; + else { + SkipWhitespace(Expr); + NumExprAST = ParseNumericOperand(Expr, SM, ImplicitFmt, KnownPhase); + while (NumExprAST != nullptr && !Expr.empty()) + NumExprAST = + ParseFileCheckBinop(Expr, NumExprAST, SM, ImplicitFmt, KnownPhase); + if (NumExprAST == nullptr) + return nullptr; + } + + // Select explicit matching format if any, implicit one otherwise. Error out + // in case of conflicting implicit format without explicit format. + if (ExplicitFmt.Valid) + Fmt = ExplicitFmt; + else if (ImplicitFmt.Conflict) { + SM.PrintMessage( + SMLoc::getFromPointer(Expr.data()), SourceMgr::DK_Error, + "Variables with conflicting format specifier: need an explicit one"); + return nullptr; + } else + Fmt = ImplicitFmt; + + auto NumExpr = + std::make_shared(NumExprAST, Fmt, KnownPhase); + if (NumVarDef != nullptr) + NumVarDef->SetNumExpr(NumExpr); + return NumExpr; +} + +/// Parse legacy numeric expressions in \p Expr, ie. numeric expressions that +/// were allowed using the pattern variable syntax before numeric expressions +/// support was added to FileCheck. Pass \p Name of pseudovariable used in the +/// expression and pass in \p Trailer the rest of the expression. If parsing +/// fails, errors are reported on \p SM. +std::shared_ptr +FileCheckPattern::ParseLegacyNumericExpression(StringRef Expr, StringRef Name, + StringRef Trailer, + const SourceMgr &SM) const { + if (!Name.equals("@LINE")) { + SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, + "invalid pseudo variable in named regex"); + return nullptr; + } + + if (!Trailer.empty()) { + if (Trailer[0] != '+' && Trailer[0] != '-') { + SM.PrintMessage(SMLoc::getFromPointer(Trailer.data()), + SourceMgr::DK_Error, + "invalid operator for pseudo variable in named regex"); + return nullptr; + } + Trailer = Trailer.substr(1); + + int Offset; + if (Trailer.getAsInteger(10, Offset)) { + SM.PrintMessage(SMLoc::getFromPointer(Trailer.data()), + SourceMgr::DK_Error, + "invalid offset for pseudo variable in named regex"); + return nullptr; + } + } + + std::shared_ptr NumVarDef; + std::shared_ptr NumExpr = + ParseNumericExpression(Expr, NumVarDef, SM); + assert(NumVarDef == nullptr && "Legacy numeric expression sets variable"); + return NumExpr; +} + /// Parses the given string into the Pattern. /// /// \p Prefix provides which prefix is being matched, \p SM provides the @@ -38,6 +838,14 @@ this->LineNumber = LineNumber; PatternLoc = SMLoc::getFromPointer(PatternStr.data()); + // Create fake @LINE pseudo variable definition. + StringRef LinePseudo = "@LINE"; + uint64_t LineNumber64 = LineNumber; + auto LineNumberVal = FileCheckNumExprVal(LineNumber64); + auto LinePseudoVar = std::make_shared( + LinePseudo, LineNumberVal, FmtUnsigned, LineNumber); + Context->GlobalNumericVariableTable[LinePseudo] = LinePseudoVar; + if (!(Req.NoCanonicalizeWhiteSpace && Req.MatchFullLines)) // Ignore trailing whitespace. while (!PatternStr.empty() && @@ -111,98 +919,227 @@ continue; } - // Named RegEx matches. These are of two forms: [[foo:.*]] which matches .* - // (or some other regex) and assigns it to the FileCheck variable 'foo'. The - // second form is [[foo]] which is a reference to foo. The variable name - // itself must be of the form "[a-zA-Z_][0-9a-zA-Z_]*", otherwise we reject - // it. This is to catch some common errors. + // Pattern variable and numeric expressions matches. Pattern variables + // come in two forms: [[foo:.*]] which matches .* (or some other regex) and + // assigns it to the FileCheck variable 'foo'. The second form is [[foo]] + // which is a substitution of foo's value. Variable names themselves must + // be of the form "[a-zA-Z_][0-9a-zA-Z_]*", otherwise we reject it. This is + // to catch some common errors. Numeric expressions also have the + // definition and substitution modes but those can be combined into a + // single [[]] pattern and definition always come with an implicit + // substitution. Numeric variable names have the same restriction as their + // pattern variable counterpart. if (PatternStr.startswith("[[")) { + StringRef MatchStr = PatternStr.substr(2); + bool IsNumExpr = MatchStr.consume_front("#"); + const char *RefTypeStr = IsNumExpr ? "numeric expression" : "named regex"; // Find the closing bracket pair ending the match. End is going to be an // offset relative to the beginning of the match string. - size_t End = FindRegexVarEnd(PatternStr.substr(2), SM); + size_t End = FindRegexVarEnd(MatchStr, SM); if (End == StringRef::npos) { - SM.PrintMessage(SMLoc::getFromPointer(PatternStr.data()), - SourceMgr::DK_Error, - "invalid named regex reference, no ]] found"); + SM.PrintMessage( + SMLoc::getFromPointer(PatternStr.data()), SourceMgr::DK_Error, + std::string("invalid ") + RefTypeStr + " reference, no ]] found"); return true; } - StringRef MatchStr = PatternStr.substr(2, End); - PatternStr = PatternStr.substr(End + 4); + MatchStr = MatchStr.substr(0, End); + PatternStr = PatternStr.substr(End + 4 + (int)IsNumExpr); + + bool CaptureParenNeeded = false; + bool SubstNeeded; + bool MatchSubstNeeded; + bool IsVarDef; + StringRef DefName; + StringRef SubstStr; + StringRef MatchRegexp; + unsigned SubstInsertIdx = RegExStr.size(); + std::shared_ptr NumVarDef; + std::shared_ptr NumExpr; + + // Parse pattern variable or legacy numeric expression. + if (!IsNumExpr) { + // 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 name in named regex"); + return true; + } - // Get the regex name (e.g. "foo"). - size_t NameEnd = MatchStr.find(':'); - StringRef Name = MatchStr.substr(0, NameEnd); + StringRef Name = MatchStr.substr(0, TrailIdx); + StringRef Trailer = MatchStr.substr(TrailIdx); + size_t DefSepIdx = Trailer.find(":"); + IsVarDef = (DefSepIdx != StringRef::npos); + MatchSubstNeeded = !IsVarDef; + SubstNeeded = MatchSubstNeeded; + CaptureParenNeeded = IsVarDef; - if (Name.empty()) { - SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, - "invalid name in named regex: empty name"); - return true; - } + if (IsVarDef && (IsPseudo || !Trailer.consume_front(":"))) { + SM.PrintMessage(SMLoc::getFromPointer(MatchStr.data()), + SourceMgr::DK_Error, + "invalid name in named regex definition"); + return true; + } - // Verify that the name/expression is well formed. FileCheck currently - // supports @LINE, @LINE+number, @LINE-number expressions. The check here - // is relaxed, more strict check is performed in \c EvaluateExpression. - bool IsExpression = false; - for (unsigned i = 0, e = Name.size(); i != e; ++i) { - if (i == 0) { - if (Name[i] == '$') // Global vars start with '$' - continue; - if (Name[i] == '@') { - if (NameEnd != StringRef::npos) { - SM.PrintMessage(SMLoc::getFromPointer(Name.data()), - SourceMgr::DK_Error, - "invalid name in named regex definition"); + if (IsVarDef) { + // Detect collision between pattern and numeric variable when the + // former is created on a later line than the latter. + if (Context->GlobalNumericVariableTable.find(Name) != + Context->GlobalNumericVariableTable.end()) { + SM.PrintMessage( + SMLoc::getFromPointer(MatchStr.data()), SourceMgr::DK_Error, + "Numeric variable with same name '" + Name + "' exists"); + return true; + } + DefName = Name; + MatchRegexp = Trailer; + } else { + SubstStr = MatchStr; + + // Syntax for pattern variables allowed for simple expressions based + // on @LINE pseudo variable (aka legacy numeric expressions) before + // numeric expression support was added. Accept those (and only + // those) for compatibility. + if (IsPseudo) { + NumExpr = ParseLegacyNumericExpression(MatchStr, Name, Trailer, SM); + if (NumExpr == nullptr) return true; - } - IsExpression = true; - continue; + IsNumExpr = true; + } else if (!Trailer.empty()) { + SM.PrintMessage(SMLoc::getFromPointer(Trailer.data()), + SourceMgr::DK_Error, + "invalid use of operator in named regex"); + return true; } } - if (Name[i] != '_' && !isalnum(Name[i]) && - (!IsExpression || (Name[i] != '+' && Name[i] != '-'))) { - SM.PrintMessage(SMLoc::getFromPointer(Name.data() + i), - SourceMgr::DK_Error, "invalid name in named regex"); + // Parse numeric expression. + } else { + SubstStr = MatchStr; + NumExpr = ParseNumericExpression(SubstStr, NumVarDef, SM); + if (NumExpr == nullptr) + return true; + } + + // Process numeric expressions and legacy numeric expressions. + if (IsNumExpr) { + IsVarDef = (NumVarDef != nullptr); + if (IsVarDef) + DefName = NumVarDef->GetName(); + + // Evaluate the numeric expression if we can already do it so that we + // directly add it to the pattern to match and don't have to do a + // substitution later. + if (NumExpr->GetKnownPhase() == FileCheckPhase::ParsePhase && + NumExpr->SetValueFromEval()) { + SM.PrintMessage( + SMLoc::getFromPointer(SubstStr.data()), SourceMgr::DK_Error, + "Failed to evaluate numeric expression known at parse time"); return true; } + + // Expression can be evaluated at match time, it will need a + // substitution. + MatchSubstNeeded = + (NumExpr->GetKnownPhase() == FileCheckPhase::MatchPhase); + SubstNeeded = (NumExpr->GetAST() != nullptr); + + // Expression will only be known after matching is done, matched value + // needs to be captured and verified against the constraint of the + // numeric expression. + if (NumExpr->GetKnownPhase() > FileCheckPhase::MatchPhase) { + CaptureParenNeeded = true; + FileCheckNumExprMatch NumExprMatch = {NumExpr, CurParen}; + CapturedNumericExpressions.emplace_back(NumExprMatch); + // No capture needed otherwise. + } + + // Get the string we need to match if we know it already. This could be + // either the value if we already know it or a suitable wildcard + // pattern if value would be known after match and needs to be verified + // against the constraint of the numeric expression. Numeric expression + // whose evaluation is known at match time get an empty string and a + // substitution is created instead. + llvm::Optional OptMatchString = + NumExpr->GetMatchString(FileCheckPhase::ParsePhase); + if (!OptMatchString.hasValue()) + assert(false && "Failed to get string to match"); + MatchRegexp = StringRef(OptMatchString.getValue()); } - // Name can't start with a digit. - if (isdigit(static_cast(Name[0]))) { - SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, - "invalid name in named regex"); - return true; + // Capturing parentheses are needed. Numeric expressions using variable + // defined on the same line need it to do the match in a single pass: + // they are matched using an appropriate wildcard pattern and the matched + // value gets verified against the constraint of the numeric expression + // later. + if (CaptureParenNeeded) { + RegExStr += '('; + // Checking numeric expressions requires substituting them by suitable + // wildcard patterns and capturing what these matched to. Adjust + // substitution index to be inside the capturing parentheses. + ++SubstInsertIdx; } - // Handle [[foo]]. - if (NameEnd == StringRef::npos) { - // Handle variables that were defined earlier on the same line by - // emitting a backreference. - if (VariableDefs.find(Name) != VariableDefs.end()) { - unsigned VarParenNum = VariableDefs[Name]; - if (VarParenNum < 1 || VarParenNum > 9) { - SM.PrintMessage(SMLoc::getFromPointer(Name.data()), - SourceMgr::DK_Error, - "Can't back-reference more than 9 variables"); - return true; - } - AddBackrefToRegEx(VarParenNum); - } else { - VariableUses.push_back(std::make_pair(Name, RegExStr.size())); + // Handle variable definition: [[:(...)]] and [[#(...):(...)]]. + if (IsVarDef) { + if (IsNumExpr) + // This store is done here rather than in Match() to allow + // ParseNumericVariable() to get the pointer to the AST class + // instance of the right variable definition corresponding to a + // given numeric variable use. + Context->GlobalNumericVariableTable[DefName] = NumVarDef; + else { + VariableDefs[DefName] = CurParen; + // Mark pattern variable as defined to detect collision between + // pattern and numeric variable in ParseNumericVariable when the + // latter is created on a later line than the former. We cannot + // reuse GlobalVariableTable by populating it with an empty string + // for that since we would then loose the ability to detect use of + // undefined variable in Match(). + Context->DefinedVariableTable[DefName] = true; } - continue; } - // Handle [[foo:.*]]. - VariableDefs[Name] = CurParen; - RegExStr += '('; - ++CurParen; + if (CaptureParenNeeded) + ++CurParen; - if (AddRegExToRegEx(MatchStr.substr(NameEnd + 1), CurParen, SM)) + if (!MatchRegexp.empty() && AddRegExToRegEx(MatchRegexp, CurParen, SM)) return true; - RegExStr += ')'; + if (CaptureParenNeeded) + RegExStr += ')'; + + // Handle use of pattern variables that were defined earlier on the + // same line by emitting a backreference. Numeric expressions cannot be + // expressed using regexs so are recorded as substitutions to perform by + // FileCheck in the matching phase when only using variables defined on + // the line. See Match() for how numeric expressions using any variable + // defined on the same line are matched. + if (!IsNumExpr && VariableDefs.find(SubstStr) != VariableDefs.end()) { + unsigned CaptureParen = VariableDefs[SubstStr]; + if (CaptureParen < 1 || CaptureParen > 9) { + SM.PrintMessage(SMLoc::getFromPointer(SubstStr.data()), + SourceMgr::DK_Error, + "Can't back-reference more than 9 variables"); + return true; + } + AddBackrefToRegEx(CaptureParen); + // Handle use of pattern variables ([[]]) defined in previous CHECK + // pattern or use of a numeric expression. + } else if (SubstNeeded) { + FileCheckPatternSubst Subst = + IsNumExpr + ? FileCheckPatternSubst(Context, SubstStr, NumExpr, + SubstInsertIdx) + : FileCheckPatternSubst(Context, SubstStr, SubstInsertIdx); + AllSubsts.push_back(Subst); + // Handle use of a numeric expression with all variables defined in + // previous CHECK pattern. + if (MatchSubstNeeded) + MatchSubsts.push_back(Subst); + } } // Handle fixed string matches. @@ -242,37 +1179,20 @@ RegExStr += Backref; } -/// Evaluates expression and stores the result to \p Value. -/// -/// Returns true on success and false when the expression has invalid syntax. -bool FileCheckPattern::EvaluateExpression(StringRef Expr, std::string &Value) const { - // The only supported expression is @LINE([\+-]\d+)? - if (!Expr.startswith("@LINE")) - return false; - Expr = Expr.substr(StringRef("@LINE").size()); - int Offset = 0; - if (!Expr.empty()) { - if (Expr[0] == '+') - Expr = Expr.substr(1); - else if (Expr[0] != '-') - return false; - if (Expr.getAsInteger(10, Offset)) - return false; - } - Value = llvm::itostr(LineNumber + Offset); - return true; -} - /// 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, the size of the matched string is returned in \p /// MatchLen. /// -/// The \p VariableTable StringMap provides the current values of filecheck -/// variables and is updated if this match defines new values. +/// The GlobalVariableTable StringMap provides the current values of FileCheck +/// pattern variables and is updated if this match defines new values. +/// Substitutions for numeric expression point to the class instance +/// representing the numeric expression which is updated when the value is +/// known, either in this function or in ParsePattern if their value is known +/// at parse time. size_t FileCheckPattern::Match(StringRef Buffer, size_t &MatchLen, - StringMap &VariableTable) const { + const SourceMgr &SM) const { // If this is the EOF pattern, match it immediately. if (CheckTy == Check::CheckEOF) { MatchLen = 0; @@ -291,29 +1211,44 @@ // actual value. StringRef RegExToMatch = RegExStr; std::string TmpStr; - if (!VariableUses.empty()) { + SmallVector MatchInfo; + + if (!MatchSubsts.empty()) { TmpStr = RegExStr; unsigned InsertOffset = 0; - for (const auto &VariableUse : VariableUses) { - std::string Value; - - if (VariableUse.first[0] == '@') { - if (!EvaluateExpression(VariableUse.first, Value)) - return StringRef::npos; - } else { - StringMap::iterator it = - VariableTable.find(VariableUse.first); - // If the variable is undefined, return an error. - if (it == VariableTable.end()) - return StringRef::npos; - - // Look up the value and escape it so that we can put it into the regex. - Value += Regex::escape(it->second); - } + // 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. Numeric expressions whose value is known at + // parse time (e.g. uses of @LINE pseudo variable) are replaced at parse + // time. For numeric expressions using variable 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 &MatchSubst : MatchSubsts) { + // Substitute and check for failure (e.g. use of undefined variable). + llvm::Optional OptValue = MatchSubst.GetSubstitute(); + if (!OptValue.hasValue()) + return StringRef::npos; + std::string Value = OptValue.getValue(); // Plop it into the regex at the adjusted offset. - TmpStr.insert(TmpStr.begin() + VariableUse.second + InsertOffset, + TmpStr.insert(TmpStr.begin() + MatchSubst.GetIndex() + InsertOffset, Value.begin(), Value.end()); InsertOffset += Value.size(); } @@ -322,35 +1257,96 @@ RegExToMatch = TmpStr; } - SmallVector MatchInfo; - if (!Regex(RegExToMatch, Regex::Newline).match(Buffer, &MatchInfo)) - return StringRef::npos; + bool AllNumExprSatisfied; + 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 variables, remember their values. - for (const auto &VariableDef : VariableDefs) { - assert(VariableDef.second < MatchInfo.size() && "Internal paren error"); - VariableTable[VariableDef.first] = MatchInfo[VariableDef.second]; - } + // If this defines any pattern variables, remember their values. + for (const auto &VariableDef : VariableDefs) { + StringRef Name = VariableDef.first; + unsigned CaptureParen = VariableDef.second; + assert(CaptureParen < MatchInfo.size() && "Internal paren error"); + StringRef MatchedStr = MatchInfo[CaptureParen]; + Context->GlobalVariableTable[Name] = MatchedStr; + } + + // Check the matched value satisfies the numeric expression constraint and + // store it in the numeric expression if that's the case. + for (const auto &CapturedNumericExpression : CapturedNumericExpressions) { + assert(CapturedNumericExpression.CaptureParen < MatchInfo.size() && + "Internal paren error"); + StringRef MatchedStr = MatchInfo[CapturedNumericExpression.CaptureParen]; + FileCheckNumExpr *NumExpr = CapturedNumericExpression.NumExpr.get(); + + // If numeric expression can be evaluated now, do it and error out if + // evaluation fails. + bool UnconstrainedVarDef = (NumExpr->GetAST() == nullptr); + if (!UnconstrainedVarDef) { + if (NumExpr->SetValueFromEval()) + return StringRef::npos; + } + + // Get numeric value from the matched string according to matching format + // of the numeric expression. Error out if this fails. + llvm::Optional OptMatchedVal; + OptMatchedVal = NumExpr->ValueFromStringRepr(MatchedStr); + if (!OptMatchedVal.hasValue()) { + SM.PrintMessage(SMLoc::getFromPointer(MatchedStr.data()), + SourceMgr::DK_Error, + "Cannot represent numeric value " + MatchedStr); + return StringRef::npos; + } + + // Now verify the constraint of the numeric expression. + FileCheckNumExprVal MatchedVal = OptMatchedVal.getValue(); + if (NumExpr->VerifyConstraint(MatchedVal)) { + AllNumExprSatisfied = false; + // Try matching again from the line following the one matched. There + // might be over match 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 has 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 (UnconstrainedVarDef) { + if (NumExpr->SetTentativeValue(MatchedVal)) + assert(false && "Value of numeric expression already set"); + } + } + } while (!AllNumExprSatisfied && !Buffer.empty()); + + // All constraints satisfied, make variable definitions final. + if (AllNumExprSatisfied) { + for (const auto &CapturedNumericExpression : CapturedNumericExpressions) { + FileCheckNumExpr *NumExpr = CapturedNumericExpression.NumExpr.get(); + NumExpr->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; } - /// Computes an arbitrary estimate for the quality of matching this pattern at /// the start of \p Buffer; a distance of zero should correspond to a perfect /// match. -unsigned -FileCheckPattern::ComputeMatchDistance(StringRef Buffer, - const StringMap &VariableTable) const { +unsigned FileCheckPattern::ComputeMatchDistance(StringRef Buffer) const { // Just compute the number of matching characters. For regular expressions, we // just compare against the regex itself and hope for the best. // @@ -367,38 +1363,51 @@ return BufferPrefix.edit_distance(ExampleString); } -void FileCheckPattern::PrintVariableUses(const SourceMgr &SM, StringRef Buffer, - const StringMap &VariableTable, - SMRange MatchRange) const { - // If this was a regular expression using variables, print the current - // variable values. - if (!VariableUses.empty()) { - for (const auto &VariableUse : VariableUses) { +/// Print value of successful substitutions or name of undefined pattern or +/// numeric variables preventing such a successful substitution. +/// +/// Note: Numeric expressions known at parse time are not shown here since no +/// substitution gets created for them but hopefully if the value is known at +/// parse time the value should be obvious (except in case of bug in FileCheck +/// itself). Numeric expression using numeric variables defined on the same +/// line are not shown either. +void FileCheckPattern::PrintSubsts(const SourceMgr &SM, StringRef Buffer, + SMRange MatchRange) const { + // Print info what we know about substitutions. This covers both uses of + // pattern variables and numeric subsitutions as long as they only use + // variables defined in earlier lines. + if (!AllSubsts.empty()) { + for (const auto &Subst : AllSubsts) { SmallString<256> Msg; raw_svector_ostream OS(Msg); - StringRef Var = VariableUse.first; - if (Var[0] == '@') { - std::string Value; - if (EvaluateExpression(Var, Value)) { - OS << "with expression \""; - OS.write_escaped(Var) << "\" equal to \""; - OS.write_escaped(Value) << "\""; - } else { - OS << "uses incorrect expression \""; - OS.write_escaped(Var) << "\""; + bool IsNumExpr = Subst.IsNumExpr(); + + // Substitution failed or is not known at match time, print undefined + // variables it uses. + llvm::Optional OptMatchedValue = Subst.MatchedValue(); + if (!OptMatchedValue.hasValue()) { + std::vector UndefVarNames; + Subst.GetUndefVarNames(UndefVarNames); + if (UndefVarNames.empty()) + continue; + if (IsNumExpr) + OS << "uses undefined numeric variables"; + else + OS << "uses undefined variable"; + for (auto UndefVarName : UndefVarNames) { + OS << " \""; + OS.write_escaped(UndefVarName) << "\""; } + // Substitution succeeded and numeric expression satisfied its + // constraint. Print substituted value. } else { - StringMap::const_iterator it = VariableTable.find(Var); - - // Check for undefined variable references. - if (it == VariableTable.end()) { - OS << "uses undefined variable \""; - OS.write_escaped(Var) << "\""; - } else { + std::string Val = OptMatchedValue.getValue(); + if (IsNumExpr) + OS << "with numeric expression \""; + else OS << "with variable \""; - OS.write_escaped(Var) << "\" equal to \""; - OS.write_escaped(it->second) << "\""; - } + OS.write_escaped(Subst.GetSubstString()) << "\" equal to \""; + OS.write_escaped(Val) << "\""; } if (MatchRange.isValid()) @@ -431,7 +1440,6 @@ void FileCheckPattern::PrintFuzzyMatch( const SourceMgr &SM, StringRef Buffer, - const StringMap &VariableTable, std::vector *Diags) const { // Attempt to find the closest/best fuzzy match. Usually an error happens // because some string in the output didn't exactly match. In these cases, we @@ -453,7 +1461,7 @@ // Compute the "quality" of this match as an arbitrary combination of the // match distance and the number of lines skipped to get to this match. - unsigned Distance = ComputeMatchDistance(Buffer.substr(i), VariableTable); + unsigned Distance = ComputeMatchDistance(Buffer.substr(i)); double Quality = Distance + (NumLinesForward / 100.); if (Quality < BestQuality || Best == StringRef::npos) { @@ -477,6 +1485,17 @@ } } +/// Return the value of pattern variable \p VarName or nothing if no such +/// variable has been defined. +llvm::Optional +FileCheckPatternContext::GetPatternVarValue(StringRef VarName) { + auto git = GlobalVariableTable.find(VarName); + if (git == GlobalVariableTable.end()) + return llvm::None; + + return git->second; +} + /// Finds the closing sequence of a regex variable usage or definition. /// /// \p Str has to point in the beginning of the definition (right after the @@ -747,9 +1766,15 @@ /// /// The strings are added to the CheckStrings vector. Returns true in case of /// an error, false otherwise. -bool llvm::FileCheck::ReadCheckFile(SourceMgr &SM, StringRef Buffer, - Regex &PrefixRE, - std::vector &CheckStrings) { +bool llvm::FileCheck::ReadCheckFile( + SourceMgr &SM, StringRef Buffer, Regex &PrefixRE, + std::vector &CheckStrings) { + auto *PatternContext = new FileCheckPatternContext(); + // Process command-line definition of variables at parse time so that numeric + // expression can refer to numeric variable defined on the command-line. + if (PatternContext->DefineCmdlineVariables(Req.GlobalDefines, SM)) + return true; + std::vector ImplicitNegativeChecks; for (const auto &PatternString : Req.ImplicitCheckNot) { // Create a buffer with fake command line content in order to display the @@ -763,7 +1788,8 @@ CmdLine->getBuffer().substr(Prefix.size(), PatternString.size()); SM.AddNewSourceBuffer(std::move(CmdLine), SMLoc()); - ImplicitNegativeChecks.push_back(FileCheckPattern(Check::CheckNot)); + ImplicitNegativeChecks.push_back( + FileCheckPattern(Check::CheckNot, PatternContext)); ImplicitNegativeChecks.back().ParsePattern(PatternInBuffer, "IMPLICIT-CHECK", SM, 0, Req); } @@ -826,7 +1852,7 @@ SMLoc PatternLoc = SMLoc::getFromPointer(Buffer.data()); // Parse the pattern. - FileCheckPattern P(CheckTy); + FileCheckPattern P(CheckTy, PatternContext); if (P.ParsePattern(Buffer.substr(0, EOL), UsedPrefix, SM, LineNumber, Req)) return true; @@ -875,7 +1901,8 @@ // Add an EOF pattern for any trailing CHECK-DAG/-NOTs, and use the first // prefix as a filler for the error message. if (!DagNotMatches.empty()) { - CheckStrings.emplace_back(FileCheckPattern(Check::CheckEOF), *Req.CheckPrefixes.begin(), + CheckStrings.emplace_back(FileCheckPattern(Check::CheckEOF, PatternContext), + *Req.CheckPrefixes.begin(), SMLoc::getFromPointer(Buffer.data())); std::swap(DagNotMatches, CheckStrings.back().DagNotStrings); } @@ -901,8 +1928,7 @@ static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM, StringRef Prefix, SMLoc Loc, const FileCheckPattern &Pat, - int MatchedCount, StringRef Buffer, - StringMap &VariableTable, size_t MatchPos, + int MatchedCount, StringRef Buffer, size_t MatchPos, size_t MatchLen, const FileCheckRequest &Req, std::vector *Diags) { bool PrintDiag = true; @@ -934,24 +1960,22 @@ Loc, ExpectedMatch ? SourceMgr::DK_Remark : SourceMgr::DK_Error, Message); SM.PrintMessage(MatchRange.Start, SourceMgr::DK_Note, "found here", {MatchRange}); - Pat.PrintVariableUses(SM, Buffer, VariableTable, MatchRange); + Pat.PrintSubsts(SM, Buffer, MatchRange); } static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM, const FileCheckString &CheckStr, int MatchedCount, - StringRef Buffer, StringMap &VariableTable, - size_t MatchPos, size_t MatchLen, FileCheckRequest &Req, + StringRef Buffer, size_t MatchPos, size_t MatchLen, + FileCheckRequest &Req, std::vector *Diags) { PrintMatch(ExpectedMatch, SM, CheckStr.Prefix, CheckStr.Loc, CheckStr.Pat, - MatchedCount, Buffer, VariableTable, MatchPos, MatchLen, Req, - Diags); + MatchedCount, Buffer, MatchPos, MatchLen, Req, Diags); } static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM, StringRef Prefix, SMLoc Loc, const FileCheckPattern &Pat, int MatchedCount, - StringRef Buffer, StringMap &VariableTable, - bool VerboseVerbose, + StringRef Buffer, bool VerboseVerbose, std::vector *Diags) { bool PrintDiag = true; if (!ExpectedMatch) { @@ -987,19 +2011,18 @@ SM.PrintMessage(SearchRange.Start, SourceMgr::DK_Note, "scanning from here"); // Allow the pattern to print additional information if desired. - Pat.PrintVariableUses(SM, Buffer, VariableTable); + Pat.PrintSubsts(SM, Buffer); if (ExpectedMatch) - Pat.PrintFuzzyMatch(SM, Buffer, VariableTable, Diags); + Pat.PrintFuzzyMatch(SM, Buffer, Diags); } static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM, const FileCheckString &CheckStr, int MatchedCount, - StringRef Buffer, StringMap &VariableTable, - bool VerboseVerbose, + StringRef Buffer, bool VerboseVerbose, std::vector *Diags) { PrintNoMatch(ExpectedMatch, SM, CheckStr.Prefix, CheckStr.Loc, CheckStr.Pat, - MatchedCount, Buffer, VariableTable, VerboseVerbose, Diags); + MatchedCount, Buffer, VerboseVerbose, Diags); } /// Count the number of newlines in the specified range. @@ -1028,7 +2051,6 @@ /// Match check string and its "not strings" and/or "dag strings". size_t FileCheckString::Check(const SourceMgr &SM, StringRef Buffer, bool IsLabelScanMode, size_t &MatchLen, - StringMap &VariableTable, FileCheckRequest &Req, std::vector *Diags) const { size_t LastPos = 0; @@ -1040,7 +2062,7 @@ // over the block again (including the last CHECK-LABEL) in normal mode. if (!IsLabelScanMode) { // Match "dag strings" (with mixed "not strings" if any). - LastPos = CheckDag(SM, Buffer, NotStrings, VariableTable, Req, Diags); + LastPos = CheckDag(SM, Buffer, NotStrings, Req, Diags); if (LastPos == StringRef::npos) return StringRef::npos; } @@ -1055,18 +2077,17 @@ StringRef MatchBuffer = Buffer.substr(LastMatchEnd); size_t CurrentMatchLen; // get a match at current start point - size_t MatchPos = Pat.Match(MatchBuffer, CurrentMatchLen, VariableTable); + size_t MatchPos = Pat.Match(MatchBuffer, CurrentMatchLen, SM); if (i == 1) FirstMatchPos = LastPos + MatchPos; // report if (MatchPos == StringRef::npos) { - PrintNoMatch(true, SM, *this, i, MatchBuffer, VariableTable, - Req.VerboseVerbose, Diags); + PrintNoMatch(true, SM, *this, i, MatchBuffer, Req.VerboseVerbose, Diags); return StringRef::npos; } - PrintMatch(true, SM, *this, i, MatchBuffer, VariableTable, MatchPos, - CurrentMatchLen, Req, Diags); + PrintMatch(true, SM, *this, i, MatchBuffer, MatchPos, CurrentMatchLen, Req, + Diags); // move start point after the match LastMatchEnd += MatchPos + CurrentMatchLen; @@ -1101,7 +2122,7 @@ // If this match had "not strings", verify that they don't exist in the // skipped region. - if (CheckNot(SM, SkippedRegion, NotStrings, VariableTable, Req, Diags)) + if (CheckNot(SM, SkippedRegion, NotStrings, Req, Diags)) return StringRef::npos; } @@ -1187,22 +2208,21 @@ bool FileCheckString::CheckNot( const SourceMgr &SM, StringRef Buffer, const std::vector &NotStrings, - StringMap &VariableTable, const FileCheckRequest &Req, - std::vector *Diags) const { + const FileCheckRequest &Req, std::vector *Diags) const { for (const FileCheckPattern *Pat : NotStrings) { assert((Pat->getCheckTy() == Check::CheckNot) && "Expect CHECK-NOT!"); size_t MatchLen = 0; - size_t Pos = Pat->Match(Buffer, MatchLen, VariableTable); + size_t Pos = Pat->Match(Buffer, MatchLen, SM); if (Pos == StringRef::npos) { PrintNoMatch(false, SM, Prefix, Pat->getLoc(), *Pat, 1, Buffer, - VariableTable, Req.VerboseVerbose, Diags); + Req.VerboseVerbose, Diags); continue; } - PrintMatch(false, SM, Prefix, Pat->getLoc(), *Pat, 1, Buffer, VariableTable, - Pos, MatchLen, Req, Diags); + PrintMatch(false, SM, Prefix, Pat->getLoc(), *Pat, 1, Buffer, Pos, MatchLen, + Req, Diags); return true; } @@ -1214,7 +2234,6 @@ size_t FileCheckString::CheckDag(const SourceMgr &SM, StringRef Buffer, std::vector &NotStrings, - StringMap &VariableTable, const FileCheckRequest &Req, std::vector *Diags) const { if (DagNotStrings.empty()) @@ -1255,19 +2274,19 @@ // 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, VariableTable); + 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) { PrintNoMatch(true, SM, Prefix, Pat.getLoc(), Pat, 1, MatchBuffer, - VariableTable, Req.VerboseVerbose, Diags); + Req.VerboseVerbose, Diags); return StringRef::npos; } // Re-calc it as the offset relative to the start of the original string. MatchPos += MatchPosBuf; if (Req.VerboseVerbose) - PrintMatch(true, SM, Prefix, Pat.getLoc(), Pat, 1, Buffer, - VariableTable, MatchPos, MatchLen, Req, Diags); + PrintMatch(true, SM, Prefix, Pat.getLoc(), Pat, 1, Buffer, MatchPos, + MatchLen, Req, Diags); MatchRange M{MatchPos, MatchPos + MatchLen}; if (Req.AllowDeprecatedDagOverlap) { // We don't need to track all matches in this mode, so we just maintain @@ -1314,8 +2333,8 @@ MatchPos = MI->End; } if (!Req.VerboseVerbose) - PrintMatch(true, SM, Prefix, Pat.getLoc(), Pat, 1, Buffer, VariableTable, - MatchPos, MatchLen, Req, Diags); + PrintMatch(true, SM, Prefix, Pat.getLoc(), Pat, 1, Buffer, MatchPos, + MatchLen, Req, Diags); // Handle the end of a CHECK-DAG group. if (std::next(PatItr) == PatEnd || @@ -1326,7 +2345,7 @@ // region. StringRef SkippedRegion = Buffer.slice(StartPos, MatchRanges.begin()->Pos); - if (CheckNot(SM, SkippedRegion, NotStrings, VariableTable, Req, Diags)) + if (CheckNot(SM, SkippedRegion, NotStrings, Req, Diags)) return StringRef::npos; // Clear "not strings". NotStrings.clear(); @@ -1390,16 +2409,103 @@ return Regex(PrefixRegexStr); } -// Remove local variables from \p VariableTable. Global variables -// (start with '$') are preserved. -static void ClearLocalVars(StringMap &VariableTable) { - SmallVector LocalVars; - for (const auto &Var : VariableTable) +/// Define pattern and numeric variables from definitions given on the command +/// line passed as a vector of VAR=VAL strings in \p CmdlineDefines. Report any +/// error to \p SM. +bool FileCheckPatternContext::DefineCmdlineVariables( + std::vector &CmdlineDefines, SourceMgr &SM) { + static bool CmdlineVariablesDefined = false; + + if (CmdlineVariablesDefined) + return true; + CmdlineVariablesDefined = true; + + // Dummy pattern to call ParseNumericExpression + FileCheckPattern P(Check::CheckPlain, this); + + bool ErrorFound = false; + for (const auto &CmdlineDef : CmdlineDefines) { + // Numeric variable definition. Same format as numeric expression with + // variable definition where ':' is replaced by '=' + if (CmdlineDef[0] == '#') { + // Copy command-line definition text with leading '#' stripped and + // replace '=' by ':' to be able to reuse ParseNumericExpression. + // Create a buffer with fake command line content in order to display + // parsing diagnostic with location information and point to the + // command-line option with invalid syntax. + std::string Prefix = "-D"; + std::string Suffix1 = " (parsed as: [["; + std::string Suffix2 = CmdlineDef; + size_t EqIdx = Suffix2.find('='); + Suffix2[EqIdx] = ':'; + std::string Suffix3 = "]])"; + std::string DiagCmdline = + Prefix + CmdlineDef + Suffix1 + Suffix2 + Suffix3; + std::unique_ptr CmdLine = + MemoryBuffer::getMemBufferCopy(DiagCmdline, "command line"); + StringRef DiagCmdlineDefRef = CmdLine->getBuffer(); + SM.AddNewSourceBuffer(std::move(CmdLine), SMLoc()); + + // Now parse to both check syntax is correct and create the necessary + // class instance. + size_t NumExprStartIdx = + Prefix.size() + CmdlineDef.size() + Suffix1.size() + 1; + StringRef CmdlineDefRef = + DiagCmdlineDefRef.substr(NumExprStartIdx).drop_back(Suffix3.size()); + std::shared_ptr NumVarDef; + std::shared_ptr NumExpr = + P.ParseNumericExpression(CmdlineDefRef, NumVarDef, SM); + if (NumVarDef == nullptr) { + ErrorFound = true; + continue; + } + NumExpr->SetValueFromEval(); + + // Record this variable definition. + GlobalNumericVariableTable[NumVarDef->GetName()] = NumVarDef; + // Pattern variable definition + } else { + std::string Prefix = "-D"; + std::string DiagCmdline = Prefix + CmdlineDef; + std::unique_ptr CmdLine = + MemoryBuffer::getMemBufferCopy(DiagCmdline, "command line"); + StringRef CmdlineDefRef = CmdLine->getBuffer().substr(Prefix.size()); + SM.AddNewSourceBuffer(std::move(CmdLine), SMLoc()); + + bool IsPseudo; + unsigned TrailIdx; + std::pair CmdlineNameVal = CmdlineDefRef.split('='); + StringRef Name = CmdlineNameVal.first; + if (P.ParseVariable(Name, IsPseudo, TrailIdx) || IsPseudo || + TrailIdx != Name.size()) { + SM.PrintMessage(SMLoc::getFromPointer(CmdlineDefRef.data()), + SourceMgr::DK_Error, + "Invalid name for variable definition '" + Name + "'"); + ErrorFound = true; + continue; + } + GlobalVariableTable.insert(CmdlineDefRef.split('=')); + } + } + + return ErrorFound; +} + +/// Undefine local variables (variables whose name does not start with a '$' +/// sign), ie remove them from GlobalVariableTable. +void FileCheckPatternContext::ClearLocalVars(void) { + SmallVector LocalPatternVars, LocalNumericVars; + for (const auto &Var : GlobalVariableTable) + if (Var.first()[0] != '$') + LocalPatternVars.push_back(Var.first()); + for (const auto &Var : GlobalNumericVariableTable) if (Var.first()[0] != '$') - LocalVars.push_back(Var.first()); + LocalNumericVars.push_back(Var.first()); - for (const auto &Var : LocalVars) - VariableTable.erase(Var); + for (const auto &Var : LocalPatternVars) + GlobalVariableTable.erase(Var); + for (const auto &Var : LocalNumericVars) + GlobalNumericVariableTable.erase(Var); } /// Check the input to FileCheck provided in the \p Buffer against the \p @@ -1411,12 +2517,8 @@ std::vector *Diags) { bool ChecksFailed = false; - /// VariableTable - This holds all the current filecheck variables. - StringMap VariableTable; - - for (const auto& Def : Req.GlobalDefines) - VariableTable.insert(StringRef(Def).split('=')); - + FileCheckPatternContext *Context = + CheckStrings.empty() ? nullptr : CheckStrings[0].Pat.GetContext(); unsigned i = 0, j = 0, e = CheckStrings.size(); while (true) { StringRef CheckRegion; @@ -1431,10 +2533,10 @@ // Scan to next CHECK-LABEL match, ignoring CHECK-NOT and CHECK-DAG size_t MatchLabelLen = 0; - size_t MatchLabelPos = CheckLabelStr.Check( - SM, Buffer, true, MatchLabelLen, VariableTable, Req, Diags); + size_t MatchLabelPos = + CheckLabelStr.Check(SM, Buffer, true, MatchLabelLen, Req, Diags); if (MatchLabelPos == StringRef::npos) - // Immediately bail of CHECK-LABEL fails, nothing else we can do. + // Immediately bail if CHECK-LABEL fails, nothing else we can do. return false; CheckRegion = Buffer.substr(0, MatchLabelPos + MatchLabelLen); @@ -1442,8 +2544,8 @@ ++j; } - if (Req.EnableVarScope) - ClearLocalVars(VariableTable); + if (Req.EnableVarScope && Context != nullptr) + Context->ClearLocalVars(); for (; i != j; ++i) { const FileCheckString &CheckStr = CheckStrings[i]; @@ -1451,8 +2553,8 @@ // Check each string within the scanned region, including a second check // of any final CHECK-LABEL (to verify CHECK-NOT and CHECK-DAG) size_t MatchLen = 0; - size_t MatchPos = CheckStr.Check(SM, CheckRegion, false, MatchLen, - VariableTable, Req, Diags); + size_t MatchPos = + CheckStr.Check(SM, CheckRegion, false, MatchLen, Req, Diags); if (MatchPos == StringRef::npos) { ChecksFailed = true; @@ -1466,6 +2568,7 @@ if (j == e) break; } + delete Context; // Success if no checks failed. return !ChecksFailed; Index: llvm/test/FileCheck/defines.txt =================================================================== --- llvm/test/FileCheck/defines.txt +++ llvm/test/FileCheck/defines.txt @@ -1,6 +1,5 @@ ; 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: 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 @@ -9,6 +8,15 @@ ; 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 -check-prefix ERRCLIFMT +; RUN: not FileCheck -D@VALUE=10 -input-file %s %s 2>&1 | FileCheck %s -check-prefix ERRCLIPSEUDO +; RUN: not FileCheck -D'VALUE + 2=10' -input-file %s %s 2>&1 | FileCheck %s -check-prefix ERRCLITRAIL +; RUN: FileCheck -D#%X,NUMVAL1=8 -D#NUMVAL2=NUMVAL1+4 -check-prefix CHECKNUM -input-file %s %s +; RUN: not FileCheck -D#%X,NUMVAL1=8 -D#NUMVAL2=NUMVAL1+6 -check-prefix CHECKNUM -input-file %s %s 2>&1 | FileCheck %s -check-prefix NUMERRMSG +; RUN: not FileCheck -D#%X,NUMVAL1=8 -D#NUMVAL2=NUMVAL1+4 -check-prefix NUMNOT -input-file %s %s 2>&1 | FileCheck %s -check-prefix NOT-NUMERRMSG +; RUN: FileCheck -D#%X,NUMVAL1=8 -D#NUMVAL2=NUMVAL1+6 -check-prefixes NUMNOT -input-file %s %s +; RUN: not FileCheck -D#,VALUE=10 -input-file %s %s 2>&1 | FileCheck %s -check-prefix NUMERRCLI + Value = 10 ; CHECK: Value = [[VALUE]] ; NOT-NOT: Value = [[VALUE]] @@ -32,3 +40,32 @@ Empty value = @@ ; EMPTY: Empty value = @[[VALUE]]@ + +; ERRCLIFMT: command line:1:3: error: Invalid name for variable definition '10VALUE' +; ERRCLIFMT-NEXT: -D10VALUE=10 +; ERRCLIFMT-NEXT: {{^ \^$}} + +; ERRCLIPSEUDO: command line:1:3: error: Invalid name for variable definition '@VALUE' +; ERRCLIPSEUDO-NEXT: -D@VALUE=10 +; ERRCLIPSEUDO-NEXT: {{^ \^$}} + +; ERRCLITRAIL: command line:1:3: error: Invalid name for variable definition 'VALUE + 2' +; ERRCLITRAIL-NEXT: -DVALUE + 2=10 +; ERRCLITRAIL-NEXT: {{^ \^$}} + +Numeric value #2 = C +; CHECKNUM: Numeric value #2 = [[#NUMVAL2]] +; NUMNOT-NOT: Numeric value #2 = [[#NUMVAL2]] + +; 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 "NUMVAL2" equal to "E" +; 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:[[#NUMVALLINE:@LINE-10]]:1: note: found here +; NOT-NUMERRMSG: defines.txt:[[#NUMVALLINE]]:1: note: with numeric expression "NUMVAL2" equal to "C" + +; NUMERRCLI: command line:1:29: error: Invalid matching format specification in numeric expression +; NUMERRCLI-NEXT: -D#,VALUE=10 (parsed as: {{\[\[#,VALUE:10\]\]}}) +; NUMERRCLI-NEXT: {{^ \^$}} Index: llvm/test/FileCheck/numeric-expression.txt =================================================================== --- /dev/null +++ llvm/test/FileCheck/numeric-expression.txt @@ -0,0 +1,346 @@ +; RUN: FileCheck -input-file %s %s +; RUN: not FileCheck -check-prefixes ERR,ERR1 -input-file %s %s 2>&1 | FileCheck -check-prefix ERRMSG1 %s +; RUN: not FileCheck -check-prefixes ERR,ERR2 -input-file %s %s 2>&1 | FileCheck -check-prefix ERRMSG2 %s +; RUN: not FileCheck -check-prefixes ERR,ERR3 -input-file %s %s 2>&1 | FileCheck -check-prefix ERRMSG3 %s +; RUN: not FileCheck -check-prefixes ERR,ERR4 -input-file %s %s 2>&1 | FileCheck -check-prefix ERRMSG4 %s +; RUN: not FileCheck -check-prefix ERR5 -input-file %s %s 2>&1 | FileCheck -check-prefix ERRMSG5 %s +; RUN: not FileCheck -check-prefixes CHECK,ERR6 -input-file %s %s 2>&1 | FileCheck -check-prefix ERRMSG6 %s +; RUN: not FileCheck -check-prefix ERR7 -input-file %s %s 2>&1 | FileCheck -check-prefix ERRMSG7 %s +; RUN: not FileCheck -check-prefix ERR8 -input-file %s %s 2>&1 | FileCheck -check-prefix ERRMSG8 %s + +; We ensure we attemt to match all lines with digits by wrapping them between +; START and END and using CHECK-NEXT to ensure each line is covered. + + +; Numeric variable definition with default matching format +DEF DEFAULT FMT +11 +; CHECK-LABEL: DEF DEFAULT FMT +; CHECK-NEXT: [[#VAR1:]] + + +; Name conflict between Numeric variable definition and pattern variable definition +PATVAR NUMVAR CONFLICT +11 +foo1bar +foo2bar +22 +; ERR-LABEL: PATVAR NUMVAR CONFLICT +; ERR1-NEXT: [[#PATVAR1:]] +; ERR1-NEXT: [[PATVAR1:foo1.*]] +; ERR2: [[PATVAR2:foo2.*]] +; ERR2-NEXT: [[#PATVAR2:]] + +; ERRMSG1: numeric-expression.txt:[[#@LINE-4]]:16: error: Numeric variable with same name 'PATVAR1' exists +; ERRMSG1-NEXT: ; {{E}}RR1-NEXT: {{\[\[PATVAR1:foo1\.\*\]\]}} +; ERRMSG2: numeric-expression.txt:[[#@LINE-4]]:17: error: Pattern variable with same name 'PATVAR2' exists +; ERRMSG2-NEXT: ; {{E}}RR2-NEXT: {{\[\[#PATVAR2:\]\]}} + + +;; Numeric variable definition with default matching format in alternate spacing +DEF DEFAULT FMT ALT SPC +11 +11 +11 +; CHECK-LABEL: DEF DEFAULT FMT ALT SPC +; CHECK-NEXT: [[# VAR1a:]] +; CHECK-NEXT: [[# VAR1b :]] +; CHECK-NEXT: [[# VAR1c : ]] + +; Numeric variable definition with explicit matching format +DEF FMT +-30 +c +D +; CHECK-LABEL: DEF FMT +; CHECK-NEXT: [[#%d,VAR2:]] +; CHECK-NEXT: [[#%x,VAR3:]] +; CHECK-NEXT: [[#%X,VAR4:]] + + +; Numeric variable definition with unsupported matching format +DEF INVALID FMT +INVVAR1=a +INVVAR2=11 +; ERR-LABEL: DEF INVALID FMT +; ERR3-NEXT: INVVAR1=[[#%c,INVVAR1:]] +; ERR4-NEXT: INVVAR2=[[#%hhd,INVVAR2:]] +; ERRMSG3: numeric-expression.txt:[[#@LINE-2]]:26: error: Invalid format specifier in numeric expression +; ERRMSG3-NEXT: ; {{E}}RR3-NEXT: INVVAR1={{\[\[#%c,INVVAR1:\]\]}} +; ERRMSG3-NEXT: {{^ \^$}} +; ERRMSG4: numeric-expression.txt:[[#@LINE-4]]:26: error: Invalid format specifier in numeric expression +; ERRMSG4-NEXT: ; {{E}}RR4-NEXT: INVVAR2={{\[\[#%hhd,INVVAR2:\]\]}} +; ERRMSG4-NEXT: {{^ \^$}} + + +; Numeric variable definition with explicit matching format in alternate spacing +DEF FMT ALT SPC +-30 +-30 +-30 +-30 +-30 +; CHECK-LABEL: DEF FMT ALT SPC +; CHECK-NEXT: [[#%d, VAR2a:]] +; CHECK-NEXT: [[# %d, VAR2b:]] +; CHECK-NEXT: [[# %d , VAR2c:]] +; CHECK-NEXT: [[# %d , VAR2d :]] +; CHECK-NEXT: [[# %d , VAR2e : ]] + +; Numeric expressions in explicit matching format and default matching rule using +; variables defined on other lines +USE DEF FMT IMPL MATCH +11 +12 +10 +-30 +-29 +-31 +c +d +b +1a +1a +D +E +C +1B +1B +; CHECK-LABEL: USE DEF FMT IMPL MATCH +; CHECK-NEXT: [[#%u,VAR1]] +; CHECK-NEXT: [[#%u,VAR1+1]] +; CHECK-NEXT: [[#%u,VAR1-1]] +; CHECK-NEXT: [[#%d,VAR2]] +; CHECK-NEXT: [[#%d,VAR2+1]] +; CHECK-NEXT: [[#%d,VAR2-1]] +; CHECK-NEXT: [[#%x,VAR3]] +; CHECK-NEXT: [[#%x,VAR3+1]] +; CHECK-NEXT: [[#%x,VAR3-1]] +; CHECK-NEXT: [[#%x,VAR3+0xe]] +; CHECK-NEXT: [[#%x,VAR3+0xE]] +; CHECK-NEXT: [[#%X,VAR4]] +; CHECK-NEXT: [[#%X,VAR4+1]] +; CHECK-NEXT: [[#%X,VAR4-1]] +; CHECK-NEXT: [[#%X,VAR4+0xe]] +; CHECK-NEXT: [[#%X,VAR4+0xE]] + + +; Numeric expression using undefined variable +UNDEF VAR USE +UNDEFVAR: 11 +; ERR5-LABEL: UNDEF VAR USE +; ERR5-NEXT: UNDEFVAR: [[#UNDEFVAR]] +; ERRMSG5: numeric-expression.txt:[[#@LINE-1]]:27: error: Using undefined numeric variable 'UNDEFVAR' +; ERRMSG5-NEXT: ; {{E}}RR5-NEXT: UNDEFVAR: {{\[\[#UNDEFVAR\]\]}} +; ERRMSG5-NEXT: {{^ \^$}} + + +; Numeric expression with unsupported operator +INVALID OPERATOR +VAR1*2: 22 +; ERR6-LABEL: INVALID OPERATOR +; ERR6-NEXT: VAR1*2: [[#VAR1*2]] +; ERRMSG6: numeric-expression.txt:[[#@LINE-1]]:29: error: Unsupported numeric operation '*' +; ERRMSG6-NEXT: ; {{E}}RR6-NEXT: VAR1*2: {{\[\[#VAR1\*2\]\]}} +; ERRMSG6-NEXT: {{^ \^$}} + + +; Numeric expression with overflow +OVERFLOW +BIGVAR=10000000000000000 +; ERR7-LABEL: OVERFLOW +; ERR7-NEXT: BIGVAR: [[#BIGVAR:0x8000000000000000+0x8000000000000000]] +; ERRMSG7: numeric-expression.txt:[[#@LINE-1]]:25: error: Failed to evaluate numeric expression known at parse time +; ERRMSG7-NEXT: {{E}}RR7-NEXT: BIGVAR: {{\[\[#BIGVAR:0x8000000000000000\+0x8000000000000000\]\]}} +; ERRMSG7-NEXT: {{^ \^$}} + + +; Numeric expression with underflow +UNDERFLOW +TINYVAR=-10000000000000000 +; ERR8-LABEL: UNDERFLOW +; ERR8-NEXT: TINYVAR: [[#%d,TINYVAR:-0x8000000000000000-0x8000000000000000]] +; ERRMSG8: numeric-expression.txt:[[#@LINE-1]]:26: error: Failed to evaluate numeric expression known at parse time +; ERRMSG8-NEXT: {{E}}RR8-NEXT: TINYVAR: {{\[\[#%d,TINYVAR:-0x8000000000000000-0x8000000000000000\]\]}} +; ERRMSG8-NEXT: {{^ \^$}} + + +; Numeric expressions in explicit matching format and default matching rule using +; variables defined on other lines in alternate spacing +USE EXPL FMT IMPL MATCH ALT SPC +11 +11 +11 +12 +12 +12 +12 +12 +12 +10 +10 +10 +10 +10 +10 +; CHECK-LABEL: USE EXPL FMT IMPL MATCH ALT SPC +; CHECK-NEXT: [[#%u, VAR1]] +; CHECK-NEXT: [[# %u, VAR1]] +; CHECK-NEXT: [[# %u, VAR1 ]] +; CHECK-NEXT: [[#%u, VAR1+1]] +; CHECK-NEXT: [[# %u, VAR1+1]] +; CHECK-NEXT: [[# %u , VAR1+1]] +; CHECK-NEXT: [[# %u , VAR1 +1]] +; CHECK-NEXT: [[# %u , VAR1 + 1]] +; CHECK-NEXT: [[# %u , VAR1 + 1 ]] +; CHECK-NEXT: [[#%u, VAR1-1]] +; CHECK-NEXT: [[# %u, VAR1-1]] +; CHECK-NEXT: [[# %u , VAR1-1]] +; CHECK-NEXT: [[# %u , VAR1 -1]] +; CHECK-NEXT: [[# %u , VAR1 - 1]] +; CHECK-NEXT: [[# %u , VAR1 - 1 ]] + + +; Numeric expressions in implicit matching format and default matching rule using +; variables defined on other lines +USE IMPL FMT IMPL MATCH +11 +12 +10 +-30 +-29 +-31 +c +d +b +1a +1a +D +E +C +1B +1B +; CHECK-LABEL: USE IMPL FMT IMPL MATCH +; CHECK-NEXT: [[#VAR1]] +; CHECK-NEXT: [[#VAR1+1]] +; CHECK-NEXT: [[#VAR1-1]] +; CHECK-NEXT: [[#VAR2]] +; CHECK-NEXT: [[#VAR2+1]] +; CHECK-NEXT: [[#VAR2-1]] +; CHECK-NEXT: [[#VAR3]] +; CHECK-NEXT: [[#VAR3+1]] +; CHECK-NEXT: [[#VAR3-1]] +; CHECK-NEXT: [[#VAR3+0xe]] +; CHECK-NEXT: [[#VAR3+0xE]] +; CHECK-NEXT: [[#VAR4]] +; CHECK-NEXT: [[#VAR4+1]] +; CHECK-NEXT: [[#VAR4-1]] +; CHECK-NEXT: [[#VAR4+0xe]] +; CHECK-NEXT: [[#VAR4+0xE]] + + +; Numeric expressions in default matching format and default matching rule +; using variables defined on other lines and an immediate interpreted as an +; unsigned value +; Note: 9223372036854775819 = 0x8000000000000000 + 11 +USE IMPL FMT IMPL MATCH UNSIGNED IMM +9223372036854775819 +; CHECK-LABEL: USE IMPL FMT IMPL MATCH UNSIGNED IMM +; CHECK-NEXT: [[#VAR1+0x8000000000000000]] + + +; Numeric expressions in default matching format and explicit matching rule using +; variables defined on other lines +USE DEF FMT EXPL MATCH +11 +11 +11 +; CHECK-LABEL: USE DEF FMT EXPL MATCH +; CHECK-NEXT: [[#==VAR1]] +; CHECK-NEXT: [[# ==VAR1]] +; CHECK-NEXT: [[# == VAR1]] + + +; Numeric expressions with conversion matching format and implicit matching rule +; using variables defined on other lines +USE CONV FMT IMPL MATCH +b +B +12 +12 +13 +13 +; CHECK-LABEL: USE CONV FMT IMPL MATCH +; CHECK-NEXT: [[# %x, VAR1]] +; CHECK-NEXT: [[# %X, VAR1]] +; CHECK-NEXT: [[# %u, VAR3]] +; CHECK-NEXT: [[# %d, VAR3]] +; 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 +1 3 +1 2 +; CHECK-LABEL: REDEF SAME 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]] Index: llvm/test/FileCheck/regex-scope.txt =================================================================== --- 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 Index: llvm/test/FileCheck/var-scope.txt =================================================================== --- /dev/null +++ llvm/test/FileCheck/var-scope.txt @@ -0,0 +1,26 @@ +// RUN: FileCheck -input-file %s %s +// RUN: FileCheck -check-prefixes CHECK,GLOBAL -input-file %s %s +// RUN: FileCheck -check-prefixes CHECK,LOCAL,LOCAL3 -input-file %s %s +// RUN: FileCheck -check-prefixes CHECK,GLOBAL --enable-var-scope -input-file %s %s +// RUN: not FileCheck -check-prefixes CHECK,LOCAL,LOCAL1 --enable-var-scope -input-file %s %s +// RUN: not FileCheck -check-prefixes CHECK,LOCAL,LOCAL2 --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]] +; LOCAL2: [[LOCAL]][[#LOCNUM+2]] +; GLOBAL: [[$GLOBAL]][[#GLOBNUM+2]] Index: llvm/test/FileCheck/verbose.txt =================================================================== --- llvm/test/FileCheck/verbose.txt +++ llvm/test/FileCheck/verbose.txt @@ -29,6 +29,39 @@ VV-NEXT: {{^}}bar{{$}} VV-NEXT: {{^}}^{{$}} +VAR1=42 +VAR1:42 VAR1=12 VAR1:12 +CHECK: VAR1=[[#VAR1:]] +CHECK-NOT: [[#VAR1 + 1]] +CHECK-NEXT: VAR1:[[#VAR1]] VAR1=[[#VAR1:]] VAR1:[[#VAR1]] + +V: verbose.txt:[[#@LINE-4]]:8: remark: {{C}}HECK: expected string found in input +V-NEXT: {{C}}HECK: {{VAR1=[[][[]#VAR1:[]][]]$}} +V-NEXT: {{^ \^$}} +V-NEXT: verbose.txt:[[#@LINE-9]]:1: note: found here +V-NEXT: {{^}}VAR1=42{{$}} +V-NEXT: {{^}}^~~~~~~{{$}} + +V-NEXT: verbose.txt:[[#@LINE-9]]:13: remark: {{C}}HECK-NEXT: expected string found in input +V-NEXT: {{C}}HECK-NEXT: {{VAR1:[[][[]#VAR1[]][]] VAR1=[[][[]#VAR1:[]][]] VAR1:[[][[]#VAR1[]][]]$}} +V-NEXT: {{^ \^$}} +V-NEXT: verbose.txt:[[#VARLIN1:@LINE-15]]:1: note: found here +V-NEXT: {{^}}VAR1:42 VAR1=12 VAR1:12{{$}} +V-NEXT: {{^}}^~~~~~~~~~~~~~~~~~~~~~~{{$}} +VV-NEXT: verbose.txt:[[#VARLIN1]]:1: note: with numeric expression "VAR1" equal to "42" +VV-NEXT: {{^}}VAR1:42 VAR1=12 VAR1:12{{$}} +VV-NEXT: {{^}}^~~~~~~~~~~~~~~~~~~~~~~{{$}} +VV-NEXT: verbose.txt:[[#VARLIN1]]:1: note: with numeric expression "VAR1" equal to "12" +VV-NEXT: {{^}}VAR1:42 VAR1=12 VAR1:12{{$}} +VV-NEXT: {{^}}^~~~~~~~~~~~~~~~~~~~~~~{{$}} + +VV-NEXT: verbose.txt:[[#@LINE-23]]:12: remark: {{C}}HECK-NOT: excluded string not found in input +VV-NEXT: {{C}}HECK-NOT: {{[[][[]#VAR1 [+] 1[]][]]$}} +VV-NEXT: {{^ \^$}} +VV-NEXT: verbose.txt:[[#@LINE-28]]:1: note: scanning from here +VV-NEXT: {{^}}VAR1:42 VAR1=12 VAR1:12{{$}} +VV-NEXT: {{^}}^{{$}} + before empty after empty