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,6 +155,7 @@ /// Returns the pointer to the global state for all patterns in this /// FileCheck instance. FileCheckPatternContext *getContext() const { return Context; } + /// Return whether \p is a valid first character for a variable name. static bool isValidVarNameStart(char C); /// Verify that the string at the start of \p Str is a well formed variable. @@ -162,6 +163,11 @@ /// variable and \p TrailIdx to the position of the last character that is /// part of the variable name. Otherwise, only return true. static bool parseVariable(StringRef Str, bool &IsPseudo, unsigned &TrailIdx); + /// 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 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; @@ -184,7 +190,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 @@ -56,6 +56,58 @@ return false; } +// Parsing helper function that strips the first character in S and returns it. +static char next(StringRef &S) { + char C = S.front(); + S = S.drop_front(); + return C; +} + +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, + Twine("unsupported numeric operation '") + Twine(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 @@ -164,6 +216,14 @@ MatchStr = MatchStr.substr(0, End); PatternStr = PatternStr.substr(End + 4); + size_t VarEndIdx = MatchStr.find(":"); + size_t SpacePos = MatchStr.substr(0, VarEndIdx).find_first_of(" \t"); + if (SpacePos != StringRef::npos) { + SM.PrintMessage(SMLoc::getFromPointer(MatchStr.data() + SpacePos), + SourceMgr::DK_Error, "unexpected whitespace"); + return true; + } + // Get the regex name (e.g. "foo") and verify it is well formed. bool IsPseudo; unsigned TrailIdx; @@ -175,7 +235,7 @@ StringRef Name = MatchStr.substr(0, TrailIdx); StringRef Trailer = MatchStr.substr(TrailIdx); - bool IsVarDef = (Trailer.find(":") != StringRef::npos); + bool IsVarDef = (VarEndIdx != StringRef::npos); if (IsVarDef && (IsPseudo || !Trailer.consume_front(":"))) { SM.PrintMessage(SMLoc::getFromPointer(MatchStr.data()), @@ -184,17 +244,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. A stricter 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]]. @@ -265,24 +317,16 @@ } /// 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 @@ -321,8 +365,7 @@ std::string Value; if (VariableUse.first[0] == '@') { - if (!EvaluateExpression(VariableUse.first, Value)) - return StringRef::npos; + evaluateExpression(VariableUse.first, Value); } else { llvm::Optional ValueRef = Context->getVarValue(VariableUse.first); @@ -398,14 +441,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 VarValue = Context->getVarValue(Var); 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' diff --git a/llvm/unittests/Support/FileCheckTest.cpp b/llvm/unittests/Support/FileCheckTest.cpp --- a/llvm/unittests/Support/FileCheckTest.cpp +++ b/llvm/unittests/Support/FileCheckTest.cpp @@ -90,6 +90,62 @@ EXPECT_EQ(TrailIdx, VarName.size() - 1); } +class ExprTester { +private: + SourceMgr SM; + FileCheckPatternContext Context; + FileCheckPattern P = FileCheckPattern(Check::CheckPlain, &Context); + +public: + bool parse_expect(std::string &VarName, std::string &Trailer) { + StringRef NameTrailer = StringRef(VarName + Trailer); + std::unique_ptr Buffer = + MemoryBuffer::getMemBufferCopy(NameTrailer, "TestBuffer"); + StringRef NameTrailerRef = Buffer->getBuffer(); + SM.AddNewSourceBuffer(std::move(Buffer), SMLoc()); + StringRef VarNameRef = NameTrailerRef.substr(0, VarName.size()); + StringRef TrailerRef = NameTrailerRef.substr(VarName.size()); + return P.parseExpression(VarNameRef, TrailerRef, SM); + } +}; + +TEST_F(FileCheckTest, ParseExpr) { + ExprTester Tester; + + // @LINE with offset. + std::string VarName = "@LINE"; + std::string Trailer = "+3"; + EXPECT_FALSE(Tester.parse_expect(VarName, Trailer)); + + // @LINE only. + Trailer = ""; + EXPECT_FALSE(Tester.parse_expect(VarName, Trailer)); + + // Wrong Pseudovar. + VarName = "@FOO"; + EXPECT_TRUE(Tester.parse_expect(VarName, Trailer)); + + // Unsupported operator. + VarName = "@LINE"; + Trailer = "/2"; + EXPECT_TRUE(Tester.parse_expect(VarName, Trailer)); + + // Missing offset operand. + VarName = "@LINE"; + Trailer = "+"; + EXPECT_TRUE(Tester.parse_expect(VarName, Trailer)); + + // Cannot parse offset operand. + VarName = "@LINE"; + Trailer = "+x"; + EXPECT_TRUE(Tester.parse_expect(VarName, Trailer)); + + // Unexpected string at end of numeric expression. + VarName = "@LINE"; + Trailer = "+5x"; + EXPECT_TRUE(Tester.parse_expect(VarName, Trailer)); +} + TEST_F(FileCheckTest, FileCheckContext) { FileCheckPatternContext Cxt = FileCheckPatternContext(); std::vector GlobalDefines;