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> 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 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. 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 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(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 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 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 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 336 TEST(BitstreamRemarks, ParsingEmpty) { 337 parseBad(StringRef(), "End of file reached."); 338 } 339 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