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 @@ -101,7 +101,9 @@ /// Define pattern and numeric variables from definitions given on the /// command line passed as a vector of VAR=VAL strings in \p CmdlineDefines. - void defineCmdlineVariables(std::vector &CmdlineDefines); + /// Report any error to \p SM and return whether an error occured. + bool defineCmdlineVariables(std::vector &CmdlineDefines, + SourceMgr &SM); /// Undefine local variables (variables whose name does not start with a '$' /// sign), ie remove them from GlobalVariableTable. @@ -153,6 +155,7 @@ /// 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 ParsePattern(StringRef PatternStr, StringRef Prefix, SourceMgr &SM, unsigned LineNumber, const FileCheckRequest &Req); size_t match(StringRef Buffer, size_t &MatchLen) const; diff --git a/llvm/lib/Support/FileCheck.cpp b/llvm/lib/Support/FileCheck.cpp --- a/llvm/lib/Support/FileCheck.cpp +++ b/llvm/lib/Support/FileCheck.cpp @@ -24,6 +24,44 @@ using namespace llvm; +// 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) { + 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; +} + /// Parses the given string into the Pattern. /// /// \p Prefix provides which prefix is being matched, \p SM provides the @@ -117,9 +155,10 @@ // itself must be of the form "[a-zA-Z_][0-9a-zA-Z_]*", otherwise we reject // it. This is to catch some common errors. if (PatternStr.startswith("[[")) { + StringRef MatchStr = PatternStr.substr(2); // 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()), @@ -128,55 +167,45 @@ return true; } - StringRef MatchStr = PatternStr.substr(2, End); + MatchStr = MatchStr.substr(0, End); PatternStr = PatternStr.substr(End + 4); - // Get the regex name (e.g. "foo"). - size_t NameEnd = MatchStr.find(':'); - StringRef Name = MatchStr.substr(0, NameEnd); + // 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; + } + + StringRef Name = MatchStr.substr(0, TrailIdx); + StringRef Trailer = MatchStr.substr(TrailIdx); + size_t DefSepIdx = Trailer.find(":"); + bool IsVarDef = (DefSepIdx != StringRef::npos); - if (Name.empty()) { - SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, - "invalid name in named regex: empty name"); + 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"); - return true; - } - IsExpression = true; - continue; + 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 (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"); - return true; - } - } - - // 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; } // Handle [[foo]]. - if (NameEnd == StringRef::npos) { + if (!IsVarDef) { // Handle variables that were defined earlier on the same line by // emitting a backreference. if (VariableDefs.find(Name) != VariableDefs.end()) { @@ -189,7 +218,7 @@ } AddBackrefToRegEx(VarParenNum); } else { - VariableUses.push_back(std::make_pair(Name, RegExStr.size())); + VariableUses.push_back(std::make_pair(MatchStr, RegExStr.size())); } continue; } @@ -199,7 +228,7 @@ RegExStr += '('; ++CurParen; - if (AddRegExToRegEx(MatchStr.substr(NameEnd + 1), CurParen, SM)) + if (AddRegExToRegEx(Trailer, CurParen, SM)) return true; RegExStr += ')'; @@ -759,7 +788,8 @@ Regex &PrefixRE, std::vector &CheckStrings) { auto *PatternContext = new FileCheckPatternContext(); - PatternContext->defineCmdlineVariables(Req.GlobalDefines); + if (PatternContext->defineCmdlineVariables(Req.GlobalDefines, SM)) + return true; std::vector ImplicitNegativeChecks; for (const auto &PatternString : Req.ImplicitCheckNot) { @@ -1379,17 +1409,41 @@ } /// Define pattern and numeric variables from definitions given on the command -/// line passed as a vector of VAR=VAL strings in \p CmdlineDefines. -void FileCheckPatternContext::defineCmdlineVariables( - std::vector &CmdlineDefines) { +/// line passed as a vector of VAR=VAL strings in \p CmdlineDefines. Report any +/// error to \p SM and return whether an error occured. +bool FileCheckPatternContext::defineCmdlineVariables( + std::vector &CmdlineDefines, SourceMgr &SM) { static bool CmdlineVariablesDefined = false; if (CmdlineVariablesDefined) - return; + return true; CmdlineVariablesDefined = true; - for (const auto &CmdlineDef : CmdlineDefines) - GlobalVariableTable.insert(StringRef(CmdlineDef).split('=')); + bool ErrorFound = false; + for (const auto &CmdlineDef : CmdlineDefines) { + 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 (FileCheckPattern::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(CmdlineNameVal); + } + + return ErrorFound; } /// Undefine local variables (variables whose name does not start with a '$' 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 @@ -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,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 Value = 10 ; CHECK: Value = [[VALUE]] ; NOT-NOT: Value = [[VALUE]] @@ -32,3 +34,15 @@ 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: {{^ \^$}}