1 | //===- unittests/Support/BitstreamRemarksParsingTest.cpp - Parsing tests --===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "llvm-c/Remarks.h" |
10 | #include "llvm/Remarks/Remark.h" |
11 | #include "llvm/Remarks/RemarkParser.h" |
12 | #include "llvm/Remarks/RemarkSerializer.h" |
13 | #include "gtest/gtest.h" |
14 | |
15 | using namespace llvm; |
16 | |
17 | template <size_t N> void parseGood(const char (&Buf)[N]) { |
18 | // 1. Parse the YAML remark -> FromYAMLRemark |
19 | // 2. Serialize it to bitstream -> BSStream |
20 | // 3. Parse it back -> FromBSRemark |
21 | // 4. Compare the remark objects |
22 | // |
23 | // This testing methodology has the drawback of relying on both the YAML |
24 | // remark parser and the bitstream remark serializer. It does simplify |
25 | // testing a lot, since working directly with bitstream is not that easy. |
26 | |
27 | // 1. |
28 | Expected<std::unique_ptr<remarks::RemarkParser>> MaybeParser = |
29 | remarks::createRemarkParser(remarks::Format::YAML, {Buf, N - 1}); |
30 | EXPECT_FALSE(errorToBool(MaybeParser.takeError())); |
31 | EXPECT_TRUE(*MaybeParser != nullptr); |
32 | |
33 | std::unique_ptr<remarks::Remark> = nullptr; |
34 | remarks::RemarkParser &Parser = **MaybeParser; |
35 | Expected<std::unique_ptr<remarks::Remark>> = Parser.next(); |
36 | EXPECT_FALSE(errorToBool(Remark.takeError())); // Check for parsing errors. |
37 | EXPECT_TRUE(*Remark != nullptr); // At least one remark. |
38 | // Keep the previous remark around. |
39 | FromYAMLRemark = std::move(*Remark); |
40 | Remark = Parser.next(); |
41 | Error E = Remark.takeError(); |
42 | EXPECT_TRUE(E.isA<remarks::EndOfFileError>()); |
43 | EXPECT_TRUE(errorToBool(std::move(E))); // Check for parsing errors. |
44 | |
45 | // 2. |
46 | remarks::StringTable BSStrTab; |
47 | BSStrTab.internalize(R&: *FromYAMLRemark); |
48 | std::string BSBuf; |
49 | raw_string_ostream BSStream(BSBuf); |
50 | Expected<std::unique_ptr<remarks::RemarkSerializer>> BSSerializer = |
51 | remarks::createRemarkSerializer(RemarksFormat: remarks::Format::Bitstream, |
52 | Mode: remarks::SerializerMode::Standalone, |
53 | OS&: BSStream, StrTab: std::move(BSStrTab)); |
54 | EXPECT_FALSE(errorToBool(BSSerializer.takeError())); |
55 | (*BSSerializer)->emit(Remark: *FromYAMLRemark); |
56 | |
57 | // 3. |
58 | Expected<std::unique_ptr<remarks::RemarkParser>> MaybeBSParser = |
59 | remarks::createRemarkParser(ParserFormat: remarks::Format::Bitstream, Buf: BSStream.str()); |
60 | EXPECT_FALSE(errorToBool(MaybeBSParser.takeError())); |
61 | EXPECT_TRUE(*MaybeBSParser != nullptr); |
62 | |
63 | std::unique_ptr<remarks::Remark> = nullptr; |
64 | remarks::RemarkParser &BSParser = **MaybeBSParser; |
65 | Expected<std::unique_ptr<remarks::Remark>> = BSParser.next(); |
66 | EXPECT_FALSE(errorToBool(BSRemark.takeError())); // Check for parsing errors. |
67 | EXPECT_TRUE(*BSRemark != nullptr); // At least one remark. |
68 | // Keep the previous remark around. |
69 | FromBSRemark = std::move(*BSRemark); |
70 | BSRemark = BSParser.next(); |
71 | Error BSE = BSRemark.takeError(); |
72 | EXPECT_TRUE(BSE.isA<remarks::EndOfFileError>()); |
73 | EXPECT_TRUE(errorToBool(std::move(BSE))); // Check for parsing errors. |
74 | |
75 | EXPECT_EQ(*FromYAMLRemark, *FromBSRemark); |
76 | } |
77 | |
78 | TEST(BitstreamRemarks, ParsingGood) { |
79 | parseGood(Buf: "\n" |
80 | "--- !Missed\n" |
81 | "Pass: inline\n" |
82 | "Name: NoDefinition\n" |
83 | "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" |
84 | "Function: foo\n" |
85 | "Args:\n" |
86 | " - Callee: bar\n" |
87 | " - String: ' will not be inlined into '\n" |
88 | " - Caller: foo\n" |
89 | " DebugLoc: { File: file.c, Line: 2, Column: 0 }\n" |
90 | " - String: ' because its definition is unavailable'\n" |
91 | "" ); |
92 | |
93 | // No debug loc should also pass. |
94 | parseGood(Buf: "\n" |
95 | "--- !Missed\n" |
96 | "Pass: inline\n" |
97 | "Name: NoDefinition\n" |
98 | "Function: foo\n" |
99 | "Args:\n" |
100 | " - Callee: bar\n" |
101 | " - String: ' will not be inlined into '\n" |
102 | " - Caller: foo\n" |
103 | " DebugLoc: { File: file.c, Line: 2, Column: 0 }\n" |
104 | " - String: ' because its definition is unavailable'\n" |
105 | "" ); |
106 | |
107 | // No args is also ok. |
108 | parseGood(Buf: "\n" |
109 | "--- !Missed\n" |
110 | "Pass: inline\n" |
111 | "Name: NoDefinition\n" |
112 | "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" |
113 | "Function: foo\n" |
114 | "" ); |
115 | } |
116 | |
117 | // Mandatory common part of a remark. |
118 | #define "\nPass: inline\nName: NoDefinition\nFunction: foo\n\n" |
119 | // Test all the types. |
120 | TEST(BitstreamRemarks, ParsingTypes) { |
121 | // Type: Passed |
122 | parseGood(Buf: "--- !Passed" COMMON_REMARK); |
123 | // Type: Missed |
124 | parseGood(Buf: "--- !Missed" COMMON_REMARK); |
125 | // Type: Analysis |
126 | parseGood(Buf: "--- !Analysis" COMMON_REMARK); |
127 | // Type: AnalysisFPCommute |
128 | parseGood(Buf: "--- !AnalysisFPCommute" COMMON_REMARK); |
129 | // Type: AnalysisAliasing |
130 | parseGood(Buf: "--- !AnalysisAliasing" COMMON_REMARK); |
131 | // Type: Failure |
132 | parseGood(Buf: "--- !Failure" COMMON_REMARK); |
133 | } |
134 | #undef COMMON_REMARK |
135 | |
136 | static inline StringRef checkStr(StringRef Str, unsigned ExpectedLen) { |
137 | const char *StrData = Str.data(); |
138 | unsigned StrLen = Str.size(); |
139 | EXPECT_EQ(StrLen, ExpectedLen); |
140 | return StringRef(StrData, StrLen); |
141 | } |
142 | |
143 | TEST(BitstreamRemarks, Contents) { |
144 | StringRef Buf = "\n" |
145 | "--- !Missed\n" |
146 | "Pass: inline\n" |
147 | "Name: NoDefinition\n" |
148 | "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" |
149 | "Function: foo\n" |
150 | "Hotness: 4\n" |
151 | "Args:\n" |
152 | " - Callee: bar\n" |
153 | " - String: ' will not be inlined into '\n" |
154 | " - Caller: foo\n" |
155 | " DebugLoc: { File: file.c, Line: 2, Column: 0 }\n" |
156 | " - String: ' because its definition is unavailable'\n" |
157 | "\n" ; |
158 | |
159 | Expected<std::unique_ptr<remarks::RemarkParser>> MaybeParser = |
160 | remarks::createRemarkParser(ParserFormat: remarks::Format::YAML, Buf); |
161 | EXPECT_FALSE(errorToBool(MaybeParser.takeError())); |
162 | EXPECT_TRUE(*MaybeParser != nullptr); |
163 | |
164 | remarks::RemarkParser &Parser = **MaybeParser; |
165 | Expected<std::unique_ptr<remarks::Remark>> = Parser.next(); |
166 | EXPECT_FALSE( |
167 | errorToBool(MaybeRemark.takeError())); // Check for parsing errors. |
168 | EXPECT_TRUE(*MaybeRemark != nullptr); // At least one remark. |
169 | |
170 | const remarks::Remark & = **MaybeRemark; |
171 | EXPECT_EQ(Remark.RemarkType, remarks::Type::Missed); |
172 | EXPECT_EQ(checkStr(Remark.PassName, 6), "inline" ); |
173 | EXPECT_EQ(checkStr(Remark.RemarkName, 12), "NoDefinition" ); |
174 | EXPECT_EQ(checkStr(Remark.FunctionName, 3), "foo" ); |
175 | EXPECT_TRUE(Remark.Loc); |
176 | const remarks::RemarkLocation &RL = *Remark.Loc; |
177 | EXPECT_EQ(checkStr(RL.SourceFilePath, 6), "file.c" ); |
178 | EXPECT_EQ(RL.SourceLine, 3U); |
179 | EXPECT_EQ(RL.SourceColumn, 12U); |
180 | EXPECT_TRUE(Remark.Hotness); |
181 | EXPECT_EQ(*Remark.Hotness, 4U); |
182 | EXPECT_EQ(Remark.Args.size(), 4U); |
183 | |
184 | unsigned ArgID = 0; |
185 | for (const remarks::Argument &Arg : Remark.Args) { |
186 | switch (ArgID) { |
187 | case 0: |
188 | EXPECT_EQ(checkStr(Arg.Key, 6), "Callee" ); |
189 | EXPECT_EQ(checkStr(Arg.Val, 3), "bar" ); |
190 | EXPECT_FALSE(Arg.Loc); |
191 | break; |
192 | case 1: |
193 | EXPECT_EQ(checkStr(Arg.Key, 6), "String" ); |
194 | EXPECT_EQ(checkStr(Arg.Val, 26), " will not be inlined into " ); |
195 | EXPECT_FALSE(Arg.Loc); |
196 | break; |
197 | case 2: { |
198 | EXPECT_EQ(checkStr(Arg.Key, 6), "Caller" ); |
199 | EXPECT_EQ(checkStr(Arg.Val, 3), "foo" ); |
200 | EXPECT_TRUE(Arg.Loc); |
201 | const remarks::RemarkLocation &RL = *Arg.Loc; |
202 | EXPECT_EQ(checkStr(RL.SourceFilePath, 6), "file.c" ); |
203 | EXPECT_EQ(RL.SourceLine, 2U); |
204 | EXPECT_EQ(RL.SourceColumn, 0U); |
205 | break; |
206 | } |
207 | case 3: |
208 | EXPECT_EQ(checkStr(Arg.Key, 6), "String" ); |
209 | EXPECT_EQ(checkStr(Arg.Val, 38), |
210 | " because its definition is unavailable" ); |
211 | EXPECT_FALSE(Arg.Loc); |
212 | break; |
213 | default: |
214 | break; |
215 | } |
216 | ++ArgID; |
217 | } |
218 | |
219 | MaybeRemark = Parser.next(); |
220 | Error E = MaybeRemark.takeError(); |
221 | EXPECT_TRUE(E.isA<remarks::EndOfFileError>()); |
222 | EXPECT_TRUE(errorToBool(std::move(E))); // Check for parsing errors. |
223 | } |
224 | |
225 | static inline StringRef (LLVMRemarkStringRef Str, |
226 | unsigned ExpectedLen) { |
227 | const char *StrData = LLVMRemarkStringGetData(String: Str); |
228 | unsigned StrLen = LLVMRemarkStringGetLen(String: Str); |
229 | EXPECT_EQ(StrLen, ExpectedLen); |
230 | return StringRef(StrData, StrLen); |
231 | } |
232 | |
233 | TEST(BitstreamRemarks, ContentsCAPI) { |
234 | remarks::StringTable BSStrTab; |
235 | remarks::Remark ; |
236 | ToSerializeRemark.RemarkType = remarks::Type::Missed; |
237 | ToSerializeRemark.PassName = "inline" ; |
238 | ToSerializeRemark.RemarkName = "NoDefinition" ; |
239 | ToSerializeRemark.FunctionName = "foo" ; |
240 | ToSerializeRemark.Loc = remarks::RemarkLocation{.SourceFilePath: "file.c" , .SourceLine: 3, .SourceColumn: 12}; |
241 | ToSerializeRemark.Hotness = 0; |
242 | ToSerializeRemark.Args.emplace_back(); |
243 | ToSerializeRemark.Args.back().Key = "Callee" ; |
244 | ToSerializeRemark.Args.back().Val = "bar" ; |
245 | ToSerializeRemark.Args.emplace_back(); |
246 | ToSerializeRemark.Args.back().Key = "String" ; |
247 | ToSerializeRemark.Args.back().Val = " will not be inlined into " ; |
248 | ToSerializeRemark.Args.emplace_back(); |
249 | ToSerializeRemark.Args.back().Key = "Caller" ; |
250 | ToSerializeRemark.Args.back().Val = "foo" ; |
251 | ToSerializeRemark.Args.back().Loc = remarks::RemarkLocation{.SourceFilePath: "file.c" , .SourceLine: 2, .SourceColumn: 0}; |
252 | ToSerializeRemark.Args.emplace_back(); |
253 | ToSerializeRemark.Args.back().Key = "String" ; |
254 | ToSerializeRemark.Args.back().Val = " because its definition is unavailable" ; |
255 | BSStrTab.internalize(R&: ToSerializeRemark); |
256 | std::string BSBuf; |
257 | raw_string_ostream BSStream(BSBuf); |
258 | Expected<std::unique_ptr<remarks::RemarkSerializer>> BSSerializer = |
259 | remarks::createRemarkSerializer(RemarksFormat: remarks::Format::Bitstream, |
260 | Mode: remarks::SerializerMode::Standalone, |
261 | OS&: BSStream, StrTab: std::move(BSStrTab)); |
262 | EXPECT_FALSE(errorToBool(BSSerializer.takeError())); |
263 | (*BSSerializer)->emit(Remark: ToSerializeRemark); |
264 | |
265 | StringRef Buf = BSStream.str(); |
266 | LLVMRemarkParserRef Parser = |
267 | LLVMRemarkParserCreateBitstream(Buf: Buf.data(), Size: Buf.size()); |
268 | LLVMRemarkEntryRef = LLVMRemarkParserGetNext(Parser); |
269 | EXPECT_FALSE(Remark == nullptr); |
270 | EXPECT_EQ(LLVMRemarkEntryGetType(Remark), LLVMRemarkTypeMissed); |
271 | EXPECT_EQ(checkStr(LLVMRemarkEntryGetPassName(Remark), 6), "inline" ); |
272 | EXPECT_EQ(checkStr(LLVMRemarkEntryGetRemarkName(Remark), 12), "NoDefinition" ); |
273 | EXPECT_EQ(checkStr(LLVMRemarkEntryGetFunctionName(Remark), 3), "foo" ); |
274 | LLVMRemarkDebugLocRef DL = LLVMRemarkEntryGetDebugLoc(Remark); |
275 | EXPECT_EQ(checkStr(LLVMRemarkDebugLocGetSourceFilePath(DL), 6), "file.c" ); |
276 | EXPECT_EQ(LLVMRemarkDebugLocGetSourceLine(DL), 3U); |
277 | EXPECT_EQ(LLVMRemarkDebugLocGetSourceColumn(DL), 12U); |
278 | EXPECT_EQ(LLVMRemarkEntryGetHotness(Remark), 0U); |
279 | EXPECT_EQ(LLVMRemarkEntryGetNumArgs(Remark), 4U); |
280 | |
281 | unsigned ArgID = 0; |
282 | LLVMRemarkArgRef Arg = LLVMRemarkEntryGetFirstArg(Remark); |
283 | do { |
284 | switch (ArgID) { |
285 | case 0: |
286 | EXPECT_EQ(checkStr(LLVMRemarkArgGetKey(Arg), 6), "Callee" ); |
287 | EXPECT_EQ(checkStr(LLVMRemarkArgGetValue(Arg), 3), "bar" ); |
288 | EXPECT_EQ(LLVMRemarkArgGetDebugLoc(Arg), nullptr); |
289 | break; |
290 | case 1: |
291 | EXPECT_EQ(checkStr(LLVMRemarkArgGetKey(Arg), 6), "String" ); |
292 | EXPECT_EQ(checkStr(LLVMRemarkArgGetValue(Arg), 26), |
293 | " will not be inlined into " ); |
294 | EXPECT_EQ(LLVMRemarkArgGetDebugLoc(Arg), nullptr); |
295 | break; |
296 | case 2: { |
297 | EXPECT_EQ(checkStr(LLVMRemarkArgGetKey(Arg), 6), "Caller" ); |
298 | EXPECT_EQ(checkStr(LLVMRemarkArgGetValue(Arg), 3), "foo" ); |
299 | LLVMRemarkDebugLocRef DL = LLVMRemarkArgGetDebugLoc(Arg); |
300 | EXPECT_EQ(checkStr(LLVMRemarkDebugLocGetSourceFilePath(DL), 6), "file.c" ); |
301 | EXPECT_EQ(LLVMRemarkDebugLocGetSourceLine(DL), 2U); |
302 | EXPECT_EQ(LLVMRemarkDebugLocGetSourceColumn(DL), 0U); |
303 | break; |
304 | } |
305 | case 3: |
306 | EXPECT_EQ(checkStr(LLVMRemarkArgGetKey(Arg), 6), "String" ); |
307 | EXPECT_EQ(checkStr(LLVMRemarkArgGetValue(Arg), 38), |
308 | " because its definition is unavailable" ); |
309 | EXPECT_EQ(LLVMRemarkArgGetDebugLoc(Arg), nullptr); |
310 | break; |
311 | default: |
312 | break; |
313 | } |
314 | ++ArgID; |
315 | } while ((Arg = LLVMRemarkEntryGetNextArg(It: Arg, Remark))); |
316 | |
317 | LLVMRemarkEntryDispose(Remark); |
318 | |
319 | EXPECT_EQ(LLVMRemarkParserGetNext(Parser), nullptr); |
320 | |
321 | EXPECT_FALSE(LLVMRemarkParserHasError(Parser)); |
322 | LLVMRemarkParserDispose(Parser); |
323 | } |
324 | |
325 | static void parseBad(StringRef Input, const char *ErrorMsg) { |
326 | Expected<std::unique_ptr<remarks::RemarkParser>> MaybeBSParser = |
327 | remarks::createRemarkParser(ParserFormat: remarks::Format::Bitstream, Buf: Input); |
328 | EXPECT_FALSE(errorToBool(MaybeBSParser.takeError())); |
329 | EXPECT_TRUE(*MaybeBSParser != nullptr); |
330 | |
331 | remarks::RemarkParser &BSParser = **MaybeBSParser; |
332 | Expected<std::unique_ptr<remarks::Remark>> = BSParser.next(); |
333 | EXPECT_EQ(ErrorMsg, toString(BSRemark.takeError())); // Expect an error. |
334 | } |
335 | |
336 | TEST(BitstreamRemarks, ParsingEmpty) { |
337 | parseBad(Input: StringRef(), ErrorMsg: "End of file reached." ); |
338 | } |
339 | |
340 | TEST(BitstreamRemarks, ParsingBadMagic) { |
341 | parseBad(Input: "KRMR" , ErrorMsg: "Unknown magic number: expecting RMRK, got KRMR." ); |
342 | } |
343 | |
344 | // Testing malformed bitstream is not easy. We would need to replace bytes in |
345 | // the stream to create malformed and unknown records and blocks. There is no |
346 | // textual format for bitstream that can be decoded, modified and encoded |
347 | // back. |
348 | |
349 | // FIXME: Add tests for the following error messages: |
350 | // * Error while parsing META_BLOCK: malformed record entry |
351 | // (RECORD_META_CONTAINER_INFO). |
352 | // * Error while parsing META_BLOCK: malformed record entry |
353 | // (RECORD_META_REMARK_VERSION). |
354 | // * Error while parsing META_BLOCK: malformed record entry |
355 | // (RECORD_META_STRTAB). |
356 | // * Error while parsing META_BLOCK: malformed record entry |
357 | // (RECORD_META_EXTERNAL_FILE). |
358 | // * Error while parsing META_BLOCK: unknown record entry (NUM). |
359 | // * Error while parsing REMARK_BLOCK: malformed record entry |
360 | // (RECORD_REMARK_HEADER). |
361 | // * Error while parsing REMARK_BLOCK: malformed record entry |
362 | // (RECORD_REMARK_DEBUG_LOC). |
363 | // * Error while parsing REMARK_BLOCK: malformed record entry |
364 | // (RECORD_REMARK_HOTNESS). |
365 | // * Error while parsing REMARK_BLOCK: malformed record entry |
366 | // (RECORD_REMARK_ARG_WITH_DEBUGLOC). |
367 | // * Error while parsing REMARK_BLOCK: malformed record entry |
368 | // (RECORD_REMARK_ARG_WITHOUT_DEBUGLOC). |
369 | // * Error while parsing REMARK_BLOCK: unknown record entry (NUM). |
370 | // * Error while parsing META_BLOCK: expecting [ENTER_SUBBLOCO, META_BLOCK, |
371 | // ...]. |
372 | // * Error while entering META_BLOCK. |
373 | // * Error while parsing META_BLOCK: expecting records. |
374 | // * Error while parsing META_BLOCK: unterminated block. |
375 | // * Error while parsing REMARK_BLOCK: expecting [ENTER_SUBBLOCO, REMARK_BLOCK, |
376 | // ...]. |
377 | // * Error while entering REMARK_BLOCK. |
378 | // * Error while parsing REMARK_BLOCK: expecting records. |
379 | // * Error while parsing REMARK_BLOCK: unterminated block. |
380 | // * Error while parsing BLOCKINFO_BLOCK: expecting [ENTER_SUBBLOCK, |
381 | // BLOCKINFO_BLOCK, ...]. |
382 | // * Error while parsing BLOCKINFO_BLOCK. |
383 | // * Unexpected error while parsing bitstream. |
384 | // * Expecting META_BLOCK after the BLOCKINFO_BLOCK. |
385 | // * Error while parsing BLOCK_META: missing container version. |
386 | // * Error while parsing BLOCK_META: invalid container type. |
387 | // * Error while parsing BLOCK_META: missing container type. |
388 | // * Error while parsing BLOCK_META: missing string table. |
389 | // * Error while parsing BLOCK_META: missing remark version. |
390 | // * Error while parsing BLOCK_META: missing external file path. |
391 | // * Error while parsing external file's BLOCK_META: wrong container type. |
392 | // * Error while parsing external file's BLOCK_META: mismatching versions: |
393 | // original meta: NUM, external file meta: NUM. |
394 | // * Error while parsing BLOCK_REMARK: missing string table. |
395 | // * Error while parsing BLOCK_REMARK: missing remark type. |
396 | // * Error while parsing BLOCK_REMARK: unknown remark type. |
397 | // * Error while parsing BLOCK_REMARK: missing remark name. |
398 | // * Error while parsing BLOCK_REMARK: missing remark pass. |
399 | // * Error while parsing BLOCK_REMARK: missing remark function name. |
400 | // * Error while parsing BLOCK_REMARK: missing key in remark argument. |
401 | // * Error while parsing BLOCK_REMARK: missing value in remark argument. |
402 | |