llvm.org GIT mirror llvm / 4a42ca4
FileCheck [2/12]: Stricter parsing of -D option Summary: This patch is part of a patch series to add support for FileCheck numeric expressions. This specific patch gives earlier and better diagnostics for the -D option. Prior to this change, parsing of -D option was very loose: it assumed that there is an equal sign (which to be fair is now checked by the FileCheck executable) and that the part on the left of the equal sign was a valid variable name. This commit adds logic to ensure that this is the case and gives diagnostic when it is not, making it clear that the issue came from a command-line option error. This is achieved by sharing the variable parsing code into a new function ParseVariable. Copyright: - Linaro (changes up to diff 183612 of revision D55940) - GraphCore (changes in later versions of revision D55940 and in new revision created off D55940) Reviewers: jhenderson, chandlerc, jdenny, probinson, grimar, arichardson, rnk Subscribers: hiraditya, llvm-commits, probinson, dblaikie, grimar, arichardson, tra, rnk, kristina, hfinkel, rogfer01, JonChesterfield Tags: #llvm Differential Revision: https://reviews.llvm.org/D60382 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@359447 91177308-0d34-0410-b5e6-96231b3b80d8 Thomas Preud'homme 1 year, 7 months ago
4 changed file(s) with 244 addition(s) and 51 deletion(s). Raw diff Collapse all Expand all
101101 llvm::Optional getVarValue(StringRef VarName);
102102
103103 /// Define variables from definitions given on the command line passed as a
104 /// vector of VAR=VAL strings in \p CmdlineDefines.
105 void defineCmdlineVariables(std::vector &CmdlineDefines);
104 /// vector of VAR=VAL strings in \p CmdlineDefines. Report any error to \p SM
105 /// and return whether an error occured.
106 bool defineCmdlineVariables(std::vector &CmdlineDefines,
107 SourceMgr &SM);
106108
107109 /// Undefine local variables (variables whose name does not start with a '$'
108110 /// sign), i.e. remove them from GlobalVariableTable.
152154 /// Returns the pointer to the global state for all patterns in this
153155 /// FileCheck instance.
154156 FileCheckPatternContext *getContext() const { return Context; }
157 /// Return whether \p is a valid first character for a variable name.
158 static bool isValidVarNameStart(char C);
159 /// Verify that the string at the start of \p Str is a well formed variable.
160 /// Return false if it is and set \p IsPseudo to indicate if it is a pseudo
161 /// variable and \p TrailIdx to the position of the last character that is
162 /// part of the variable name. Otherwise, only return true.
163 static bool parseVariable(StringRef Str, bool &IsPseudo, unsigned &TrailIdx);
155164 bool ParsePattern(StringRef PatternStr, StringRef Prefix, SourceMgr &SM,
156165 unsigned LineNumber, const FileCheckRequest &Req);
157166 size_t match(StringRef Buffer, size_t &MatchLen) const;
2323
2424 using namespace llvm;
2525
26 bool FileCheckPattern::isValidVarNameStart(char C) {
27 return C == '_' || isalpha(C);
28 }
29
30 bool FileCheckPattern::parseVariable(StringRef Str, bool &IsPseudo,
31 unsigned &TrailIdx) {
32 if (Str.empty())
33 return true;
34
35 bool ParsedOneChar = false;
36 unsigned I = 0;
37 IsPseudo = Str[0] == '@';
38
39 // Global vars start with '$'.
40 if (Str[0] == '$' || IsPseudo)
41 ++I;
42
43 for (unsigned E = Str.size(); I != E; ++I) {
44 if (!ParsedOneChar && !isValidVarNameStart(Str[I]))
45 return true;
46
47 // Variable names are composed of alphanumeric characters and underscores.
48 if (Str[I] != '_' && !isalnum(Str[I]))
49 break;
50 ParsedOneChar = true;
51 }
52
53 TrailIdx = I;
54 return false;
55 }
56
2657 /// Parses the given string into the Pattern.
2758 ///
2859 /// \p Prefix provides which prefix is being matched, \p SM provides the
116147 // itself must be of the form "[a-zA-Z_][0-9a-zA-Z_]*", otherwise we reject
117148 // it. This is to catch some common errors.
118149 if (PatternStr.startswith("[[")) {
150 StringRef MatchStr = PatternStr.substr(2);
119151 // Find the closing bracket pair ending the match. End is going to be an
120152 // offset relative to the beginning of the match string.
121 size_t End = FindRegexVarEnd(PatternStr.substr(2), SM);
153 size_t End = FindRegexVarEnd(MatchStr, SM);
122154
123155 if (End == StringRef::npos) {
124156 SM.PrintMessage(SMLoc::getFromPointer(PatternStr.data()),
127159 return true;
128160 }
129161
130 StringRef MatchStr = PatternStr.substr(2, End);
162 MatchStr = MatchStr.substr(0, End);
131163 PatternStr = PatternStr.substr(End + 4);
132164
133 // Get the regex name (e.g. "foo").
134 size_t NameEnd = MatchStr.find(':');
135 StringRef Name = MatchStr.substr(0, NameEnd);
136
137 if (Name.empty()) {
138 SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error,
139 "invalid name in named regex: empty name");
165 // Get the regex name (e.g. "foo") and verify it is well formed.
166 bool IsPseudo;
167 unsigned TrailIdx;
168 if (parseVariable(MatchStr, IsPseudo, TrailIdx)) {
169 SM.PrintMessage(SMLoc::getFromPointer(MatchStr.data()),
170 SourceMgr::DK_Error, "invalid name in named regex");
171 return true;
172 }
173
174 StringRef Name = MatchStr.substr(0, TrailIdx);
175 StringRef Trailer = MatchStr.substr(TrailIdx);
176 bool IsVarDef = (Trailer.find(":") != StringRef::npos);
177
178 if (IsVarDef && (IsPseudo || !Trailer.consume_front(":"))) {
179 SM.PrintMessage(SMLoc::getFromPointer(MatchStr.data()),
180 SourceMgr::DK_Error,
181 "invalid name in named regex definition");
140182 return true;
141183 }
142184
143185 // Verify that the name/expression is well formed. FileCheck currently
144186 // supports @LINE, @LINE+number, @LINE-number expressions. The check here
145 // is relaxed, more strict check is performed in \c EvaluateExpression.
146 bool IsExpression = false;
147 for (unsigned i = 0, e = Name.size(); i != e; ++i) {
148 if (i == 0) {
149 if (Name[i] == '$') // Global vars start with '$'
150 continue;
151 if (Name[i] == '@') {
152 if (NameEnd != StringRef::npos) {
153 SM.PrintMessage(SMLoc::getFromPointer(Name.data()),
154 SourceMgr::DK_Error,
155 "invalid name in named regex definition");
156 return true;
157 }
158 IsExpression = true;
159 continue;
187 // is relaxed. A stricter check is performed in \c EvaluateExpression.
188 if (IsPseudo) {
189 for (unsigned I = 0, E = Trailer.size(); I != E; ++I) {
190 if (!isalnum(Trailer[I]) && Trailer[I] != '+' && Trailer[I] != '-') {
191 SM.PrintMessage(SMLoc::getFromPointer(Name.data() + I),
192 SourceMgr::DK_Error, "invalid name in named regex");
193 return true;
160194 }
161195 }
162 if (Name[i] != '_' && !isalnum(Name[i]) &&
163 (!IsExpression || (Name[i] != '+' && Name[i] != '-'))) {
164 SM.PrintMessage(SMLoc::getFromPointer(Name.data() + i),
165 SourceMgr::DK_Error, "invalid name in named regex");
166 return true;
167 }
168 }
169
170 // Name can't start with a digit.
171 if (isdigit(static_cast(Name[0]))) {
172 SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error,
173 "invalid name in named regex");
174 return true;
175196 }
176197
177198 // Handle [[foo]].
178 if (NameEnd == StringRef::npos) {
199 if (!IsVarDef) {
179200 // Handle variables that were defined earlier on the same line by
180201 // emitting a backreference.
181202 if (VariableDefs.find(Name) != VariableDefs.end()) {
188209 }
189210 AddBackrefToRegEx(VarParenNum);
190211 } else {
191 VariableUses.push_back(std::make_pair(Name, RegExStr.size()));
212 VariableUses.push_back(std::make_pair(MatchStr, RegExStr.size()));
192213 }
193214 continue;
194215 }
198219 RegExStr += '(';
199220 ++CurParen;
200221
201 if (AddRegExToRegEx(MatchStr.substr(NameEnd + 1), CurParen, SM))
222 if (AddRegExToRegEx(Trailer, CurParen, SM))
202223 return true;
203224
204225 RegExStr += ')';
754775 bool llvm::FileCheck::ReadCheckFile(
755776 SourceMgr &SM, StringRef Buffer, Regex &PrefixRE,
756777 std::vector &CheckStrings) {
757 PatternContext.defineCmdlineVariables(Req.GlobalDefines);
778 if (PatternContext.defineCmdlineVariables(Req.GlobalDefines, SM))
779 return true;
758780
759781 std::vector ImplicitNegativeChecks;
760782 for (const auto &PatternString : Req.ImplicitCheckNot) {
13731395 return Regex(PrefixRegexStr);
13741396 }
13751397
1376 void FileCheckPatternContext::defineCmdlineVariables(
1377 std::vector &CmdlineDefines) {
1398 bool FileCheckPatternContext::defineCmdlineVariables(
1399 std::vector &CmdlineDefines, SourceMgr &SM) {
13781400 assert(GlobalVariableTable.empty() &&
13791401 "Overriding defined variable with command-line variable definitions");
1402
1403 if (CmdlineDefines.empty())
1404 return false;
1405
1406 // Create a string representing the vector of command-line definitions. Each
1407 // definition is on its own line and prefixed with a definition number to
1408 // clarify which definition a given diagnostic corresponds to.
1409 unsigned I = 0;
1410 bool ErrorFound = false;
1411 std::string CmdlineDefsDiag;
1412 StringRef Prefix1 = "Global define #";
1413 StringRef Prefix2 = ": ";
13801414 for (StringRef CmdlineDef : CmdlineDefines)
1381 GlobalVariableTable.insert(CmdlineDef.split('='));
1415 CmdlineDefsDiag +=
1416 (Prefix1 + Twine(++I) + Prefix2 + CmdlineDef + "\n").str();
1417
1418 std::unique_ptr CmdLineDefsDiagBuffer =
1419 MemoryBuffer::getMemBufferCopy(CmdlineDefsDiag, "Global defines");
1420 StringRef CmdlineDefsDiagRef = CmdLineDefsDiagBuffer->getBuffer();
1421 SM.AddNewSourceBuffer(std::move(CmdLineDefsDiagBuffer), SMLoc());
1422
1423 SmallVector CmdlineDefsDiagVec;
1424 CmdlineDefsDiagRef.split(CmdlineDefsDiagVec, '\n', -1 /*MaxSplit*/,
1425 false /*KeepEmpty*/);
1426 for (StringRef CmdlineDefDiag : CmdlineDefsDiagVec) {
1427 unsigned NameStart = CmdlineDefDiag.find(Prefix2) + Prefix2.size();
1428 if (CmdlineDefDiag.substr(NameStart).find('=') == StringRef::npos) {
1429 SM.PrintMessage(SMLoc::getFromPointer(CmdlineDefDiag.data()),
1430 SourceMgr::DK_Error,
1431 "Missing equal sign in global definition");
1432 ErrorFound = true;
1433 continue;
1434 }
1435 std::pair CmdlineNameVal =
1436 CmdlineDefDiag.substr(NameStart).split('=');
1437 StringRef Name = CmdlineNameVal.first;
1438 bool IsPseudo;
1439 unsigned TrailIdx;
1440 if (FileCheckPattern::parseVariable(Name, IsPseudo, TrailIdx) || IsPseudo ||
1441 TrailIdx != Name.size() || Name.empty()) {
1442 SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error,
1443 "invalid name for variable definition '" + Name + "'");
1444 ErrorFound = true;
1445 continue;
1446 }
1447 GlobalVariableTable.insert(CmdlineNameVal);
1448 }
1449
1450 return ErrorFound;
13821451 }
13831452
13841453 void FileCheckPatternContext::clearLocalVars() {
0 ; RUN: FileCheck -DVALUE=10 -input-file %s %s
11 ; RUN: not FileCheck -DVALUE=20 -input-file %s %s 2>&1 | FileCheck %s -check-prefix ERRMSG
2 ;
32 ; RUN: not FileCheck -DVALUE=10 -check-prefix NOT -input-file %s %s 2>&1 | FileCheck %s -check-prefix NOT-ERRMSG
43 ; RUN: FileCheck -DVALUE=20 -check-prefix NOT -input-file %s %s
54 ; RUN: not FileCheck -DVALUE10 -input-file %s %s 2>&1 | FileCheck %s -check-prefix ERRCLIEQ1
87 ; RUN: not FileCheck -D= -input-file %s %s 2>&1 | FileCheck %s -check-prefix ERRCLIVAR2
98 ; RUN: FileCheck -DVALUE= -check-prefix EMPTY -input-file %s %s 2>&1
109
10 ; RUN: not FileCheck -D10VALUE=10 -input-file %s %s 2>&1 | FileCheck %s --strict-whitespace -check-prefix ERRCLIFMT
11 ; RUN: not FileCheck -D@VALUE=10 -input-file %s %s 2>&1 | FileCheck %s --strict-whitespace -check-prefix ERRCLIPSEUDO
12 ; RUN: not FileCheck -D'VALUE + 2=10' -input-file %s %s 2>&1 | FileCheck %s --strict-whitespace -check-prefix ERRCLITRAIL
1113 Value = 10
1214 ; CHECK: Value = [[VALUE]]
1315 ; NOT-NOT: Value = [[VALUE]]
3133
3234 Empty value = @@
3335 ; EMPTY: Empty value = @[[VALUE]]@
36
37 ; ERRCLIFMT: Global defines:1:19: error: invalid name for variable definition '10VALUE'
38 ; ERRCLIFMT-NEXT: Global define #1: 10VALUE=10
39 ; ERRCLIFMT-NEXT: {{^ \^$}}
40
41 ; ERRCLIPSEUDO: Global defines:1:19: error: invalid name for variable definition '@VALUE'
42 ; ERRCLIPSEUDO-NEXT: Global define #1: @VALUE=10
43 ; ERRCLIPSEUDO-NEXT: {{^ \^$}}
44
45 ; ERRCLITRAIL: Global defines:1:19: error: invalid name for variable definition 'VALUE + 2'
46 ; ERRCLITRAIL-NEXT: Global define #1: VALUE + 2=10
47 ; ERRCLITRAIL-NEXT: {{^ \^$}}
1313
1414 class FileCheckTest : public ::testing::Test {};
1515
16 TEST_F(FileCheckTest, ValidVarNameStart) {
17 EXPECT_TRUE(FileCheckPattern::isValidVarNameStart('a'));
18 EXPECT_TRUE(FileCheckPattern::isValidVarNameStart('G'));
19 EXPECT_TRUE(FileCheckPattern::isValidVarNameStart('_'));
20 EXPECT_FALSE(FileCheckPattern::isValidVarNameStart('2'));
21 EXPECT_FALSE(FileCheckPattern::isValidVarNameStart('$'));
22 EXPECT_FALSE(FileCheckPattern::isValidVarNameStart('@'));
23 EXPECT_FALSE(FileCheckPattern::isValidVarNameStart('+'));
24 EXPECT_FALSE(FileCheckPattern::isValidVarNameStart('-'));
25 EXPECT_FALSE(FileCheckPattern::isValidVarNameStart(':'));
26 }
27
28 TEST_F(FileCheckTest, ParseVar) {
29 StringRef VarName = "GoodVar42";
30 bool IsPseudo = true;
31 unsigned TrailIdx = 0;
32 EXPECT_FALSE(FileCheckPattern::parseVariable(VarName, IsPseudo, TrailIdx));
33 EXPECT_FALSE(IsPseudo);
34 EXPECT_EQ(TrailIdx, VarName.size());
35
36 VarName = "$GoodGlobalVar";
37 IsPseudo = true;
38 TrailIdx = 0;
39 EXPECT_FALSE(FileCheckPattern::parseVariable(VarName, IsPseudo, TrailIdx));
40 EXPECT_FALSE(IsPseudo);
41 EXPECT_EQ(TrailIdx, VarName.size());
42
43 VarName = "@GoodPseudoVar";
44 IsPseudo = true;
45 TrailIdx = 0;
46 EXPECT_FALSE(FileCheckPattern::parseVariable(VarName, IsPseudo, TrailIdx));
47 EXPECT_TRUE(IsPseudo);
48 EXPECT_EQ(TrailIdx, VarName.size());
49
50 VarName = "42BadVar";
51 EXPECT_TRUE(FileCheckPattern::parseVariable(VarName, IsPseudo, TrailIdx));
52
53 VarName = "$@";
54 EXPECT_TRUE(FileCheckPattern::parseVariable(VarName, IsPseudo, TrailIdx));
55
56 VarName = "B@dVar";
57 IsPseudo = true;
58 TrailIdx = 0;
59 EXPECT_FALSE(FileCheckPattern::parseVariable(VarName, IsPseudo, TrailIdx));
60 EXPECT_FALSE(IsPseudo);
61 EXPECT_EQ(TrailIdx, 1U);
62
63 VarName = "B$dVar";
64 IsPseudo = true;
65 TrailIdx = 0;
66 EXPECT_FALSE(FileCheckPattern::parseVariable(VarName, IsPseudo, TrailIdx));
67 EXPECT_FALSE(IsPseudo);
68 EXPECT_EQ(TrailIdx, 1U);
69
70 VarName = "BadVar+";
71 IsPseudo = true;
72 TrailIdx = 0;
73 EXPECT_FALSE(FileCheckPattern::parseVariable(VarName, IsPseudo, TrailIdx));
74 EXPECT_FALSE(IsPseudo);
75 EXPECT_EQ(TrailIdx, VarName.size() - 1);
76
77 VarName = "BadVar-";
78 IsPseudo = true;
79 TrailIdx = 0;
80 EXPECT_FALSE(FileCheckPattern::parseVariable(VarName, IsPseudo, TrailIdx));
81 EXPECT_FALSE(IsPseudo);
82 EXPECT_EQ(TrailIdx, VarName.size() - 1);
83
84 VarName = "BadVar:";
85 IsPseudo = true;
86 TrailIdx = 0;
87 EXPECT_FALSE(FileCheckPattern::parseVariable(VarName, IsPseudo, TrailIdx));
88 EXPECT_FALSE(IsPseudo);
89 EXPECT_EQ(TrailIdx, VarName.size() - 1);
90 }
91
1692 TEST_F(FileCheckTest, FileCheckContext) {
17 FileCheckPatternContext Cxt;
93 FileCheckPatternContext Cxt = FileCheckPatternContext();
1894 std::vector GlobalDefines;
95 SourceMgr SM;
1996
20 // Define local and global variables from command-line.
97 // Missing equal sign
98 GlobalDefines.emplace_back(std::string("LocalVar"));
99 EXPECT_TRUE(Cxt.defineCmdlineVariables(GlobalDefines, SM));
100
101 // Empty variable
102 GlobalDefines.clear();
103 GlobalDefines.emplace_back(std::string("=18"));
104 EXPECT_TRUE(Cxt.defineCmdlineVariables(GlobalDefines, SM));
105
106 // Invalid variable
107 GlobalDefines.clear();
108 GlobalDefines.emplace_back(std::string("18LocalVar=18"));
109 EXPECT_TRUE(Cxt.defineCmdlineVariables(GlobalDefines, SM));
110
111 // Define local variables from command-line.
112 GlobalDefines.clear();
21113 GlobalDefines.emplace_back(std::string("LocalVar=FOO"));
22 Cxt.defineCmdlineVariables(GlobalDefines);
114 GlobalDefines.emplace_back(std::string("EmptyVar="));
115 bool GotError = Cxt.defineCmdlineVariables(GlobalDefines, SM);
116 EXPECT_FALSE(GotError);
23117
24118 // Check defined variables are present and undefined is absent.
25119 StringRef LocalVarStr = "LocalVar";
120 StringRef EmptyVarStr = "EmptyVar";
26121 StringRef UnknownVarStr = "UnknownVar";
27122 llvm::Optional LocalVar = Cxt.getVarValue(LocalVarStr);
123 llvm::Optional EmptyVar = Cxt.getVarValue(EmptyVarStr);
28124 llvm::Optional UnknownVar = Cxt.getVarValue(UnknownVarStr);
29125 EXPECT_TRUE(LocalVar);
30126 EXPECT_EQ(*LocalVar, "FOO");
127 EXPECT_TRUE(EmptyVar);
128 EXPECT_EQ(*EmptyVar, "");
31129 EXPECT_FALSE(UnknownVar);
32130
33131 // Clear local variables and check they become absent.
34132 Cxt.clearLocalVars();
35133 LocalVar = Cxt.getVarValue(LocalVarStr);
36134 EXPECT_FALSE(LocalVar);
135 EmptyVar = Cxt.getVarValue(EmptyVarStr);
136 EXPECT_FALSE(EmptyVar);
37137
38138 // Redefine global variables and check variables are defined again.
39139 GlobalDefines.emplace_back(std::string("$GlobalVar=BAR"));
40 Cxt.defineCmdlineVariables(GlobalDefines);
140 GotError = Cxt.defineCmdlineVariables(GlobalDefines, SM);
141 EXPECT_FALSE(GotError);
41142 StringRef GlobalVarStr = "$GlobalVar";
42143 llvm::Optional GlobalVar = Cxt.getVarValue(GlobalVarStr);
43144 EXPECT_TRUE(GlobalVar);