llvm.org GIT mirror llvm / 11c6039
FileCheck [3/12]: Stricter parsing of @LINE expressions 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 @LINE expressions. Rather than detect parsing errors at matching time, this commit adds enhance parsing to detect issues with @LINE expressions at parse time and diagnose them more accurately. 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/D60383 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@359475 91177308-0d34-0410-b5e6-96231b3b80d8 Thomas Preud'homme 1 year, 7 months ago
4 changed file(s) with 190 addition(s) and 48 deletion(s). Raw diff Collapse all Expand all
154154 /// Returns the pointer to the global state for all patterns in this
155155 /// FileCheck instance.
156156 FileCheckPatternContext *getContext() const { return Context; }
157
157158 /// Return whether \p is a valid first character for a variable name.
158159 static bool isValidVarNameStart(char C);
159160 /// Verify that the string at the start of \p Str is a well formed variable.
161162 /// variable and \p TrailIdx to the position of the last character that is
162163 /// part of the variable name. Otherwise, only return true.
163164 static bool parseVariable(StringRef Str, bool &IsPseudo, unsigned &TrailIdx);
165 /// Parse a numeric expression involving pseudo variable \p Name with the
166 /// string corresponding to the operation being performed in \p Trailer.
167 /// Return whether parsing failed in which case errors are reported on \p SM.
168 bool parseExpression(StringRef Name, StringRef Trailer,
169 const SourceMgr &SM) const;
164170 bool ParsePattern(StringRef PatternStr, StringRef Prefix, SourceMgr &SM,
165171 unsigned LineNumber, const FileCheckRequest &Req);
166172 size_t match(StringRef Buffer, size_t &MatchLen) const;
183189 bool AddRegExToRegEx(StringRef RS, unsigned &CurParen, SourceMgr &SM);
184190 void AddBackrefToRegEx(unsigned BackrefNum);
185191 unsigned computeMatchDistance(StringRef Buffer) const;
186 bool EvaluateExpression(StringRef Expr, std::string &Value) const;
192 void evaluateExpression(StringRef Expr, std::string &Value) const;
187193 size_t FindRegexVarEnd(StringRef Str, SourceMgr &SM);
188194 };
189195
5454 return false;
5555 }
5656
57 // Parsing helper function that strips the first character in S and returns it.
58 static char popFront(StringRef &S) {
59 char C = S.front();
60 S = S.drop_front();
61 return C;
62 }
63
64 bool FileCheckPattern::parseExpression(StringRef Name, StringRef Trailer,
65 const SourceMgr &SM) const {
66 if (!Name.equals("@LINE")) {
67 SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error,
68 "invalid pseudo variable '" + Name + "'");
69 return true;
70 }
71
72 // Check if this is a supported operation and select function to perform it.
73 if (Trailer.empty())
74 return false;
75 SMLoc OpLoc = SMLoc::getFromPointer(Trailer.data());
76 char Operator = popFront(Trailer);
77 switch (Operator) {
78 case '+':
79 case '-':
80 break;
81 default:
82 SM.PrintMessage(OpLoc, SourceMgr::DK_Error,
83 Twine("unsupported numeric operation '") + Twine(Operator) +
84 "'");
85 return true;
86 }
87
88 // Parse right operand.
89 if (Trailer.empty()) {
90 SM.PrintMessage(SMLoc::getFromPointer(Trailer.data()), SourceMgr::DK_Error,
91 "missing operand in numeric expression '" + Trailer + "'");
92 return true;
93 }
94 uint64_t Offset;
95 if (Trailer.consumeInteger(10, Offset)) {
96 SM.PrintMessage(SMLoc::getFromPointer(Trailer.data()), SourceMgr::DK_Error,
97 "invalid offset in numeric expression '" + Trailer + "'");
98 return true;
99 }
100 if (!Trailer.empty()) {
101 SM.PrintMessage(SMLoc::getFromPointer(Trailer.data()), SourceMgr::DK_Error,
102 "unexpected characters at end of numeric expression '" +
103 Trailer + "'");
104 return true;
105 }
106 return false;
107 }
108
57109 /// Parses the given string into the Pattern.
58110 ///
59111 /// \p Prefix provides which prefix is being matched, \p SM provides the
162214 MatchStr = MatchStr.substr(0, End);
163215 PatternStr = PatternStr.substr(End + 4);
164216
217 size_t VarEndIdx = MatchStr.find(":");
218 size_t SpacePos = MatchStr.substr(0, VarEndIdx).find_first_of(" \t");
219 if (SpacePos != StringRef::npos) {
220 SM.PrintMessage(SMLoc::getFromPointer(MatchStr.data() + SpacePos),
221 SourceMgr::DK_Error, "unexpected whitespace");
222 return true;
223 }
224
165225 // Get the regex name (e.g. "foo") and verify it is well formed.
166226 bool IsPseudo;
167227 unsigned TrailIdx;
173233
174234 StringRef Name = MatchStr.substr(0, TrailIdx);
175235 StringRef Trailer = MatchStr.substr(TrailIdx);
176 bool IsVarDef = (Trailer.find(":") != StringRef::npos);
236 bool IsVarDef = (VarEndIdx != StringRef::npos);
177237
178238 if (IsVarDef && (IsPseudo || !Trailer.consume_front(":"))) {
179239 SM.PrintMessage(SMLoc::getFromPointer(MatchStr.data()),
182242 return true;
183243 }
184244
185 // Verify that the name/expression is well formed. FileCheck currently
186 // supports @LINE, @LINE+number, @LINE-number expressions. The check here
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;
194 }
195 }
245 if (!IsVarDef && IsPseudo) {
246 if (parseExpression(Name, Trailer, SM))
247 return true;
196248 }
197249
198250 // Handle [[foo]].
263315 }
264316
265317 /// Evaluates expression and stores the result to \p Value.
266 ///
267 /// Returns true on success and false when the expression has invalid syntax.
268 bool FileCheckPattern::EvaluateExpression(StringRef Expr, std::string &Value) const {
269 // The only supported expression is @LINE([\+-]\d+)?
270 if (!Expr.startswith("@LINE"))
271 return false;
318 void FileCheckPattern::evaluateExpression(StringRef Expr,
319 std::string &Value) const {
272320 Expr = Expr.substr(StringRef("@LINE").size());
273321 int Offset = 0;
274322 if (!Expr.empty()) {
275323 if (Expr[0] == '+')
276324 Expr = Expr.substr(1);
277 else if (Expr[0] != '-')
278 return false;
279 if (Expr.getAsInteger(10, Offset))
280 return false;
325 Expr.getAsInteger(10, Offset);
281326 }
282327 Value = llvm::itostr(LineNumber + Offset);
283 return true;
284328 }
285329
286330 /// Matches the pattern string against the input buffer \p Buffer
319363 std::string Value;
320364
321365 if (VariableUse.first[0] == '@') {
322 if (!EvaluateExpression(VariableUse.first, Value))
323 return StringRef::npos;
366 evaluateExpression(VariableUse.first, Value);
324367 } else {
325368 llvm::Optional ValueRef =
326369 Context->getVarValue(VariableUse.first);
396439 StringRef Var = VariableUse.first;
397440 if (Var[0] == '@') {
398441 std::string Value;
399 if (EvaluateExpression(Var, Value)) {
400 OS << "with expression \"";
401 OS.write_escaped(Var) << "\" equal to \"";
402 OS.write_escaped(Value) << "\"";
403 } else {
404 OS << "uses incorrect expression \"";
405 OS.write_escaped(Var) << "\"";
406 }
442 evaluateExpression(Var, Value);
443 OS << "with expression \"";
444 OS.write_escaped(Var) << "\" equal to \"";
445 OS.write_escaped(Value) << "\"";
407446 } else {
408447 llvm::Optional VarValue = Context->getVarValue(Var);
409448
0 ; RUN: FileCheck -input-file %s %s
1 ; RUN: not FileCheck -check-prefix BAD -input-file %s %s
2 3
3 4 aaa
4 5 bbb
5 6 ccc
6 7 CHECK: [[@LINE-3]] {{a}}aa
7 8 CHECK: [[@LINE-3]] {{b}}bb
8 9 CHECK: [[@LINE-3]] {{c}}cc
9 10 foobar
10 11 CHECK: [[@LINE-1]] {{foo}}bar
11 12
12 13 arst CHECK: [[@LINE]] {{a}}rst
13 14
14 15 BAD: [[@LINE:cant-have-regex]]
1 ; RUN: not FileCheck -check-prefix BAD1 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR1 %s
2 ; RUN: not FileCheck -check-prefix BAD2 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR2 %s
3 ; RUN: not FileCheck -check-prefix BAD3 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR3 %s
4 ; RUN: not FileCheck -check-prefix BAD4 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR4 %s
5 ; RUN: not FileCheck -check-prefix BAD5 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR5 %s
6 ; RUN: not FileCheck -check-prefix BAD6 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR6 %s
7 ; RUN: not FileCheck -check-prefix BAD7 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR7 %s
8 ; RUN: not FileCheck -check-prefix BAD8 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR8 %s
9 ; RUN: not FileCheck -check-prefix BAD9 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR9 %s
10 ; RUN: not FileCheck -check-prefix BAD10 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR10 %s
11 ; RUN: not FileCheck -check-prefix BAD10 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR10 %s
12 13
13 14 aaa
14 15 bbb
15 16 ccc
16 17 CHECK: [[@LINE-3]] {{a}}aa
17 18 CHECK: [[@LINE-3]] {{b}}bb
18 19 CHECK: [[@LINE-3]] {{c}}cc
19 20 foobar
20 21 CHECK: [[@LINE-1]] {{foo}}bar
21 22
22 23 arst CHECK: [[@LINE]] {{a}}rst
23 24
24 25 BAD1: [[@LINE:cant-have-regex]]
25 26 ERR1: line-count.txt:[[@LINE-1]]:12: error: invalid name in named regex definition
26 27
27 28 BAD2: [[ @LINE]]
28 29 ERR2: line-count.txt:[[@LINE-1]]:12: error: unexpected whitespace
29 30
30 31 BAD3: [[@LINE ]]
31 32 ERR3: line-count.txt:[[@LINE-1]]:17: error: unexpected whitespace
32 33
33 34 BAD4: [[ @LINE-1]]
34 35 ERR4: line-count.txt:[[@LINE-1]]:12: error: unexpected whitespace
35 36
36 37 BAD5: [[@LINE -1]]
37 38 ERR5: line-count.txt:[[@LINE-1]]:17: error: unexpected whitespace
38 39
39 40 BAD6: [[@LINE- 1]]
40 41 ERR6: line-count.txt:[[@LINE-1]]:18: error: unexpected whitespace
41 42
42 43 BAD7: [[@LINE-1 ]]
43 44 ERR7: line-count.txt:[[@LINE-1]]:19: error: unexpected whitespace
44 45
45 46 BAD8: [[@LIN]]
46 47 ERR8: line-count.txt:[[@LINE-1]]:12: error: invalid pseudo variable '@LIN'
47 48
48 49 BAD9: [[@LINE*2]]
49 50 ERR9: line-count.txt:[[@LINE-1]]:17: error: unsupported numeric operation '*'
50 51
51 52 BAD10: [[@LINE-x]]
52 53 ERR10: line-count.txt:[[@LINE-1]]:19: error: invalid offset in numeric expression 'x'
53 54
54 55 BAD11: [[@LINE-1x]]
55 56 ERR11: line-count.txt:[[@LINE-1]]:19: error: unexpected characters at end of numeric expression 'x'
8787 EXPECT_FALSE(FileCheckPattern::parseVariable(VarName, IsPseudo, TrailIdx));
8888 EXPECT_FALSE(IsPseudo);
8989 EXPECT_EQ(TrailIdx, VarName.size() - 1);
90 }
91
92 class ExprTester {
93 private:
94 SourceMgr SM;
95 FileCheckPatternContext Context;
96 FileCheckPattern P = FileCheckPattern(Check::CheckPlain, &Context);
97
98 public:
99 bool parseExpect(std::string &VarName, std::string &Trailer) {
100 StringRef NameTrailer = StringRef(VarName + Trailer);
101 std::unique_ptr Buffer =
102 MemoryBuffer::getMemBufferCopy(NameTrailer, "TestBuffer");
103 StringRef NameTrailerRef = Buffer->getBuffer();
104 SM.AddNewSourceBuffer(std::move(Buffer), SMLoc());
105 StringRef VarNameRef = NameTrailerRef.substr(0, VarName.size());
106 StringRef TrailerRef = NameTrailerRef.substr(VarName.size());
107 return P.parseExpression(VarNameRef, TrailerRef, SM);
108 }
109 };
110
111 TEST_F(FileCheckTest, ParseExpr) {
112 ExprTester Tester;
113
114 // @LINE with offset.
115 std::string VarName = "@LINE";
116 std::string Trailer = "+3";
117 EXPECT_FALSE(Tester.parseExpect(VarName, Trailer));
118
119 // @LINE only.
120 Trailer = "";
121 EXPECT_FALSE(Tester.parseExpect(VarName, Trailer));
122
123 // Wrong Pseudovar.
124 VarName = "@FOO";
125 EXPECT_TRUE(Tester.parseExpect(VarName, Trailer));
126
127 // Unsupported operator.
128 VarName = "@LINE";
129 Trailer = "/2";
130 EXPECT_TRUE(Tester.parseExpect(VarName, Trailer));
131
132 // Missing offset operand.
133 VarName = "@LINE";
134 Trailer = "+";
135 EXPECT_TRUE(Tester.parseExpect(VarName, Trailer));
136
137 // Cannot parse offset operand.
138 VarName = "@LINE";
139 Trailer = "+x";
140 EXPECT_TRUE(Tester.parseExpect(VarName, Trailer));
141
142 // Unexpected string at end of numeric expression.
143 VarName = "@LINE";
144 Trailer = "+5x";
145 EXPECT_TRUE(Tester.parseExpect(VarName, Trailer));
90146 }
91147
92148 TEST_F(FileCheckTest, FileCheckContext) {