diff --git a/llvm/docs/CommandGuide/FileCheck.rst b/llvm/docs/CommandGuide/FileCheck.rst --- a/llvm/docs/CommandGuide/FileCheck.rst +++ b/llvm/docs/CommandGuide/FileCheck.rst @@ -568,15 +568,26 @@ FileCheck Numeric Variables and Expressions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:program:`FileCheck` also allows to check for numeric values satisfying a -numeric expression constraint based on numeric variables. This allows to -capture numeric relations between two numbers in a text to check, such as the -the need for consecutive registers to be used. +: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 ``NUMVAR`` +is the name of the numeric variable to define to the matching value. + +For example: + +.. code-block:: llvm + + ; CHECK: mov r[[#REG:]], 42 + +would match ``mov r5, 42`` and set ``REG`` to the value ``5``. The syntax to check a numeric expression constraint is ``[[#]]`` where: -*```` is the name of a numeric variable defined on the command line. +*```` is the name of a numeric variable defined on a previous line. *```` is an optional numeric operation to perform on the value of ````. Currently supported numeric operations are ``+`` and ``-``. @@ -589,7 +600,7 @@ .. code-block:: llvm - ; CHECK: add r[[#REG]], r[[#REG]], r[[#REG+1]] + ; CHECK: add r[[#REG:]], r[[#REG]], r[[#REG+1]] The above example would match the lines: @@ -609,6 +620,9 @@ 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 cannot use +a numeric variable defined on the same line. + FileCheck Pseudo Numeric Variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/llvm/include/llvm/Support/FileCheck.h b/llvm/include/llvm/Support/FileCheck.h --- a/llvm/include/llvm/Support/FileCheck.h +++ b/llvm/include/llvm/Support/FileCheck.h @@ -59,7 +59,9 @@ }; /// Class representing a numeric variable with a given value in the AST of a -/// numeric expression. +/// numeric expression. Each definition of a variable gets its own instance of +/// this class, variable uses share the same instance as the respective +/// definition. class FileCheckNumExprVar { private: /// Name of the numeric variable. @@ -71,7 +73,15 @@ /// Value of numeric variable if defined. uint64_t Value; + /// Line number where this variable is defined. Used to determine whether a + /// variable is defined on the same line as a given use. + unsigned DefLineNumber; + public: + /// Constructor for a variable \p Name defined at line \p DefLineNumber. + FileCheckNumExprVar(StringRef Name, unsigned DefLineNumber) + : Name(Name), Defined(false), DefLineNumber(DefLineNumber) {} + /// Constructor for numeric variable \p Name with a known \p Value at parse /// time (e.g. the @LINE numeric variable). FileCheckNumExprVar(StringRef Name, uint64_t Value) @@ -90,6 +100,9 @@ /// Clear value of this numeric variable. Return whether value was already /// undefined. bool clearValue(); + + /// Return line number where this variable is defined. + unsigned getDefLineNumber() { return DefLineNumber; } }; /// Type of functions evaluating a given binary operation. @@ -227,6 +240,18 @@ }; } // 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 variable whose value is being defined. +struct FileCheckNumExprMatch { + /// Pointer to class representing the numeric variable whose value is being + /// defined. + std::shared_ptr NumVarDef; + + /// Parenthesized capture number for this numeric variable definition. + unsigned CaptureParen; +}; + /// Class holding the FileCheckPattern global state, shared by all patterns: /// tables holding values of variables and whether they are defined or not at /// any given time in the matching process. @@ -248,7 +273,9 @@ /// 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. + /// 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: @@ -301,6 +328,11 @@ /// iterating over values. std::map VariableDefs; + /// Holds the capture parentheses number and pointer to corresponding + /// FileCheckNumExprVar class instance of all numeric variable definitions. + /// Used to set the matched value of all those variables. + std::map NumericVariableDefs; + /// Pointer to a class instance holding the global state shared by all /// patterns: /// - tables with the values of live pattern and numeric variables at @@ -311,13 +343,15 @@ Check::FileCheckType CheckTy; - /// Contains the number of line this pattern is in. + /// Line number for this CHECK pattern. Used to determine whether a variable + /// definition is made on an earlier line to the one with this CHECK. unsigned LineNumber; public: explicit FileCheckPattern(Check::FileCheckType Ty, - FileCheckPatternContext *Context) - : Context(Context), CheckTy(Ty) {} + FileCheckPatternContext *Context, + unsigned LineNumber) + : Context(Context), CheckTy(Ty), LineNumber(LineNumber) {} /// Returns the location in source code. SMLoc getLoc() const { return PatternLoc; } @@ -327,12 +361,15 @@ FileCheckPatternContext *getContext() const { return Context; } static bool parseVariable(StringRef Str, bool &IsPseudo, unsigned &TrailIdx); + bool parseNumericVariable(StringRef &Expr, StringRef &Name, bool IsDefinition, + const SourceMgr &SM) const; std::shared_ptr - parseNumericExpression(StringRef Name, bool IsPseudo, StringRef Trailer, + 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) const; + const FileCheckRequest &Req); + 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, @@ -349,6 +386,10 @@ void AddBackrefToRegEx(unsigned BackrefNum); unsigned computeMatchDistance(StringRef Buffer) const; size_t FindRegexVarEnd(StringRef Str, SourceMgr &SM); + + /// Numeric expression parsing helpers. + std::shared_ptr + ParseFileCheckBinop(StringRef &Expr, const SourceMgr &SM) const; }; //===----------------------------------------------------------------------===// diff --git a/llvm/lib/Support/FileCheck.cpp b/llvm/lib/Support/FileCheck.cpp --- a/llvm/lib/Support/FileCheck.cpp +++ b/llvm/lib/Support/FileCheck.cpp @@ -75,6 +75,8 @@ /// matched. llvm::Optional FileCheckPatternSubst::getSubstitute() const { if (IsNumExpr) { + assert(NumExpr->getAST() != nullptr && + "Substituting empty numeric expression"); llvm::Optional EvaluatedValue = NumExpr->getAST()->eval(); if (!EvaluatedValue.hasValue()) return llvm::None; @@ -143,7 +145,7 @@ } /// Parsing helper function that strips all leading whitespace from \p s. -static inline void SkipWhitespace(StringRef &s) { s = s.ltrim(" \t"); } +static inline void skipWhitespace(StringRef &s) { s = s.ltrim(" \t"); } /// Parsing helper function that strips the first character in \p s and returns /// it. @@ -153,48 +155,98 @@ return c; } -static uint64_t doAdd(uint64_t Opl, uint64_t Opr) { return Opl + Opr; } -static uint64_t doSub(uint64_t Opl, uint64_t Opr) { return 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. -std::shared_ptr -FileCheckPattern::parseNumericExpression(StringRef Name, bool IsPseudo, - StringRef Trailer, - const SourceMgr &SM) const { - std::shared_ptr NumExprAST; +/// Parse \p Expr for use or definition (if \p IsDefinition is true) of a +/// numeric variable. Return whether parsing fails in which case errors are +/// reported on \p SM. Otherwise, the name of the numeric variable is set in +/// \p Name. +bool FileCheckPattern::parseNumericVariable(StringRef &Expr, StringRef &Name, + bool IsDefinition, + const SourceMgr &SM) const { + bool IsPseudo; + unsigned TrailIdx; + + if (parseVariable(Expr, IsPseudo, TrailIdx)) { + SM.PrintMessage(SMLoc::getFromPointer(Expr.data()), SourceMgr::DK_Error, + "Invalid variable name"); + return true; + } + Name = Expr.substr(0, TrailIdx); + Expr = Expr.substr(TrailIdx); if (IsPseudo && !Name.equals("@LINE")) { SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, "Invalid pseudo numeric variable '" + Name + "'"); - return nullptr; + return true; } - - // 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()) { + if (IsDefinition && IsPseudo) { SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, - "Using undefined numeric variable '" + Name + "'"); - return nullptr; + "Definition of pseudo variable '" + Name + "' unsupported"); + return true; + } + + if (IsDefinition) { + // Detect collision between pattern and numeric variable when the latter is + // created later than the former. + if (Context->DefinedVariableTable.find(Name) != + Context->DefinedVariableTable.end()) { + SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, + "Pattern variable with name '" + Name + + "' already exists"); + return true; + } + + return false; + } 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()) { + SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, + "Using undefined numeric variable '" + Name + "'"); + return true; + } + + std::shared_ptr NumVar = git->second; + if (!IsPseudo && NumVar->getDefLineNumber() == LineNumber) { + SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, + "Numeric variable '" + NumVar->getName() + + "' defined on the same line"); + return true; + } + + return false; } +} + +static uint64_t doAdd(uint64_t Opl, uint64_t Opr) { return Opl + Opr; } + +static uint64_t doSub(uint64_t Opl, uint64_t Opr) { return Opl - Opr; } - std::shared_ptr Opl = git->second; +/// Parse \p Expr for a binary operation. +/// Return the class representing that binary operation in the AST of the +/// numeric expression or nullptr if parsing fails in which case errors +/// are reported on \p SM. +std::shared_ptr +FileCheckPattern::ParseFileCheckBinop(StringRef &Expr, + const SourceMgr &SM) const { + StringRef Name; + if (parseNumericVariable(Expr, Name, false, SM)) + // Error reporting done in parseNumericVariable. + return nullptr; + std::shared_ptr Opl = + Context->GlobalNumericVariableTable.find(Name)->second; // Check if this is a supported operation and selection function to perform // it. - SkipWhitespace(Trailer); - if (Trailer.empty()) { - NumExprAST = std::make_shared(doAdd, Opl, 0); - return std::make_shared(NumExprAST); - } - SMLoc oploc = SMLoc::getFromPointer(Trailer.data()); - char Operator = next(Trailer); + skipWhitespace(Expr); + if (Expr.empty()) + return std::make_shared(doAdd, Opl, 0); + SMLoc oploc = SMLoc::getFromPointer(Expr.data()); + char Operator = next(Expr); binop_eval_t EvalBinop; switch (Operator) { case '+': @@ -211,27 +263,75 @@ } // Parse right operand. - SkipWhitespace(Trailer); - if (Trailer.empty()) { - SM.PrintMessage(SMLoc::getFromPointer(Trailer.data()), SourceMgr::DK_Error, + skipWhitespace(Expr); + if (Expr.empty()) { + SM.PrintMessage(SMLoc::getFromPointer(Expr.data()), SourceMgr::DK_Error, "Missing operand in numeric expression"); return nullptr; } uint64_t Opr; - if (Trailer.consumeInteger(10, Opr)) { - SM.PrintMessage(SMLoc::getFromPointer(Trailer.data()), SourceMgr::DK_Error, - "Invalid offset in numeric expression '" + Trailer + "'"); + if (Expr.consumeInteger(10, Opr)) { + SM.PrintMessage(SMLoc::getFromPointer(Expr.data()), SourceMgr::DK_Error, + "Invalid offset in numeric expression '" + Expr + "'"); return nullptr; } - SkipWhitespace(Trailer); - if (!Trailer.empty()) { - SM.PrintMessage(SMLoc::getFromPointer(Trailer.data()), SourceMgr::DK_Error, + skipWhitespace(Expr); + if (!Expr.empty()) { + SM.PrintMessage(SMLoc::getFromPointer(Expr.data()), SourceMgr::DK_Error, "Unexpected characters at end of numeric expression '" + - Trailer + "'"); + Expr + "'"); return nullptr; } - NumExprAST = std::make_shared(EvalBinop, Opl, Opr); + 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 { + std::shared_ptr NumExprAST; + + // Parse numeric variable definition. + NumVarDef.reset(); + size_t DefEnd = Expr.find(':'); + if (DefEnd != StringRef::npos) { + StringRef DefExpr = Expr.substr(0, DefEnd); + StringRef UseExpr = Expr = Expr.substr(DefEnd + 1); + + skipWhitespace(DefExpr); + StringRef Name; + if (parseNumericVariable(DefExpr, Name, true /*IsDefinition*/, SM)) + // Invalid variable definition. Error reporting done in parsing function. + return nullptr; + + NumVarDef = std::make_shared(Name, this->LineNumber); + + skipWhitespace(DefExpr); + if (!DefExpr.empty()) { + SM.PrintMessage(SMLoc::getFromPointer(DefExpr.data()), + SourceMgr::DK_Error, + "Invalid numeric variable definition"); + return nullptr; + } + skipWhitespace(UseExpr); + if (!UseExpr.empty()) { + SM.PrintMessage( + SMLoc::getFromPointer(UseExpr.data()), SourceMgr::DK_Error, + "Unexpected string after variable definition: '" + UseExpr + "'"); + return nullptr; + } + } else { + // Parse numeric expression itself. + skipWhitespace(Expr); + NumExprAST = ParseFileCheckBinop(Expr, SM); + if (NumExprAST == nullptr) + return nullptr; + } + return std::make_shared(NumExprAST); } @@ -242,16 +342,15 @@ /// the input file from which the pattern string was read. Returns true in /// case of an error, false otherwise. bool FileCheckPattern::ParsePattern(StringRef PatternStr, StringRef Prefix, - SourceMgr &SM, unsigned LineNumber, - const FileCheckRequest &Req) { + SourceMgr &SM, + const FileCheckRequest &Req) { bool MatchFullLinesHere = Req.MatchFullLines && CheckTy != Check::CheckNot; - this->LineNumber = LineNumber; PatternLoc = SMLoc::getFromPointer(PatternStr.data()); // Create fake @LINE pseudo variable definition. StringRef LinePseudo = "@LINE"; - uint64_t LineNumber64 = LineNumber; + uint64_t LineNumber64 = this->LineNumber; auto LinePseudoVar = std::make_shared(LinePseudo, LineNumber64); Context->GlobalNumericVariableTable[LinePseudo] = LinePseudoVar; @@ -334,9 +433,9 @@ // 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 only have the - // substitution mode. Numeric variable names have the same restriction as - // their pattern variable counterpart. + // to catch some common errors. Numeric expressions also have the + // definition and substitution modes and 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("#"); @@ -356,68 +455,84 @@ MatchStr = MatchStr.substr(0, End); PatternStr = PatternStr.substr(End + 4 + (int)IsNumExpr); - if (IsNumExpr) - SkipWhitespace(MatchStr); - else { + 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) { size_t SpacePos = MatchStr.find_first_of(" \t"); if (SpacePos != StringRef::npos) { SM.PrintMessage(SMLoc::getFromPointer(MatchStr.data() + SpacePos), SourceMgr::DK_Error, "Unexpected whitespace"); return true; } - } - - // Get the regex name (e.g. "foo") and verify it is well formed. - bool IsPseudo; - unsigned TrailIdx; - if (parseVariable(MatchStr, IsPseudo, TrailIdx)) { - SM.PrintMessage(SMLoc::getFromPointer(MatchStr.data()), - SourceMgr::DK_Error, "Invalid variable name"); - return true; - } - - unsigned SubstInsertIdx = RegExStr.size(); - std::shared_ptr NumExpr; - - StringRef Name = MatchStr.substr(0, TrailIdx); - StringRef Trailer = MatchStr.substr(TrailIdx); - size_t DefSepIdx = Trailer.find(":"); - bool IsVarDef = (DefSepIdx != StringRef::npos); - if (IsVarDef) { - if (IsPseudo || !Trailer.consume_front(":")) { + // 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 pattern variable definition"); + SourceMgr::DK_Error, "Invalid variable name"); return true; } - // Detect collision between pattern and numeric variable when the former - // is created later than the latter. - if (Context->GlobalNumericVariableTable.find(Name) != - Context->GlobalNumericVariableTable.end()) { - SM.PrintMessage( - SMLoc::getFromPointer(MatchStr.data()), SourceMgr::DK_Error, - "Numeric variable with name '" + Name + "' already exists"); - return true; - } + StringRef Name = MatchStr.substr(0, TrailIdx); + StringRef Trailer = MatchStr.substr(TrailIdx); + size_t DefSepIdx = Trailer.find(":"); + IsVarDef = (DefSepIdx != StringRef::npos); + IsNumExpr = IsPseudo; + + if (IsVarDef) { + if ((IsPseudo || !Trailer.consume_front(":"))) { + SM.PrintMessage(SMLoc::getFromPointer(MatchStr.data()), + SourceMgr::DK_Error, + "Invalid name in pattern variable definition"); + return true; + } + + // Detect collision between pattern and numeric variable when the + // former is created later than the latter. + if (Context->GlobalNumericVariableTable.find(Name) != + Context->GlobalNumericVariableTable.end()) { + SM.PrintMessage( + SMLoc::getFromPointer(MatchStr.data()), SourceMgr::DK_Error, + "Numeric variable with name '" + Name + "' already exists"); + return true; + } + DefName = Name; + MatchRegexp = Trailer; + } else + SubstStr = MatchStr; } - if (IsNumExpr || (!IsVarDef && IsPseudo)) { - NumExpr = parseNumericExpression(Name, IsPseudo, Trailer, SM); + // Parse numeric expression. + if (IsNumExpr) { + NumExpr = parseNumericExpression(MatchStr, NumVarDef, SM); if (NumExpr == nullptr) return true; IsNumExpr = true; + if (NumVarDef != nullptr) { + IsVarDef = true; + DefName = NumVarDef->getName(); + MatchRegexp = StringRef("[0-9]+"); + } else + SubstStr = MatchStr; } // Handle variable use: [[foo]] and [[#]]. if (!IsVarDef) { // Handle use of pattern variables that were defined earlier on the - // same line by emitting a backreference. - if (!IsNumExpr && VariableDefs.find(Name) != VariableDefs.end()) { - unsigned CaptureParen = VariableDefs[Name]; + // same line by emitting a backreference. Numeric expressions do not + // support using a numeric variable defined on the same line. + if (!IsNumExpr && VariableDefs.find(SubstStr) != VariableDefs.end()) { + unsigned CaptureParen = VariableDefs[SubstStr]; if (CaptureParen < 1 || CaptureParen > 9) { - SM.PrintMessage(SMLoc::getFromPointer(Name.data()), + SM.PrintMessage(SMLoc::getFromPointer(SubstStr.data()), SourceMgr::DK_Error, "Can't back-reference more than 9 variables"); return true; @@ -428,26 +543,37 @@ // CHECK pattern or use of a numeric expression. FileCheckPatternSubst Subst = IsNumExpr - ? FileCheckPatternSubst(Context, MatchStr, NumExpr, + ? FileCheckPatternSubst(Context, SubstStr, NumExpr, SubstInsertIdx) - : FileCheckPatternSubst(Context, MatchStr, SubstInsertIdx); + : FileCheckPatternSubst(Context, SubstStr, SubstInsertIdx); Substs.push_back(Subst); } continue; } - // Handle [[foo:.*]]. - VariableDefs[Name] = CurParen; - // Mark pattern variable as defined to detect collision between pattern - // and numeric variable in DefineCmdlineVariables when the latter is - // created later than the former. We cannot reuse GlobalVariableTable for - // that by populating it with an empty string since we would then loose - // the ability to detect use of undefined variable in Match(). - Context->DefinedVariableTable[Name] = true; + // Handle variable definition: [[:(...)]] and [[#(...):(...)]]. + if (IsNumExpr) { + struct FileCheckNumExprMatch NumExprDef = {NumVarDef, CurParen}; + NumericVariableDefs[DefName] = NumExprDef; + // This store is done here rather than in match() to allow + // parseNumericVariable() to get the pointer to the class instance + // of the right variable definition corresponding to a given numeric + // variable use. + Context->GlobalNumericVariableTable[DefName] = NumVarDef; + } else { + VariableDefs[DefName] = CurParen; + // Mark pattern variable as defined to detect collision between pattern + // and numeric variable in parseNumericVariable and + // DefineCmdlineVariables when the latter is created later than the + // former. We cannot reuse GlobalVariableTable for that by populating + // it with an empty string since we would then loose the ability to + // detect use of undefined variable in match(). + Context->DefinedVariableTable[DefName] = true; + } RegExStr += '('; ++CurParen; - if (AddRegExToRegEx(Trailer, CurParen, SM)) + if (AddRegExToRegEx(MatchRegexp, CurParen, SM)) return true; RegExStr += ')'; @@ -498,7 +624,11 @@ /// /// The GlobalVariableTable StringMap provides the current values of FileCheck /// pattern variables and is updated if this match defines new values. -size_t FileCheckPattern::match(StringRef Buffer, size_t &MatchLen) const { +/// Likewise, GlobalNumericVariableTable StringMap provides the current values +/// of FileCheck numeric variables and is updated if this match defines new +/// numeric values. +size_t FileCheckPattern::match(StringRef Buffer, size_t &MatchLen, + const SourceMgr &SM) const { // If this is the EOF pattern, match it immediately. if (CheckTy == Check::CheckEOF) { MatchLen = 0; @@ -556,6 +686,24 @@ MatchInfo[VariableDef.second]; } + // If this defines any numeric variables, remember their values. + for (const auto &NumericVariableDef : NumericVariableDefs) { + assert(NumericVariableDef.second.CaptureParen < MatchInfo.size() && + "Internal paren error"); + unsigned CaptureParen = NumericVariableDef.second.CaptureParen; + std::shared_ptr NumVarDef = + NumericVariableDef.second.NumVarDef; + + StringRef MatchedValue = MatchInfo[CaptureParen]; + uint64_t Val; + if (MatchedValue.getAsInteger(10, Val)) { + SM.PrintMessage(SMLoc::getFromPointer(MatchedValue.data()), + SourceMgr::DK_Error, "Unable to represent numeric value"); + } + if (NumVarDef->setValue(Val)) + assert(false && "Numeric variable redefined"); + } + // Like CHECK-NEXT, CHECK-EMPTY's match range is considered to start after // the required preceding newline, which is consumed by the pattern in the // case of CHECK-EMPTY but not CHECK-NEXT. @@ -994,9 +1142,9 @@ SM.AddNewSourceBuffer(std::move(CmdLine), SMLoc()); ImplicitNegativeChecks.push_back( - FileCheckPattern(Check::CheckNot, PatternContext)); + FileCheckPattern(Check::CheckNot, PatternContext, 0)); ImplicitNegativeChecks.back().ParsePattern(PatternInBuffer, - "IMPLICIT-CHECK", SM, 0, Req); + "IMPLICIT-CHECK", SM, Req); } std::vector DagNotMatches = ImplicitNegativeChecks; @@ -1057,8 +1205,8 @@ SMLoc PatternLoc = SMLoc::getFromPointer(Buffer.data()); // Parse the pattern. - FileCheckPattern P(CheckTy, PatternContext); - if (P.ParsePattern(Buffer.substr(0, EOL), UsedPrefix, SM, LineNumber, Req)) + FileCheckPattern P(CheckTy, PatternContext, LineNumber); + if (P.ParsePattern(Buffer.substr(0, EOL), UsedPrefix, SM, Req)) return true; // Verify that CHECK-LABEL lines do not define or use variables @@ -1101,9 +1249,9 @@ // 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, PatternContext), - *Req.CheckPrefixes.begin(), - SMLoc::getFromPointer(Buffer.data())); + CheckStrings.emplace_back( + FileCheckPattern(Check::CheckEOF, PatternContext, LineNumber + 1), + *Req.CheckPrefixes.begin(), SMLoc::getFromPointer(Buffer.data())); std::swap(DagNotMatches, CheckStrings.back().DagNotStrings); } @@ -1277,7 +1425,7 @@ StringRef MatchBuffer = Buffer.substr(LastMatchEnd); size_t CurrentMatchLen; // get a match at current start point - size_t MatchPos = Pat.match(MatchBuffer, CurrentMatchLen); + size_t MatchPos = Pat.match(MatchBuffer, CurrentMatchLen, SM); if (i == 1) FirstMatchPos = LastPos + MatchPos; @@ -1401,7 +1549,7 @@ assert((Pat->getCheckTy() == Check::CheckNot) && "Expect CHECK-NOT!"); size_t MatchLen = 0; - size_t Pos = Pat->match(Buffer, MatchLen); + size_t Pos = Pat->match(Buffer, MatchLen, SM); if (Pos == StringRef::npos) { PrintNoMatch(false, SM, Prefix, Pat->getLoc(), *Pat, 1, Buffer, @@ -1462,7 +1610,7 @@ // CHECK-DAG group. for (auto MI = MatchRanges.begin(), ME = MatchRanges.end(); true; ++MI) { StringRef MatchBuffer = Buffer.substr(MatchPos); - size_t MatchPosBuf = Pat.match(MatchBuffer, MatchLen); + size_t MatchPosBuf = Pat.match(MatchBuffer, MatchLen, SM); // With a group of CHECK-DAGs, a single mismatching means the match on // that group of CHECK-DAGs fails immediately. if (MatchPosBuf == StringRef::npos) { @@ -1608,6 +1756,9 @@ return true; CmdlineVariablesDefined = true; + // Dummy pattern to call parseNumericVariable + FileCheckPattern P(Check::CheckPlain, this, 0); + bool ErrorFound = false; for (const auto &CmdlineDef : CmdlineDefines) { // Numeric variable definition. @@ -1623,27 +1774,15 @@ SM.AddNewSourceBuffer(std::move(CmdLine), SMLoc()); // Parse numeric variable definition. - bool IsPseudo; - unsigned TrailIdx; size_t EqIdx = CmdlineDef.find('='); - StringRef CmdlineName = DiagCmdlineDefRef.substr(1, EqIdx - 1); - if (FileCheckPattern::parseVariable(CmdlineName, IsPseudo, TrailIdx) || - IsPseudo || TrailIdx != CmdlineName.size()) { - SM.PrintMessage(SMLoc::getFromPointer(CmdlineName.data()), - SourceMgr::DK_Error, - "Invalid name in numeric variable definition '" + - CmdlineName + "'"); - ErrorFound = true; - continue; - } - - // Detect collision between pattern and numeric variable when the latter - // is created later than the former. - if (DefinedVariableTable.find(CmdlineName) != - DefinedVariableTable.end()) { - SM.PrintMessage( - SMLoc::getFromPointer(CmdlineName.data()), SourceMgr::DK_Error, - "Pattern variable with name '" + CmdlineName + "' already exists"); + StringRef Expr = DiagCmdlineDefRef.substr(1, EqIdx - 1); + StringRef CmdlineName; + SMLoc CmdlineNameLoc = SMLoc::getFromPointer(Expr.data()); + if (P.parseNumericVariable(Expr, CmdlineName, true /*IsDefinition*/, + SM) || + !Expr.empty()) { + SM.PrintMessage(CmdlineNameLoc, SourceMgr::DK_Error, + "Invalid variable name"); ErrorFound = true; continue; } @@ -1659,10 +1798,11 @@ continue; } std::shared_ptr NumVarDef = - std::make_shared(CmdlineName, Val); + std::make_shared(CmdlineName, (unsigned)0); + NumVarDef->setValue(Val); // Record this variable definition. - GlobalNumericVariableTable[CmdlineName] = NumVarDef; + GlobalNumericVariableTable[NumVarDef->getName()] = NumVarDef; } else { // Pattern variable definition std::string Prefix = "-D"; @@ -1700,7 +1840,7 @@ // and numeric variable in DefineCmdlineVariables when the latter is // created later than the former. We cannot reuse GlobalVariableTable for // that by populating it with an empty string since we would then loose - // the ability to detect use of undefined variable in Match(). + // the ability to detect use of undefined variable in match(). DefinedVariableTable[Name] = true; } } diff --git a/llvm/test/FileCheck/defines.txt b/llvm/test/FileCheck/defines.txt --- a/llvm/test/FileCheck/defines.txt +++ b/llvm/test/FileCheck/defines.txt @@ -70,15 +70,15 @@ ; NOT-NUMERRMSG: defines.txt:[[#@LINE-10]]:1: note: found here ; NOT-NUMERRMSG: defines.txt:[[#@LINE-11]]:1: note: with numeric expression "NUMVAL" equal to "12" -; NUMERRCLIFMT: command line:1:4: error: Invalid name in numeric variable definition '10VALUE' +; NUMERRCLIFMT: command line:1:4: error: Invalid variable name ; NUMERRCLIFMT-NEXT: -D#10VALUE=10 ; NUMERRCLIFMT-NEXT: {{^ \^$}} -; NUMERRCLIPSEUDO: command line:1:4: error: Invalid name in numeric variable definition '@VALUE' +; NUMERRCLIPSEUDO: command line:1:4: error: Invalid pseudo numeric variable ; NUMERRCLIPSEUDO-NEXT: -D#@VALUE=10 ; NUMERRCLIPSEUDO-NEXT: {{^ \^$}} -; NUMERRCLITRAIL: command line:1:4: error: Invalid name in numeric variable definition 'VALUE + 2' +; NUMERRCLITRAIL: command line:1:4: error: Invalid variable name ; NUMERRCLITRAIL-NEXT: -D#VALUE + 2=10 ; NUMERRCLITRAIL-NEXT: {{^ \^$}} diff --git a/llvm/test/FileCheck/numeric-expression.txt b/llvm/test/FileCheck/numeric-expression.txt --- a/llvm/test/FileCheck/numeric-expression.txt +++ b/llvm/test/FileCheck/numeric-expression.txt @@ -1,28 +1,48 @@ -; RUN: FileCheck -D#VAR1=11 -input-file %s %s +; RUN: FileCheck -input-file %s %s ; RUN: not FileCheck -check-prefix UNDEF-USE -input-file %s %s 2>&1 | FileCheck --strict-whitespace -check-prefix UNDEF-USE-MSG %s -; RUN: not FileCheck -D#VAR1=11 -check-prefixes CHECK,INVAL-OP -input-file %s %s 2>&1 | FileCheck --strict-whitespace -check-prefix INVAL-OP-MSG %s -; RUN: not FileCheck -D#VAR1=11 -D#NUMVAR=42 -check-prefixes CONFLICT,CONFLICT1 -input-file %s %s 2>&1 | FileCheck --strict-whitespace -check-prefix CLI-INPUT-PAT-CONFLICT %s -; RUN: not FileCheck -D#VAR1=11 -D#NUMVAR=42 -DNUMVAR=foobar -check-prefix CONFLICT -input-file %s %s 2>&1 | FileCheck --strict-whitespace -check-prefix CLI-CLI-PAT-CONFLICT %s -; RUN: not FileCheck -D#VAR1=11 -DPATVAR=foobar -D#PATVAR=42 -check-prefix CONFLICT -input-file %s %s 2>&1 | FileCheck --strict-whitespace -check-prefix CLI-CLI-NUM-CONFLICT %s +; RUN: not FileCheck -check-prefixes CHECK,INVAL-OP -input-file %s %s 2>&1 | FileCheck --strict-whitespace -check-prefix INVAL-OP-MSG %s +; RUN: not FileCheck -check-prefixes CONFLICT,CONFLICT1,CONFLICT2 -input-file %s %s 2>&1 | FileCheck --strict-whitespace -check-prefix INPUT-PAT-CONFLICT %s +; RUN: not FileCheck -D#NUMVAR=42 -check-prefixes CONFLICT,CONFLICT2 -input-file %s %s 2>&1 | FileCheck --strict-whitespace -check-prefix INPUT-PAT-CONFLICT %s +; RUN: not FileCheck -D#NUMVAR=42 -DNUMVAR=foobar -check-prefix CONFLICT -input-file %s %s 2>&1 | FileCheck --strict-whitespace -check-prefix CLI-PAT-CONFLICT %s +; RUN: not FileCheck -check-prefixes CONFLICT,CONFLICT3,CONFLICT4 -input-file %s %s 2>&1 | FileCheck --strict-whitespace -check-prefix INPUT-NUM-CONFLICT %s +; RUN: not FileCheck -DPATVAR=foobar -check-prefixes CONFLICT,CONFLICT4 -input-file %s %s 2>&1 | FileCheck --strict-whitespace -check-prefix INPUT-NUM-CONFLICT %s +; RUN: not FileCheck -DPATVAR=foobar -D#PATVAR=42 -check-prefix CONFLICT -input-file %s %s 2>&1 | FileCheck --strict-whitespace -check-prefix CLI-NUM-CONFLICT %s ; We ensure we attemt to match all lines with digits by using CHECK-NEXT ; directives for the checks to ensure each line of input is covered. -; Numeric expressions using variables defined on the command-line without -; spaces +; Numeric variable definition without spaces +DEF NO SPC +11 +; CHECK-LABEL: DEF NO SPC +; CHECK-NEXT: [[#VAR1:]] + + +; Numeric variable definition in alternate spacing +DEF ALT SPC +11 +11 +11 +; CHECK-LABEL: DEF ALT SPC +; CHECK-NEXT: [[# VAR1a:]] +; CHECK-NEXT: [[# VAR1b :]] +; CHECK-NEXT: [[# VAR1c : ]] + + +; Numeric expressions using variables defined on other lines without spaces USE NO SPC 11 12 10 -; CHECK-LABEL: USE NO SPC +; CHECK-LABEL: USE ; CHECK-NEXT: [[#VAR1]] ; CHECK-NEXT: [[#VAR1+1]] ; CHECK-NEXT: [[#VAR1-1]] -; Numeric expressions using variables defined on the command-line in alternate +; Numeric expressions using variables defined on other lines in alternate ; spacing USE ALT SPC 11 @@ -79,17 +99,25 @@ ; Name conflict between Numeric variable definition and pattern variable -; definition +; definition whether from the command-line or input text PATVAR NUMVAR CONFLICT +redef1 42 foobar +redef2 42 ; CONFLICT-LABEL: PATVAR NUMVAR CONFLICT -; CONFLICT1-NEXT: [[NUMVAR:foo.*]] -; CLI-INPUT-PAT-CONFLICT: numeric-expression.txt:[[#@LINE-1]]:21: error: Numeric variable with name 'NUMVAR' already exists -; CLI-INPUT-PAT-CONFLICT-NEXT: ; {{C}}ONFLICT1-NEXT: {{\[\[NUMVAR:foo\.\*\]\]}} -; CLI-INPUT-PAT-CONFLICT-NEXT: {{^ \^$}} -; CLI-CLI-PAT-CONFLICT: command line:1:3: error: Numeric variable with name 'NUMVAR' already exists -; CLI-CLI-PAT-CONFLICT-NEXT: -DNUMVAR=foobar -; CLI-CLI-PAT-CONFLICT-NEXT: {{^ \^$}} -; CLI-CLI-NUM-CONFLICT: command line:1:4: error: Pattern variable with name 'PATVAR' already exists -; CLI-CLI-NUM-CONFLICT-NEXT: -D#PATVAR=42 -; CLI-CLI-NUM-CONFLICT-NEXT: {{^ \^$}} +; CONFLICT1-NEXT: redef1 [[#NUMVAR:]] +; CONFLICT2: [[NUMVAR:foo.*]] +; CONFLICT3: [[PATVAR:foo.*]] +; CONFLICT4: redef2 [[#PATVAR:]] +; INPUT-PAT-CONFLICT: numeric-expression.txt:[[#@LINE-3]]:16: error: Numeric variable with name 'NUMVAR' already exists +; INPUT-PAT-CONFLICT-NEXT: ; {{C}}ONFLICT2: {{\[\[NUMVAR:foo\.\*\]\]}} +; INPUT-PAT-CONFLICT-NEXT: {{^ \^$}} +; CLI-PAT-CONFLICT: command line:1:3: error: Numeric variable with name 'NUMVAR' already exists +; CLI-PAT-CONFLICT-NEXT: -DNUMVAR=foobar +; CLI-PAT-CONFLICT-NEXT: {{^ \^$}} +; INPUT-NUM-CONFLICT: numeric-expression.txt:[[#@LINE-7]]:24: error: Pattern variable with name 'PATVAR' already exists +; INPUT-NUM-CONFLICT-NEXT: ; CONFLICT4: redef2 {{\[\[#PATVAR:\]\]}} +; INPUT-NUM-CONFLICT-NEXT: {{^ \^$}} +; CLI-NUM-CONFLICT: command line:1:4: error: Pattern variable with name 'PATVAR' already exists +; CLI-NUM-CONFLICT-NEXT: -D#PATVAR=42 +; CLI-NUM-CONFLICT-NEXT: {{^ \^$}} diff --git a/llvm/test/FileCheck/var-scope.txt b/llvm/test/FileCheck/var-scope.txt --- a/llvm/test/FileCheck/var-scope.txt +++ b/llvm/test/FileCheck/var-scope.txt @@ -1,15 +1,15 @@ -// RUN: FileCheck -D#LOCNUM=1 -D'#$GLOBNUM=1' -input-file %s %s -// RUN: FileCheck -D#LOCNUM=1 -D'#$GLOBNUM=1' -check-prefixes CHECK,GLOBAL -input-file %s %s -// RUN: FileCheck -D#LOCNUM=1 -D'#$GLOBNUM=1' -check-prefixes CHECK,LOCAL3 -input-file %s %s -// RUN: FileCheck -D#LOCNUM=1 -D'#$GLOBNUM=1' -check-prefixes CHECK,GLOBAL --enable-var-scope -input-file %s %s -// RUN: not FileCheck -D#LOCNUM=1 -D#$GLOBNUM=1 -check-prefixes CHECK,LOCAL1 --enable-var-scope -input-file %s %s -// RUN: not FileCheck -D#LOCNUM=1 -D#$GLOBNUM=1 -check-prefixes CHECK,LOCAL2 --enable-var-scope -input-file %s %s -// RUN: not FileCheck -D#LOCNUM=1 -D#$GLOBNUM=1 -check-prefixes CHECK,LOCAL3 --enable-var-scope -input-file %s %s +// RUN: FileCheck -input-file %s %s +// RUN: FileCheck -check-prefixes CHECK,GLOBAL -input-file %s %s +// RUN: FileCheck -check-prefixes CHECK,LOCAL3 -input-file %s %s +// RUN: FileCheck -check-prefixes CHECK,GLOBAL --enable-var-scope -input-file %s %s +// RUN: not FileCheck -check-prefixes CHECK,LOCAL1 --enable-var-scope -input-file %s %s +// RUN: not FileCheck -check-prefixes CHECK,LOCAL2 --enable-var-scope -input-file %s %s +// RUN: not FileCheck -check-prefixes CHECK,LOCAL3 --enable-var-scope -input-file %s %s local1 global1 -; CHECK: [[LOCAL:loc[^[:digit:]]*]][[#LOCNUM]] -; CHECK: [[$GLOBAL:glo[^[:digit:]]*]][[#$GLOBNUM]] +; CHECK: [[LOCAL:loc[^[:digit:]]*]][[#LOCNUM:]] +; CHECK: [[$GLOBAL:glo[^[:digit:]]*]][[#$GLOBNUM:]] local2 global2 diff --git a/llvm/test/FileCheck/verbose.txt b/llvm/test/FileCheck/verbose.txt --- a/llvm/test/FileCheck/verbose.txt +++ b/llvm/test/FileCheck/verbose.txt @@ -1,6 +1,6 @@ -; RUN: FileCheck -D#NUMVAR=42 -input-file %s %s 2>&1 | FileCheck -check-prefix QUIET --allow-empty %s -; RUN: FileCheck -v -D#NUMVAR=42 -input-file %s %s 2>&1 | FileCheck --strict-whitespace -check-prefix V %s -; RUN: FileCheck -vv -D#NUMVAR=42 -input-file %s %s 2>&1 | FileCheck --strict-whitespace -check-prefixes V,VV %s +; RUN: FileCheck -input-file %s %s 2>&1 | FileCheck -check-prefix QUIET --allow-empty %s +; RUN: FileCheck -v -input-file %s %s 2>&1 | FileCheck --strict-whitespace -check-prefix V %s +; RUN: FileCheck -vv -input-file %s %s 2>&1 | FileCheck --strict-whitespace -check-prefixes V,VV %s foo bar @@ -29,36 +29,33 @@ VV-NEXT: {{^}}bar{{$}} VV-NEXT: {{^}}^{{$}} -NUMVAR:42 +NUMVAR=42 NUMVAR - 1:41 -CHECK: NUMVAR:[[#NUMVAR]] +CHECK: NUMVAR=[[#NUMVAR:]] CHECK-NOT: [[#NUMVAR + 1]] CHECK-NEXT: NUMVAR - 1:[[#NUMVAR - 1]] V: verbose.txt:[[#@LINE-4]]:8: remark: {{C}}HECK: expected string found in input -V-NEXT: {{C}}HECK: {{NUMVAR:[[][[]#NUMVAR[]][]]$}} +V-NEXT: {{C}}HECK: {{NUMVAR=[[][[]#NUMVAR:[]][]]$}} V-NEXT: {{^ \^$}} V-NEXT: verbose.txt:[[#@LINE-9]]:1: note: found here -V-NEXT: {{^}}NUMVAR:42{{$}} -V-NEXT: {{^}}^~~~~~~~~{{$}} -V-NEXT: verbose.txt:[[#@LINE-12]]:1: note: with numeric expression "NUMVAR" equal to "42" -V-NEXT: {{^}}NUMVAR:42{{$}} +V-NEXT: {{^}}NUMVAR=42{{$}} V-NEXT: {{^}}^~~~~~~~~{{$}} -V-NEXT: verbose.txt:[[#@LINE-12]]:13: remark: {{C}}HECK-NEXT: expected string found in input +V-NEXT: verbose.txt:[[#@LINE-9]]:13: remark: {{C}}HECK-NEXT: expected string found in input V-NEXT: {{C}}HECK-NEXT: {{NUMVAR - 1:[[][[]#NUMVAR - 1[]][]]$}} V-NEXT: {{^ \^$}} -V-NEXT: verbose.txt:[[#@LINE-18]]:1: note: found here +V-NEXT: verbose.txt:[[#@LINE-15]]:1: note: found here V-NEXT: {{^}}NUMVAR - 1:41{{$}} V-NEXT: {{^}}^~~~~~~~~~~~~{{$}} -V-NEXT: verbose.txt:[[#@LINE-21]]:1: note: with numeric expression "NUMVAR - 1" equal to "41" +V-NEXT: verbose.txt:[[#@LINE-18]]:1: note: with numeric expression "NUMVAR - 1" equal to "41" V-NEXT: {{^}}NUMVAR - 1:41{{$}} V-NEXT: {{^}}^~~~~~~~~~~~~{{$}} -VV-NEXT: verbose.txt:[[#@LINE-23]]:12: remark: {{C}}HECK-NOT: excluded string not found in input +VV-NEXT: verbose.txt:[[#@LINE-20]]:12: remark: {{C}}HECK-NOT: excluded string not found in input VV-NEXT: {{C}}HECK-NOT: {{[[][[]#NUMVAR [+] 1[]][]]$}} -VV-NEXT: {{^ \^$}} -VV-NEXT: verbose.txt:[[#@LINE-28]]:1: note: scanning from here +VV-NEXT: {{^ \^$}} +VV-NEXT: verbose.txt:[[#@LINE-25]]:1: note: scanning from here VV-NEXT: {{^}}NUMVAR - 1:41{{$}} VV-NEXT: {{^}}^{{$}}