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 @@ -155,7 +155,10 @@ /// Returns the pointer to the global state for all patterns in this /// FileCheck instance. FileCheckPatternContext *GetContext() const { return Context; } + static bool ParseVariable(StringRef Str, bool &IsPseudo, unsigned &TrailIdx); + bool ParseExpression(StringRef Name, StringRef Trailer, + 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; @@ -176,7 +179,7 @@ bool AddRegExToRegEx(StringRef RS, unsigned &CurParen, SourceMgr &SM); void AddBackrefToRegEx(unsigned BackrefNum); unsigned ComputeMatchDistance(StringRef Buffer) const; - bool EvaluateExpression(StringRef Expr, std::string &Value) const; + void EvaluateExpression(StringRef Expr, std::string &Value) const; size_t FindRegexVarEnd(StringRef Str, SourceMgr &SM); }; diff --git a/llvm/lib/Support/FileCheck.cpp b/llvm/lib/Support/FileCheck.cpp --- a/llvm/lib/Support/FileCheck.cpp +++ b/llvm/lib/Support/FileCheck.cpp @@ -62,6 +62,62 @@ 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; +} + +/// Parse a numeric expression involving pseudo variable \p Name with the +/// string corresponding to the operation being performed in \p Trailer. +/// Return whether parsing failed in which case errors are reported on \p SM. +bool FileCheckPattern::ParseExpression(StringRef Name, StringRef Trailer, + const SourceMgr &SM) const { + if (!Name.equals("@LINE")) { + SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, + "Invalid pseudo variable '" + Name + "'"); + return true; + } + + // Check if this is a supported operation and select function to perform it. + if (Trailer.empty()) + return false; + SMLoc oploc = SMLoc::getFromPointer(Trailer.data()); + char Operator = Next(Trailer); + switch (Operator) { + case '+': + case '-': + break; + default: + SM.PrintMessage(oploc, SourceMgr::DK_Error, + std::string("Unsupported numeric operation '") + Operator + + "'"); + return true; + } + + // Parse right operand. + if (Trailer.empty()) { + SM.PrintMessage(SMLoc::getFromPointer(Trailer.data()), SourceMgr::DK_Error, + "Missing operand in numeric expression '" + Trailer + "'"); + return true; + } + uint64_t Offset; + if (Trailer.consumeInteger(10, Offset)) { + SM.PrintMessage(SMLoc::getFromPointer(Trailer.data()), SourceMgr::DK_Error, + "Invalid offset in numeric expression '" + Trailer + "'"); + return true; + } + if (!Trailer.empty()) { + SM.PrintMessage(SMLoc::getFromPointer(Trailer.data()), SourceMgr::DK_Error, + "Unexpected characters at end of numeric expression '" + + Trailer + "'"); + return true; + } + return false; +} + /// Parses the given string into the Pattern. /// /// \p Prefix provides which prefix is being matched, \p SM provides the @@ -170,6 +226,13 @@ MatchStr = MatchStr.substr(0, End); PatternStr = PatternStr.substr(End + 4); + 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; @@ -191,17 +254,9 @@ 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. - if (IsPseudo) { - for (unsigned i = 0, e = Trailer.size(); i != e; ++i) { - if (!isalnum(Trailer[i]) && Trailer[i] != '+' && Trailer[i] != '-') { - SM.PrintMessage(SMLoc::getFromPointer(Name.data() + i), - SourceMgr::DK_Error, "invalid name in named regex"); - return true; - } - } + if (!IsVarDef && IsPseudo) { + if (ParseExpression(Name, Trailer, SM)) + return true; } // Handle [[foo]]. @@ -272,24 +327,15 @@ } /// 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; +void FileCheckPattern::EvaluateExpression(StringRef Expr, std::string &Value) const { 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; + Expr.getAsInteger(10, Offset); } Value = llvm::itostr(LineNumber + Offset); - return true; } /// Matches the pattern string against the input buffer \p Buffer @@ -327,8 +373,7 @@ std::string Value; if (VariableUse.first[0] == '@') { - if (!EvaluateExpression(VariableUse.first, Value)) - return StringRef::npos; + EvaluateExpression(VariableUse.first, Value); } else { llvm::Optional OptValue = Context->GetPatternVarValue(VariableUse.first); @@ -406,14 +451,10 @@ 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) << "\""; - } + EvaluateExpression(Var, Value); + OS << "with expression \""; + OS.write_escaped(Var) << "\" equal to \""; + OS.write_escaped(Value) << "\""; } else { llvm::Optional OptValue = Context->GetPatternVarValue(Var); 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 @@ -8,9 +8,9 @@ ; 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: not FileCheck -D10VALUE=10 -input-file %s %s 2>&1 | FileCheck %s --strict-whitespace -check-prefix ERRCLIFMT +; RUN: not FileCheck -D@VALUE=10 -input-file %s %s 2>&1 | FileCheck %s --strict-whitespace -check-prefix ERRCLIPSEUDO +; RUN: not FileCheck -D'VALUE + 2=10' -input-file %s %s 2>&1 | FileCheck %s --strict-whitespace -check-prefix ERRCLITRAIL Value = 10 ; CHECK: Value = [[VALUE]] ; NOT-NOT: Value = [[VALUE]] diff --git a/llvm/test/FileCheck/line-count.txt b/llvm/test/FileCheck/line-count.txt --- a/llvm/test/FileCheck/line-count.txt +++ b/llvm/test/FileCheck/line-count.txt @@ -1,15 +1,56 @@ ; RUN: FileCheck -input-file %s %s -; RUN: not FileCheck -check-prefix BAD -input-file %s %s -3 -4 aaa -5 bbb -6 ccc -7 CHECK: [[@LINE-3]] {{a}}aa -8 CHECK: [[@LINE-3]] {{b}}bb -9 CHECK: [[@LINE-3]] {{c}}cc -10 foobar -11 CHECK: [[@LINE-1]] {{foo}}bar -12 -13 arst CHECK: [[@LINE]] {{a}}rst -14 -15 BAD: [[@LINE:cant-have-regex]] +; RUN: not FileCheck -check-prefix BAD1 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR1 %s +; RUN: not FileCheck -check-prefix BAD2 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR2 %s +; RUN: not FileCheck -check-prefix BAD3 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR3 %s +; RUN: not FileCheck -check-prefix BAD4 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR4 %s +; RUN: not FileCheck -check-prefix BAD5 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR5 %s +; RUN: not FileCheck -check-prefix BAD6 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR6 %s +; RUN: not FileCheck -check-prefix BAD7 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR7 %s +; RUN: not FileCheck -check-prefix BAD8 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR8 %s +; RUN: not FileCheck -check-prefix BAD9 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR9 %s +; RUN: not FileCheck -check-prefix BAD10 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR10 %s +; RUN: not FileCheck -check-prefix BAD10 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR10 %s +13 +14 aaa +15 bbb +16 ccc +17 CHECK: [[@LINE-3]] {{a}}aa +18 CHECK: [[@LINE-3]] {{b}}bb +19 CHECK: [[@LINE-3]] {{c}}cc +20 foobar +21 CHECK: [[@LINE-1]] {{foo}}bar +22 +23 arst CHECK: [[@LINE]] {{a}}rst +24 +25 BAD1: [[@LINE:cant-have-regex]] +26 ERR1: line-count.txt:[[@LINE-1]]:12: error: Invalid name in named regex definition +27 +28 BAD2: [[ @LINE]] +29 ERR2: line-count.txt:[[@LINE-1]]:12: error: Unexpected whitespace +30 +31 BAD3: [[@LINE ]] +32 ERR3: line-count.txt:[[@LINE-1]]:17: error: Unexpected whitespace +33 +34 BAD4: [[ @LINE-1]] +35 ERR4: line-count.txt:[[@LINE-1]]:12: error: Unexpected whitespace +36 +37 BAD5: [[@LINE -1]] +38 ERR5: line-count.txt:[[@LINE-1]]:17: error: Unexpected whitespace +39 +40 BAD6: [[@LINE- 1]] +41 ERR6: line-count.txt:[[@LINE-1]]:18: error: Unexpected whitespace +42 +43 BAD7: [[@LINE-1 ]] +44 ERR7: line-count.txt:[[@LINE-1]]:19: error: Unexpected whitespace +45 +46 BAD8: [[@LIN]] +47 ERR8: line-count.txt:[[@LINE-1]]:12: error: Invalid pseudo variable '@LIN' +48 +49 BAD9: [[@LINE*2]] +50 ERR9: line-count.txt:[[@LINE-1]]:17: error: Unsupported numeric operation '*' +51 +52 BAD10: [[@LINE-x]] +53 ERR10: line-count.txt:[[@LINE-1]]:19: error: Invalid offset in numeric expression 'x' +54 +55 BAD11: [[@LINE-1x]] +56 ERR11: line-count.txt:[[@LINE-1]]:19: error: Unexpected characters at end of numeric expression 'x'