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 
parseGood(const char (& Buf)[N])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> FromYAMLRemark = nullptr;
34   remarks::RemarkParser &Parser = **MaybeParser;
35   Expected<std::unique_ptr<remarks::Remark>> 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(*FromYAMLRemark);
48   std::string BSBuf;
49   raw_string_ostream BSStream(BSBuf);
50   Expected<std::unique_ptr<remarks::RemarkSerializer>> BSSerializer =
51       remarks::createRemarkSerializer(remarks::Format::Bitstream,
52                                       remarks::SerializerMode::Standalone,
53                                       BSStream, std::move(BSStrTab));
54   EXPECT_FALSE(errorToBool(BSSerializer.takeError()));
55   (*BSSerializer)->emit(*FromYAMLRemark);
56 
57   // 3.
58   Expected<std::unique_ptr<remarks::RemarkParser>> MaybeBSParser =
59       remarks::createRemarkParser(remarks::Format::Bitstream, BSStream.str());
60   EXPECT_FALSE(errorToBool(MaybeBSParser.takeError()));
61   EXPECT_TRUE(*MaybeBSParser != nullptr);
62 
63   std::unique_ptr<remarks::Remark> FromBSRemark = nullptr;
64   remarks::RemarkParser &BSParser = **MaybeBSParser;
65   Expected<std::unique_ptr<remarks::Remark>> BSRemark = 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 
TEST(BitstreamRemarks,ParsingGood)78 TEST(BitstreamRemarks, ParsingGood) {
79   parseGood("\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("\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("\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 COMMON_REMARK "\nPass: inline\nName: NoDefinition\nFunction: foo\n\n"
119 // Test all the types.
TEST(BitstreamRemarks,ParsingTypes)120 TEST(BitstreamRemarks, ParsingTypes) {
121   // Type: Passed
122   parseGood("--- !Passed" COMMON_REMARK);
123   // Type: Missed
124   parseGood("--- !Missed" COMMON_REMARK);
125   // Type: Analysis
126   parseGood("--- !Analysis" COMMON_REMARK);
127   // Type: AnalysisFPCommute
128   parseGood("--- !AnalysisFPCommute" COMMON_REMARK);
129   // Type: AnalysisAliasing
130   parseGood("--- !AnalysisAliasing" COMMON_REMARK);
131   // Type: Failure
132   parseGood("--- !Failure" COMMON_REMARK);
133 }
134 #undef COMMON_REMARK
135 
checkStr(StringRef Str,unsigned ExpectedLen)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 
TEST(BitstreamRemarks,Contents)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(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>> MaybeRemark = 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 &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 
checkStr(LLVMRemarkStringRef Str,unsigned ExpectedLen)225 static inline StringRef checkStr(LLVMRemarkStringRef Str,
226                                  unsigned ExpectedLen) {
227   const char *StrData = LLVMRemarkStringGetData(Str);
228   unsigned StrLen = LLVMRemarkStringGetLen(Str);
229   EXPECT_EQ(StrLen, ExpectedLen);
230   return StringRef(StrData, StrLen);
231 }
232 
TEST(BitstreamRemarks,ContentsCAPI)233 TEST(BitstreamRemarks, ContentsCAPI) {
234   remarks::StringTable BSStrTab;
235   remarks::Remark ToSerializeRemark;
236   ToSerializeRemark.RemarkType = remarks::Type::Missed;
237   ToSerializeRemark.PassName = "inline";
238   ToSerializeRemark.RemarkName = "NoDefinition";
239   ToSerializeRemark.FunctionName = "foo";
240   ToSerializeRemark.Loc = remarks::RemarkLocation{"file.c", 3, 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{"file.c", 2, 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(ToSerializeRemark);
256   std::string BSBuf;
257   raw_string_ostream BSStream(BSBuf);
258   Expected<std::unique_ptr<remarks::RemarkSerializer>> BSSerializer =
259       remarks::createRemarkSerializer(remarks::Format::Bitstream,
260                                       remarks::SerializerMode::Standalone,
261                                       BSStream, std::move(BSStrTab));
262   EXPECT_FALSE(errorToBool(BSSerializer.takeError()));
263   (*BSSerializer)->emit(ToSerializeRemark);
264 
265   StringRef Buf = BSStream.str();
266   LLVMRemarkParserRef Parser =
267       LLVMRemarkParserCreateBitstream(Buf.data(), Buf.size());
268   LLVMRemarkEntryRef Remark = 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(Arg, Remark)));
316 
317   LLVMRemarkEntryDispose(Remark);
318 
319   EXPECT_EQ(LLVMRemarkParserGetNext(Parser), nullptr);
320 
321   EXPECT_FALSE(LLVMRemarkParserHasError(Parser));
322   LLVMRemarkParserDispose(Parser);
323 }
324 
parseBad(StringRef Input,const char * ErrorMsg)325 static void parseBad(StringRef Input, const char *ErrorMsg) {
326   Expected<std::unique_ptr<remarks::RemarkParser>> MaybeBSParser =
327       remarks::createRemarkParser(remarks::Format::Bitstream, 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>> BSRemark = BSParser.next();
333   EXPECT_EQ(ErrorMsg, toString(BSRemark.takeError())); // Expect an error.
334 }
335 
TEST(BitstreamRemarks,ParsingEmpty)336 TEST(BitstreamRemarks, ParsingEmpty) {
337   parseBad(StringRef(), "End of file reached.");
338 }
339 
TEST(BitstreamRemarks,ParsingBadMagic)340 TEST(BitstreamRemarks, ParsingBadMagic) {
341   parseBad("KRMR", "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