|  | //===- unittests/Support/BitstreamRemarksParsingTest.cpp - Parsing tests --===// | 
|  | // | 
|  | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
|  | // See https://llvm.org/LICENSE.txt for license information. | 
|  | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #include "llvm-c/Remarks.h" | 
|  | #include "llvm/Remarks/Remark.h" | 
|  | #include "llvm/Remarks/RemarkParser.h" | 
|  | #include "llvm/Remarks/RemarkSerializer.h" | 
|  | #include "gtest/gtest.h" | 
|  |  | 
|  | using namespace llvm; | 
|  |  | 
|  | template <size_t N> void parseGood(const char (&Buf)[N]) { | 
|  | // 1. Parse the YAML remark -> FromYAMLRemark | 
|  | // 2. Serialize it to bitstream -> BSStream | 
|  | // 3. Parse it back -> FromBSRemark | 
|  | // 4. Compare the remark objects | 
|  | // | 
|  | // This testing methodology has the drawback of relying on both the YAML | 
|  | // remark parser and the bitstream remark serializer. It does simplify | 
|  | // testing a lot, since working directly with bitstream is not that easy. | 
|  |  | 
|  | // 1. | 
|  | Expected<std::unique_ptr<remarks::RemarkParser>> MaybeParser = | 
|  | remarks::createRemarkParser(remarks::Format::YAML, {Buf, N - 1}); | 
|  | EXPECT_FALSE(errorToBool(MaybeParser.takeError())); | 
|  | EXPECT_TRUE(*MaybeParser != nullptr); | 
|  |  | 
|  | std::unique_ptr<remarks::Remark> FromYAMLRemark = nullptr; | 
|  | remarks::RemarkParser &Parser = **MaybeParser; | 
|  | Expected<std::unique_ptr<remarks::Remark>> Remark = Parser.next(); | 
|  | EXPECT_FALSE(errorToBool(Remark.takeError())); // Check for parsing errors. | 
|  | EXPECT_TRUE(*Remark != nullptr);               // At least one remark. | 
|  | // Keep the previous remark around. | 
|  | FromYAMLRemark = std::move(*Remark); | 
|  | Remark = Parser.next(); | 
|  | Error E = Remark.takeError(); | 
|  | EXPECT_TRUE(E.isA<remarks::EndOfFileError>()); | 
|  | EXPECT_TRUE(errorToBool(std::move(E))); // Check for parsing errors. | 
|  |  | 
|  | // 2. | 
|  | remarks::StringTable BSStrTab; | 
|  | BSStrTab.internalize(*FromYAMLRemark); | 
|  | std::string BSBuf; | 
|  | raw_string_ostream BSStream(BSBuf); | 
|  | Expected<std::unique_ptr<remarks::RemarkSerializer>> BSSerializer = | 
|  | remarks::createRemarkSerializer(remarks::Format::Bitstream, | 
|  | remarks::SerializerMode::Standalone, | 
|  | BSStream, std::move(BSStrTab)); | 
|  | EXPECT_FALSE(errorToBool(BSSerializer.takeError())); | 
|  | (*BSSerializer)->emit(*FromYAMLRemark); | 
|  |  | 
|  | // 3. | 
|  | Expected<std::unique_ptr<remarks::RemarkParser>> MaybeBSParser = | 
|  | remarks::createRemarkParser(remarks::Format::Bitstream, BSStream.str()); | 
|  | EXPECT_FALSE(errorToBool(MaybeBSParser.takeError())); | 
|  | EXPECT_TRUE(*MaybeBSParser != nullptr); | 
|  |  | 
|  | std::unique_ptr<remarks::Remark> FromBSRemark = nullptr; | 
|  | remarks::RemarkParser &BSParser = **MaybeBSParser; | 
|  | Expected<std::unique_ptr<remarks::Remark>> BSRemark = BSParser.next(); | 
|  | EXPECT_FALSE(errorToBool(BSRemark.takeError())); // Check for parsing errors. | 
|  | EXPECT_TRUE(*BSRemark != nullptr);               // At least one remark. | 
|  | // Keep the previous remark around. | 
|  | FromBSRemark = std::move(*BSRemark); | 
|  | BSRemark = BSParser.next(); | 
|  | Error BSE = BSRemark.takeError(); | 
|  | EXPECT_TRUE(BSE.isA<remarks::EndOfFileError>()); | 
|  | EXPECT_TRUE(errorToBool(std::move(BSE))); // Check for parsing errors. | 
|  |  | 
|  | EXPECT_EQ(*FromYAMLRemark, *FromBSRemark); | 
|  | } | 
|  |  | 
|  | TEST(BitstreamRemarks, ParsingGood) { | 
|  | parseGood("\n" | 
|  | "--- !Missed\n" | 
|  | "Pass: inline\n" | 
|  | "Name: NoDefinition\n" | 
|  | "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" | 
|  | "Function: foo\n" | 
|  | "Args:\n" | 
|  | "  - Callee: bar\n" | 
|  | "  - String: ' will not be inlined into '\n" | 
|  | "  - Caller: foo\n" | 
|  | "    DebugLoc: { File: file.c, Line: 2, Column: 0 }\n" | 
|  | "  - String: ' because its definition is unavailable'\n" | 
|  | ""); | 
|  |  | 
|  | // No debug loc should also pass. | 
|  | parseGood("\n" | 
|  | "--- !Missed\n" | 
|  | "Pass: inline\n" | 
|  | "Name: NoDefinition\n" | 
|  | "Function: foo\n" | 
|  | "Args:\n" | 
|  | "  - Callee: bar\n" | 
|  | "  - String: ' will not be inlined into '\n" | 
|  | "  - Caller: foo\n" | 
|  | "    DebugLoc: { File: file.c, Line: 2, Column: 0 }\n" | 
|  | "  - String: ' because its definition is unavailable'\n" | 
|  | ""); | 
|  |  | 
|  | // No args is also ok. | 
|  | parseGood("\n" | 
|  | "--- !Missed\n" | 
|  | "Pass: inline\n" | 
|  | "Name: NoDefinition\n" | 
|  | "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" | 
|  | "Function: foo\n" | 
|  | ""); | 
|  | } | 
|  |  | 
|  | // Mandatory common part of a remark. | 
|  | #define COMMON_REMARK "\nPass: inline\nName: NoDefinition\nFunction: foo\n\n" | 
|  | // Test all the types. | 
|  | TEST(BitstreamRemarks, ParsingTypes) { | 
|  | // Type: Passed | 
|  | parseGood("--- !Passed" COMMON_REMARK); | 
|  | // Type: Missed | 
|  | parseGood("--- !Missed" COMMON_REMARK); | 
|  | // Type: Analysis | 
|  | parseGood("--- !Analysis" COMMON_REMARK); | 
|  | // Type: AnalysisFPCommute | 
|  | parseGood("--- !AnalysisFPCommute" COMMON_REMARK); | 
|  | // Type: AnalysisAliasing | 
|  | parseGood("--- !AnalysisAliasing" COMMON_REMARK); | 
|  | // Type: Failure | 
|  | parseGood("--- !Failure" COMMON_REMARK); | 
|  | } | 
|  | #undef COMMON_REMARK | 
|  |  | 
|  | static inline StringRef checkStr(StringRef Str, unsigned ExpectedLen) { | 
|  | const char *StrData = Str.data(); | 
|  | unsigned StrLen = Str.size(); | 
|  | EXPECT_EQ(StrLen, ExpectedLen); | 
|  | return StringRef(StrData, StrLen); | 
|  | } | 
|  |  | 
|  | TEST(BitstreamRemarks, Contents) { | 
|  | StringRef Buf = "\n" | 
|  | "--- !Missed\n" | 
|  | "Pass: inline\n" | 
|  | "Name: NoDefinition\n" | 
|  | "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" | 
|  | "Function: foo\n" | 
|  | "Hotness: 4\n" | 
|  | "Args:\n" | 
|  | "  - Callee: bar\n" | 
|  | "  - String: ' will not be inlined into '\n" | 
|  | "  - Caller: foo\n" | 
|  | "    DebugLoc: { File: file.c, Line: 2, Column: 0 }\n" | 
|  | "  - String: ' because its definition is unavailable'\n" | 
|  | "\n"; | 
|  |  | 
|  | Expected<std::unique_ptr<remarks::RemarkParser>> MaybeParser = | 
|  | remarks::createRemarkParser(remarks::Format::YAML, Buf); | 
|  | EXPECT_FALSE(errorToBool(MaybeParser.takeError())); | 
|  | EXPECT_TRUE(*MaybeParser != nullptr); | 
|  |  | 
|  | remarks::RemarkParser &Parser = **MaybeParser; | 
|  | Expected<std::unique_ptr<remarks::Remark>> MaybeRemark = Parser.next(); | 
|  | EXPECT_FALSE( | 
|  | errorToBool(MaybeRemark.takeError())); // Check for parsing errors. | 
|  | EXPECT_TRUE(*MaybeRemark != nullptr);      // At least one remark. | 
|  |  | 
|  | const remarks::Remark &Remark = **MaybeRemark; | 
|  | EXPECT_EQ(Remark.RemarkType, remarks::Type::Missed); | 
|  | EXPECT_EQ(checkStr(Remark.PassName, 6), "inline"); | 
|  | EXPECT_EQ(checkStr(Remark.RemarkName, 12), "NoDefinition"); | 
|  | EXPECT_EQ(checkStr(Remark.FunctionName, 3), "foo"); | 
|  | EXPECT_TRUE(Remark.Loc); | 
|  | const remarks::RemarkLocation &RL = *Remark.Loc; | 
|  | EXPECT_EQ(checkStr(RL.SourceFilePath, 6), "file.c"); | 
|  | EXPECT_EQ(RL.SourceLine, 3U); | 
|  | EXPECT_EQ(RL.SourceColumn, 12U); | 
|  | EXPECT_TRUE(Remark.Hotness); | 
|  | EXPECT_EQ(*Remark.Hotness, 4U); | 
|  | EXPECT_EQ(Remark.Args.size(), 4U); | 
|  |  | 
|  | unsigned ArgID = 0; | 
|  | for (const remarks::Argument &Arg : Remark.Args) { | 
|  | switch (ArgID) { | 
|  | case 0: | 
|  | EXPECT_EQ(checkStr(Arg.Key, 6), "Callee"); | 
|  | EXPECT_EQ(checkStr(Arg.Val, 3), "bar"); | 
|  | EXPECT_FALSE(Arg.Loc); | 
|  | break; | 
|  | case 1: | 
|  | EXPECT_EQ(checkStr(Arg.Key, 6), "String"); | 
|  | EXPECT_EQ(checkStr(Arg.Val, 26), " will not be inlined into "); | 
|  | EXPECT_FALSE(Arg.Loc); | 
|  | break; | 
|  | case 2: { | 
|  | EXPECT_EQ(checkStr(Arg.Key, 6), "Caller"); | 
|  | EXPECT_EQ(checkStr(Arg.Val, 3), "foo"); | 
|  | EXPECT_TRUE(Arg.Loc); | 
|  | const remarks::RemarkLocation &RL = *Arg.Loc; | 
|  | EXPECT_EQ(checkStr(RL.SourceFilePath, 6), "file.c"); | 
|  | EXPECT_EQ(RL.SourceLine, 2U); | 
|  | EXPECT_EQ(RL.SourceColumn, 0U); | 
|  | break; | 
|  | } | 
|  | case 3: | 
|  | EXPECT_EQ(checkStr(Arg.Key, 6), "String"); | 
|  | EXPECT_EQ(checkStr(Arg.Val, 38), | 
|  | " because its definition is unavailable"); | 
|  | EXPECT_FALSE(Arg.Loc); | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | ++ArgID; | 
|  | } | 
|  |  | 
|  | MaybeRemark = Parser.next(); | 
|  | Error E = MaybeRemark.takeError(); | 
|  | EXPECT_TRUE(E.isA<remarks::EndOfFileError>()); | 
|  | EXPECT_TRUE(errorToBool(std::move(E))); // Check for parsing errors. | 
|  | } | 
|  |  | 
|  | static inline StringRef checkStr(LLVMRemarkStringRef Str, | 
|  | unsigned ExpectedLen) { | 
|  | const char *StrData = LLVMRemarkStringGetData(Str); | 
|  | unsigned StrLen = LLVMRemarkStringGetLen(Str); | 
|  | EXPECT_EQ(StrLen, ExpectedLen); | 
|  | return StringRef(StrData, StrLen); | 
|  | } | 
|  |  | 
|  | TEST(BitstreamRemarks, ContentsCAPI) { | 
|  | remarks::StringTable BSStrTab; | 
|  | remarks::Remark ToSerializeRemark; | 
|  | ToSerializeRemark.RemarkType = remarks::Type::Missed; | 
|  | ToSerializeRemark.PassName = "inline"; | 
|  | ToSerializeRemark.RemarkName = "NoDefinition"; | 
|  | ToSerializeRemark.FunctionName = "foo"; | 
|  | ToSerializeRemark.Loc = remarks::RemarkLocation{"file.c", 3, 12}; | 
|  | ToSerializeRemark.Hotness = 0; | 
|  | ToSerializeRemark.Args.emplace_back(); | 
|  | ToSerializeRemark.Args.back().Key = "Callee"; | 
|  | ToSerializeRemark.Args.back().Val = "bar"; | 
|  | ToSerializeRemark.Args.emplace_back(); | 
|  | ToSerializeRemark.Args.back().Key = "String"; | 
|  | ToSerializeRemark.Args.back().Val = " will not be inlined into "; | 
|  | ToSerializeRemark.Args.emplace_back(); | 
|  | ToSerializeRemark.Args.back().Key = "Caller"; | 
|  | ToSerializeRemark.Args.back().Val = "foo"; | 
|  | ToSerializeRemark.Args.back().Loc = remarks::RemarkLocation{"file.c", 2, 0}; | 
|  | ToSerializeRemark.Args.emplace_back(); | 
|  | ToSerializeRemark.Args.back().Key = "String"; | 
|  | ToSerializeRemark.Args.back().Val = " because its definition is unavailable"; | 
|  | BSStrTab.internalize(ToSerializeRemark); | 
|  | std::string BSBuf; | 
|  | raw_string_ostream BSStream(BSBuf); | 
|  | Expected<std::unique_ptr<remarks::RemarkSerializer>> BSSerializer = | 
|  | remarks::createRemarkSerializer(remarks::Format::Bitstream, | 
|  | remarks::SerializerMode::Standalone, | 
|  | BSStream, std::move(BSStrTab)); | 
|  | EXPECT_FALSE(errorToBool(BSSerializer.takeError())); | 
|  | (*BSSerializer)->emit(ToSerializeRemark); | 
|  |  | 
|  | StringRef Buf = BSStream.str(); | 
|  | LLVMRemarkParserRef Parser = | 
|  | LLVMRemarkParserCreateBitstream(Buf.data(), Buf.size()); | 
|  | LLVMRemarkEntryRef Remark = LLVMRemarkParserGetNext(Parser); | 
|  | EXPECT_FALSE(Remark == nullptr); | 
|  | EXPECT_EQ(LLVMRemarkEntryGetType(Remark), LLVMRemarkTypeMissed); | 
|  | EXPECT_EQ(checkStr(LLVMRemarkEntryGetPassName(Remark), 6), "inline"); | 
|  | EXPECT_EQ(checkStr(LLVMRemarkEntryGetRemarkName(Remark), 12), "NoDefinition"); | 
|  | EXPECT_EQ(checkStr(LLVMRemarkEntryGetFunctionName(Remark), 3), "foo"); | 
|  | LLVMRemarkDebugLocRef DL = LLVMRemarkEntryGetDebugLoc(Remark); | 
|  | EXPECT_EQ(checkStr(LLVMRemarkDebugLocGetSourceFilePath(DL), 6), "file.c"); | 
|  | EXPECT_EQ(LLVMRemarkDebugLocGetSourceLine(DL), 3U); | 
|  | EXPECT_EQ(LLVMRemarkDebugLocGetSourceColumn(DL), 12U); | 
|  | EXPECT_EQ(LLVMRemarkEntryGetHotness(Remark), 0U); | 
|  | EXPECT_EQ(LLVMRemarkEntryGetNumArgs(Remark), 4U); | 
|  |  | 
|  | unsigned ArgID = 0; | 
|  | LLVMRemarkArgRef Arg = LLVMRemarkEntryGetFirstArg(Remark); | 
|  | do { | 
|  | switch (ArgID) { | 
|  | case 0: | 
|  | EXPECT_EQ(checkStr(LLVMRemarkArgGetKey(Arg), 6), "Callee"); | 
|  | EXPECT_EQ(checkStr(LLVMRemarkArgGetValue(Arg), 3), "bar"); | 
|  | EXPECT_EQ(LLVMRemarkArgGetDebugLoc(Arg), nullptr); | 
|  | break; | 
|  | case 1: | 
|  | EXPECT_EQ(checkStr(LLVMRemarkArgGetKey(Arg), 6), "String"); | 
|  | EXPECT_EQ(checkStr(LLVMRemarkArgGetValue(Arg), 26), | 
|  | " will not be inlined into "); | 
|  | EXPECT_EQ(LLVMRemarkArgGetDebugLoc(Arg), nullptr); | 
|  | break; | 
|  | case 2: { | 
|  | EXPECT_EQ(checkStr(LLVMRemarkArgGetKey(Arg), 6), "Caller"); | 
|  | EXPECT_EQ(checkStr(LLVMRemarkArgGetValue(Arg), 3), "foo"); | 
|  | LLVMRemarkDebugLocRef DL = LLVMRemarkArgGetDebugLoc(Arg); | 
|  | EXPECT_EQ(checkStr(LLVMRemarkDebugLocGetSourceFilePath(DL), 6), "file.c"); | 
|  | EXPECT_EQ(LLVMRemarkDebugLocGetSourceLine(DL), 2U); | 
|  | EXPECT_EQ(LLVMRemarkDebugLocGetSourceColumn(DL), 0U); | 
|  | break; | 
|  | } | 
|  | case 3: | 
|  | EXPECT_EQ(checkStr(LLVMRemarkArgGetKey(Arg), 6), "String"); | 
|  | EXPECT_EQ(checkStr(LLVMRemarkArgGetValue(Arg), 38), | 
|  | " because its definition is unavailable"); | 
|  | EXPECT_EQ(LLVMRemarkArgGetDebugLoc(Arg), nullptr); | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | ++ArgID; | 
|  | } while ((Arg = LLVMRemarkEntryGetNextArg(Arg, Remark))); | 
|  |  | 
|  | LLVMRemarkEntryDispose(Remark); | 
|  |  | 
|  | EXPECT_EQ(LLVMRemarkParserGetNext(Parser), nullptr); | 
|  |  | 
|  | EXPECT_FALSE(LLVMRemarkParserHasError(Parser)); | 
|  | LLVMRemarkParserDispose(Parser); | 
|  | } | 
|  |  | 
|  | static void parseBad(StringRef Input, const char *ErrorMsg) { | 
|  | Expected<std::unique_ptr<remarks::RemarkParser>> MaybeBSParser = | 
|  | remarks::createRemarkParser(remarks::Format::Bitstream, Input); | 
|  | EXPECT_FALSE(errorToBool(MaybeBSParser.takeError())); | 
|  | EXPECT_TRUE(*MaybeBSParser != nullptr); | 
|  |  | 
|  | remarks::RemarkParser &BSParser = **MaybeBSParser; | 
|  | Expected<std::unique_ptr<remarks::Remark>> BSRemark = BSParser.next(); | 
|  | EXPECT_EQ(ErrorMsg, toString(BSRemark.takeError())); // Expect an error. | 
|  | } | 
|  |  | 
|  | TEST(BitstreamRemarks, ParsingEmpty) { | 
|  | parseBad(StringRef(), "End of file reached."); | 
|  | } | 
|  |  | 
|  | TEST(BitstreamRemarks, ParsingBadMagic) { | 
|  | parseBad("KRMR", "Unknown magic number: expecting RMRK, got KRMR."); | 
|  | } | 
|  |  | 
|  | // Testing malformed bitstream is not easy. We would need to replace bytes in | 
|  | // the stream to create malformed and unknown records and blocks. There is no | 
|  | // textual format for bitstream that can be decoded, modified and encoded | 
|  | // back. | 
|  |  | 
|  | // FIXME: Add tests for the following error messages: | 
|  | // * Error while parsing META_BLOCK: malformed record entry | 
|  | // (RECORD_META_CONTAINER_INFO). | 
|  | // * Error while parsing META_BLOCK: malformed record entry | 
|  | // (RECORD_META_REMARK_VERSION). | 
|  | // * Error while parsing META_BLOCK: malformed record entry | 
|  | // (RECORD_META_STRTAB). | 
|  | // * Error while parsing META_BLOCK: malformed record entry | 
|  | // (RECORD_META_EXTERNAL_FILE). | 
|  | // * Error while parsing META_BLOCK: unknown record entry (NUM). | 
|  | // * Error while parsing REMARK_BLOCK: malformed record entry | 
|  | // (RECORD_REMARK_HEADER). | 
|  | // * Error while parsing REMARK_BLOCK: malformed record entry | 
|  | // (RECORD_REMARK_DEBUG_LOC). | 
|  | // * Error while parsing REMARK_BLOCK: malformed record entry | 
|  | // (RECORD_REMARK_HOTNESS). | 
|  | // * Error while parsing REMARK_BLOCK: malformed record entry | 
|  | // (RECORD_REMARK_ARG_WITH_DEBUGLOC). | 
|  | // * Error while parsing REMARK_BLOCK: malformed record entry | 
|  | // (RECORD_REMARK_ARG_WITHOUT_DEBUGLOC). | 
|  | // * Error while parsing REMARK_BLOCK: unknown record entry (NUM). | 
|  | // * Error while parsing META_BLOCK: expecting [ENTER_SUBBLOCO, META_BLOCK, | 
|  | // ...]. | 
|  | // * Error while entering META_BLOCK. | 
|  | // * Error while parsing META_BLOCK: expecting records. | 
|  | // * Error while parsing META_BLOCK: unterminated block. | 
|  | // * Error while parsing REMARK_BLOCK: expecting [ENTER_SUBBLOCO, REMARK_BLOCK, | 
|  | // ...]. | 
|  | // * Error while entering REMARK_BLOCK. | 
|  | // * Error while parsing REMARK_BLOCK: expecting records. | 
|  | // * Error while parsing REMARK_BLOCK: unterminated block. | 
|  | // * Error while parsing BLOCKINFO_BLOCK: expecting [ENTER_SUBBLOCK, | 
|  | // BLOCKINFO_BLOCK, ...]. | 
|  | // * Error while parsing BLOCKINFO_BLOCK. | 
|  | // * Unexpected error while parsing bitstream. | 
|  | // * Expecting META_BLOCK after the BLOCKINFO_BLOCK. | 
|  | // * Error while parsing BLOCK_META: missing container version. | 
|  | // * Error while parsing BLOCK_META: invalid container type. | 
|  | // * Error while parsing BLOCK_META: missing container type. | 
|  | // * Error while parsing BLOCK_META: missing string table. | 
|  | // * Error while parsing BLOCK_META: missing remark version. | 
|  | // * Error while parsing BLOCK_META: missing external file path. | 
|  | // * Error while parsing external file's BLOCK_META: wrong container type. | 
|  | // * Error while parsing external file's BLOCK_META: mismatching versions: | 
|  | // original meta: NUM, external file meta: NUM. | 
|  | // * Error while parsing BLOCK_REMARK: missing string table. | 
|  | // * Error while parsing BLOCK_REMARK: missing remark type. | 
|  | // * Error while parsing BLOCK_REMARK: unknown remark type. | 
|  | // * Error while parsing BLOCK_REMARK: missing remark name. | 
|  | // * Error while parsing BLOCK_REMARK: missing remark pass. | 
|  | // * Error while parsing BLOCK_REMARK: missing remark function name. | 
|  | // * Error while parsing BLOCK_REMARK: missing key in remark argument. | 
|  | // * Error while parsing BLOCK_REMARK: missing value in remark argument. |