|  | //===- unittest/Support/RemarksLinkingTest.cpp - Linking 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/Bitcode/BitcodeAnalyzer.h" | 
|  | #include "llvm/Remarks/RemarkLinker.h" | 
|  | #include "llvm/Remarks/RemarkSerializer.h" | 
|  | #include "llvm/Support/raw_ostream.h" | 
|  | #include "gtest/gtest.h" | 
|  | #include <string> | 
|  |  | 
|  | using namespace llvm; | 
|  |  | 
|  | static void serializeAndCheck(remarks::RemarkLinker &RL, | 
|  | remarks::Format OutputFormat, | 
|  | StringRef ExpectedOutput) { | 
|  | // 1. Create a serializer. | 
|  | // 2. Serialize all the remarks from the linker. | 
|  | // 3. Check that it matches the output. | 
|  | std::string Buf; | 
|  | raw_string_ostream OS(Buf); | 
|  | Error E = RL.serialize(OS, OutputFormat); | 
|  | EXPECT_FALSE(static_cast<bool>(E)); | 
|  |  | 
|  | // For bitstream, run it through the analyzer. | 
|  | if (OutputFormat == remarks::Format::Bitstream) { | 
|  | std::string AnalyzeBuf; | 
|  | raw_string_ostream AnalyzeOS(AnalyzeBuf); | 
|  | BCDumpOptions O(AnalyzeOS); | 
|  | O.ShowBinaryBlobs = true; | 
|  | BitcodeAnalyzer BA(OS.str()); | 
|  | EXPECT_FALSE(BA.analyze(O)); // Expect no errors. | 
|  | EXPECT_EQ(AnalyzeOS.str(), ExpectedOutput); | 
|  | } else { | 
|  | EXPECT_EQ(OS.str(), ExpectedOutput); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void check(remarks::Format InputFormat, StringRef Input, | 
|  | remarks::Format OutputFormat, StringRef ExpectedOutput, | 
|  | std::optional<bool> KeepAllRemarks = {}) { | 
|  | remarks::RemarkLinker RL; | 
|  | if (KeepAllRemarks) | 
|  | RL.setKeepAllRemarks(*KeepAllRemarks); | 
|  | EXPECT_FALSE(RL.link(Input, InputFormat)); | 
|  | serializeAndCheck(RL, OutputFormat, ExpectedOutput); | 
|  | } | 
|  |  | 
|  | static void check(remarks::Format InputFormat, StringRef Input, | 
|  | remarks::Format InputFormat2, StringRef Input2, | 
|  | remarks::Format OutputFormat, StringRef ExpectedOutput) { | 
|  | remarks::RemarkLinker RL; | 
|  | EXPECT_FALSE(RL.link(Input, InputFormat)); | 
|  | EXPECT_FALSE(RL.link(Input2, InputFormat2)); | 
|  | serializeAndCheck(RL, OutputFormat, ExpectedOutput); | 
|  | } | 
|  |  | 
|  | TEST(Remarks, LinkingGoodYAML) { | 
|  | // One YAML remark. | 
|  | check(remarks::Format::YAML, | 
|  | "--- !Missed\n" | 
|  | "Pass:            inline\n" | 
|  | "Name:            NoDefinition\n" | 
|  | "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n" | 
|  | "Function:        foo\n" | 
|  | "...\n", | 
|  | remarks::Format::YAML, | 
|  | "--- !Missed\n" | 
|  | "Pass:            inline\n" | 
|  | "Name:            NoDefinition\n" | 
|  | "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n" | 
|  | "Function:        foo\n" | 
|  | "...\n"); | 
|  |  | 
|  | // Check that we don't keep remarks without debug locations, unless | 
|  | // KeepAllRemarks is set. | 
|  | check(remarks::Format::YAML, | 
|  | "--- !Missed\n" | 
|  | "Pass:            inline\n" | 
|  | "Name:            NoDefinition\n" | 
|  | "Function:        foo\n" | 
|  | "...\n", | 
|  | remarks::Format::YAML, "", | 
|  | /*KeepAllRemarks=*/false); | 
|  | check(remarks::Format::YAML, | 
|  | "--- !Missed\n" | 
|  | "Pass:            inline\n" | 
|  | "Name:            NoDefinition\n" | 
|  | "Function:        foo\n" | 
|  | "...\n", | 
|  | remarks::Format::YAML, | 
|  | "--- !Missed\n" | 
|  | "Pass:            inline\n" | 
|  | "Name:            NoDefinition\n" | 
|  | "Function:        foo\n" | 
|  | "...\n"); | 
|  |  | 
|  | // Check that we deduplicate remarks. | 
|  | check(remarks::Format::YAML, | 
|  | "--- !Missed\n" | 
|  | "Pass:            inline\n" | 
|  | "Name:            NoDefinition\n" | 
|  | "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n" | 
|  | "Function:        foo\n" | 
|  | "...\n" | 
|  | "--- !Missed\n" | 
|  | "Pass:            inline\n" | 
|  | "Name:            NoDefinition\n" | 
|  | "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n" | 
|  | "Function:        foo\n" | 
|  | "...\n", | 
|  | remarks::Format::YAML, | 
|  | "--- !Missed\n" | 
|  | "Pass:            inline\n" | 
|  | "Name:            NoDefinition\n" | 
|  | "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n" | 
|  | "Function:        foo\n" | 
|  | "...\n"); | 
|  | } | 
|  |  | 
|  | TEST(Remarks, LinkingGoodBitstream) { | 
|  | // One YAML remark. | 
|  | check(remarks::Format::YAML, | 
|  | "--- !Missed\n" | 
|  | "Pass:            inline\n" | 
|  | "Name:            NoDefinition\n" | 
|  | "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n" | 
|  | "Function:        foo\n" | 
|  | "...\n", | 
|  | remarks::Format::Bitstream, | 
|  | "<BLOCKINFO_BLOCK/>\n" | 
|  | "<Meta BlockID=8 NumWords=12 BlockCodeSize=3>\n" | 
|  | "  <Container info codeid=1 abbrevid=4 op0=0 op1=2/>\n" | 
|  | "  <Remark version codeid=2 abbrevid=5 op0=0/>\n" | 
|  | "  <String table codeid=3 abbrevid=6/> blob data = " | 
|  | "'inline\\x00NoDefinition\\x00foo\\x00file.c\\x00'\n" | 
|  | "</Meta>\n" | 
|  | "<Remark BlockID=9 NumWords=4 BlockCodeSize=4>\n" | 
|  | "  <Remark header codeid=5 abbrevid=4 op0=2 op1=1 op2=0 op3=2/>\n" | 
|  | "  <Remark debug location codeid=6 abbrevid=5 op0=3 op1=3 op2=12/>\n" | 
|  | "</Remark>\n"); | 
|  |  | 
|  | // Check that we keep remarks without debug info. | 
|  | check(remarks::Format::YAML, | 
|  | "--- !Missed\n" | 
|  | "Pass:            inline\n" | 
|  | "Name:            NoDefinition\n" | 
|  | "Function:        foo\n" | 
|  | "...\n", | 
|  | remarks::Format::Bitstream, | 
|  | "<BLOCKINFO_BLOCK/>\n" | 
|  | "<Meta BlockID=8 NumWords=10 BlockCodeSize=3>\n" | 
|  | "  <Container info codeid=1 abbrevid=4 op0=0 op1=2/>\n" | 
|  | "  <Remark version codeid=2 abbrevid=5 op0=0/>\n" | 
|  | "  <String table codeid=3 abbrevid=6/> blob data = " | 
|  | "'inline\\x00NoDefinition\\x00foo\\x00'\n" | 
|  | "</Meta>\n" | 
|  | "<Remark BlockID=9 NumWords=1 BlockCodeSize=4>\n" | 
|  | "  <Remark header codeid=5 abbrevid=4 op0=2 op1=1 op2=0 op3=2/>\n" | 
|  | "</Remark>\n"); | 
|  |  | 
|  | // Check that we deduplicate remarks. | 
|  | check(remarks::Format::YAML, | 
|  | "--- !Missed\n" | 
|  | "Pass:            inline\n" | 
|  | "Name:            NoDefinition\n" | 
|  | "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n" | 
|  | "Function:        foo\n" | 
|  | "...\n" | 
|  | "--- !Missed\n" | 
|  | "Pass:            inline\n" | 
|  | "Name:            NoDefinition\n" | 
|  | "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n" | 
|  | "Function:        foo\n" | 
|  | "...\n", | 
|  | remarks::Format::Bitstream, | 
|  | "<BLOCKINFO_BLOCK/>\n" | 
|  | "<Meta BlockID=8 NumWords=12 BlockCodeSize=3>\n" | 
|  | "  <Container info codeid=1 abbrevid=4 op0=0 op1=2/>\n" | 
|  | "  <Remark version codeid=2 abbrevid=5 op0=0/>\n" | 
|  | "  <String table codeid=3 abbrevid=6/> blob data = " | 
|  | "'inline\\x00NoDefinition\\x00foo\\x00file.c\\x00'\n" | 
|  | "</Meta>\n" | 
|  | "<Remark BlockID=9 NumWords=4 BlockCodeSize=4>\n" | 
|  | "  <Remark header codeid=5 abbrevid=4 op0=2 op1=1 op2=0 op3=2/>\n" | 
|  | "  <Remark debug location codeid=6 abbrevid=5 op0=3 op1=3 op2=12/>\n" | 
|  | "</Remark>\n"); | 
|  | } | 
|  |  | 
|  | TEST(Remarks, LinkingGoodStrTab) { | 
|  | // Check that remarks from different entries use the same strtab. | 
|  | check(remarks::Format::YAML, | 
|  | "--- !Missed\n" | 
|  | "Pass:            inline\n" | 
|  | "Name:            NoDefinition\n" | 
|  | "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n" | 
|  | "Function:        foo\n" | 
|  | "...\n", | 
|  | remarks::Format::YAML, | 
|  | "--- !Passed\n" | 
|  | "Pass:            inline\n" | 
|  | "Name:            Ok\n" | 
|  | "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n" | 
|  | "Function:        foo\n" | 
|  | "...\n", | 
|  | remarks::Format::Bitstream, | 
|  | "<BLOCKINFO_BLOCK/>\n" | 
|  | "<Meta BlockID=8 NumWords=13 BlockCodeSize=3>\n" | 
|  | "  <Container info codeid=1 abbrevid=4 op0=0 op1=2/>\n" | 
|  | "  <Remark version codeid=2 abbrevid=5 op0=0/>\n" | 
|  | "  <String table codeid=3 abbrevid=6/> blob data = " | 
|  | "'inline\\x00NoDefinition\\x00foo\\x00file.c\\x00Ok\\x00'\n" | 
|  | "</Meta>\n" | 
|  | "<Remark BlockID=9 NumWords=4 BlockCodeSize=4>\n" | 
|  | "  <Remark header codeid=5 abbrevid=4 op0=1 op1=4 op2=0 op3=2/>\n" | 
|  | "  <Remark debug location codeid=6 abbrevid=5 op0=3 op1=3 op2=12/>\n" | 
|  | "</Remark>\n" | 
|  | "<Remark BlockID=9 NumWords=4 BlockCodeSize=4>\n" | 
|  | "  <Remark header codeid=5 abbrevid=4 op0=2 op1=1 op2=0 op3=2/>\n" | 
|  | "  <Remark debug location codeid=6 abbrevid=5 op0=3 op1=3 op2=12/>\n" | 
|  | "</Remark>\n"); | 
|  | } | 
|  |  | 
|  | // Check that we propagate parsing errors. | 
|  | TEST(Remarks, LinkingError) { | 
|  | remarks::RemarkLinker RL; | 
|  | { | 
|  | Error E = RL.link("badyaml", remarks::Format::YAML); | 
|  | EXPECT_TRUE(static_cast<bool>(E)); | 
|  | EXPECT_EQ(toString(std::move(E)), | 
|  | "YAML:1:1: error: document root is not of mapping type.\n" | 
|  | "\n" | 
|  | "badyaml\n" | 
|  | "^~~~~~~\n" | 
|  | "\n"); | 
|  | } | 
|  |  | 
|  | { | 
|  | // Check that the prepend path is propagated and fails with the full path. | 
|  | // Also ensures that the remark format is correctly auto-detected. | 
|  | RL.setExternalFilePrependPath("/baddir/"); | 
|  | Error E = RL.link(StringRef( | 
|  | "REMARKS\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0badfile.opt.yaml", 40)); | 
|  | EXPECT_TRUE(static_cast<bool>(E)); | 
|  | std::string ErrorMessage = toString(std::move(E)); | 
|  | EXPECT_EQ(StringRef(ErrorMessage).lower(), | 
|  | StringRef("'/baddir/badfile.opt.yaml': No such file or directory") | 
|  | .lower()); | 
|  | } | 
|  | } |