1 | //===- unittest/Support/RemarksLinkingTest.cpp - Linking 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/Bitcode/BitcodeAnalyzer.h" |
10 | #include "llvm/Remarks/RemarkLinker.h" |
11 | #include "llvm/Remarks/RemarkSerializer.h" |
12 | #include "llvm/Support/raw_ostream.h" |
13 | #include "gtest/gtest.h" |
14 | #include <string> |
15 | |
16 | using namespace llvm; |
17 | |
18 | static void serializeAndCheck(remarks::RemarkLinker &RL, |
19 | remarks::Format OutputFormat, |
20 | StringRef ExpectedOutput) { |
21 | // 1. Create a serializer. |
22 | // 2. Serialize all the remarks from the linker. |
23 | // 3. Check that it matches the output. |
24 | std::string Buf; |
25 | raw_string_ostream OS(Buf); |
26 | Error E = RL.serialize(OS, RemarksFormat: OutputFormat); |
27 | EXPECT_FALSE(static_cast<bool>(E)); |
28 | |
29 | // For bitstream, run it through the analyzer. |
30 | if (OutputFormat == remarks::Format::Bitstream) { |
31 | std::string AnalyzeBuf; |
32 | raw_string_ostream AnalyzeOS(AnalyzeBuf); |
33 | BCDumpOptions O(AnalyzeOS); |
34 | O.ShowBinaryBlobs = true; |
35 | BitcodeAnalyzer BA(OS.str()); |
36 | EXPECT_FALSE(BA.analyze(O)); // Expect no errors. |
37 | EXPECT_EQ(AnalyzeOS.str(), ExpectedOutput); |
38 | } else { |
39 | EXPECT_EQ(OS.str(), ExpectedOutput); |
40 | } |
41 | } |
42 | |
43 | static void (remarks::Format InputFormat, StringRef Input, |
44 | remarks::Format OutputFormat, StringRef ExpectedOutput, |
45 | std::optional<bool> = {}) { |
46 | remarks::RemarkLinker RL; |
47 | if (KeepAllRemarks) |
48 | RL.setKeepAllRemarks(*KeepAllRemarks); |
49 | EXPECT_FALSE(RL.link(Input, InputFormat)); |
50 | serializeAndCheck(RL, OutputFormat, ExpectedOutput); |
51 | } |
52 | |
53 | static void (remarks::Format InputFormat, StringRef Input, |
54 | remarks::Format InputFormat2, StringRef Input2, |
55 | remarks::Format OutputFormat, StringRef ExpectedOutput) { |
56 | remarks::RemarkLinker RL; |
57 | EXPECT_FALSE(RL.link(Input, InputFormat)); |
58 | EXPECT_FALSE(RL.link(Input2, InputFormat2)); |
59 | serializeAndCheck(RL, OutputFormat, ExpectedOutput); |
60 | } |
61 | |
62 | TEST(Remarks, LinkingGoodYAML) { |
63 | // One YAML remark. |
64 | check(InputFormat: remarks::Format::YAML, |
65 | Input: "--- !Missed\n" |
66 | "Pass: inline\n" |
67 | "Name: NoDefinition\n" |
68 | "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" |
69 | "Function: foo\n" |
70 | "...\n" , |
71 | OutputFormat: remarks::Format::YAML, |
72 | ExpectedOutput: "--- !Missed\n" |
73 | "Pass: inline\n" |
74 | "Name: NoDefinition\n" |
75 | "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" |
76 | "Function: foo\n" |
77 | "...\n" ); |
78 | |
79 | // Check that we don't keep remarks without debug locations, unless |
80 | // KeepAllRemarks is set. |
81 | check(InputFormat: remarks::Format::YAML, |
82 | Input: "--- !Missed\n" |
83 | "Pass: inline\n" |
84 | "Name: NoDefinition\n" |
85 | "Function: foo\n" |
86 | "...\n" , |
87 | OutputFormat: remarks::Format::YAML, ExpectedOutput: "" , |
88 | /*KeepAllRemarks=*/false); |
89 | check(InputFormat: remarks::Format::YAML, |
90 | Input: "--- !Missed\n" |
91 | "Pass: inline\n" |
92 | "Name: NoDefinition\n" |
93 | "Function: foo\n" |
94 | "...\n" , |
95 | OutputFormat: remarks::Format::YAML, |
96 | ExpectedOutput: "--- !Missed\n" |
97 | "Pass: inline\n" |
98 | "Name: NoDefinition\n" |
99 | "Function: foo\n" |
100 | "...\n" ); |
101 | |
102 | // Check that we deduplicate remarks. |
103 | check(InputFormat: remarks::Format::YAML, |
104 | Input: "--- !Missed\n" |
105 | "Pass: inline\n" |
106 | "Name: NoDefinition\n" |
107 | "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" |
108 | "Function: foo\n" |
109 | "...\n" |
110 | "--- !Missed\n" |
111 | "Pass: inline\n" |
112 | "Name: NoDefinition\n" |
113 | "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" |
114 | "Function: foo\n" |
115 | "...\n" , |
116 | OutputFormat: remarks::Format::YAML, |
117 | ExpectedOutput: "--- !Missed\n" |
118 | "Pass: inline\n" |
119 | "Name: NoDefinition\n" |
120 | "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" |
121 | "Function: foo\n" |
122 | "...\n" ); |
123 | } |
124 | |
125 | TEST(Remarks, LinkingGoodBitstream) { |
126 | // One YAML remark. |
127 | check(InputFormat: remarks::Format::YAML, |
128 | Input: "--- !Missed\n" |
129 | "Pass: inline\n" |
130 | "Name: NoDefinition\n" |
131 | "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" |
132 | "Function: foo\n" |
133 | "...\n" , |
134 | OutputFormat: remarks::Format::Bitstream, |
135 | ExpectedOutput: "<BLOCKINFO_BLOCK/>\n" |
136 | "<Meta BlockID=8 NumWords=12 BlockCodeSize=3>\n" |
137 | " <Container info codeid=1 abbrevid=4 op0=0 op1=2/>\n" |
138 | " <Remark version codeid=2 abbrevid=5 op0=0/>\n" |
139 | " <String table codeid=3 abbrevid=6/> blob data = " |
140 | "'inline\\x00NoDefinition\\x00foo\\x00file.c\\x00'\n" |
141 | "</Meta>\n" |
142 | "<Remark BlockID=9 NumWords=4 BlockCodeSize=4>\n" |
143 | " <Remark header codeid=5 abbrevid=4 op0=2 op1=1 op2=0 op3=2/>\n" |
144 | " <Remark debug location codeid=6 abbrevid=5 op0=3 op1=3 op2=12/>\n" |
145 | "</Remark>\n" ); |
146 | |
147 | // Check that we keep remarks without debug info. |
148 | check(InputFormat: remarks::Format::YAML, |
149 | Input: "--- !Missed\n" |
150 | "Pass: inline\n" |
151 | "Name: NoDefinition\n" |
152 | "Function: foo\n" |
153 | "...\n" , |
154 | OutputFormat: remarks::Format::Bitstream, |
155 | ExpectedOutput: "<BLOCKINFO_BLOCK/>\n" |
156 | "<Meta BlockID=8 NumWords=10 BlockCodeSize=3>\n" |
157 | " <Container info codeid=1 abbrevid=4 op0=0 op1=2/>\n" |
158 | " <Remark version codeid=2 abbrevid=5 op0=0/>\n" |
159 | " <String table codeid=3 abbrevid=6/> blob data = " |
160 | "'inline\\x00NoDefinition\\x00foo\\x00'\n" |
161 | "</Meta>\n" |
162 | "<Remark BlockID=9 NumWords=1 BlockCodeSize=4>\n" |
163 | " <Remark header codeid=5 abbrevid=4 op0=2 op1=1 op2=0 op3=2/>\n" |
164 | "</Remark>\n" ); |
165 | |
166 | // Check that we deduplicate remarks. |
167 | check(InputFormat: remarks::Format::YAML, |
168 | Input: "--- !Missed\n" |
169 | "Pass: inline\n" |
170 | "Name: NoDefinition\n" |
171 | "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" |
172 | "Function: foo\n" |
173 | "...\n" |
174 | "--- !Missed\n" |
175 | "Pass: inline\n" |
176 | "Name: NoDefinition\n" |
177 | "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" |
178 | "Function: foo\n" |
179 | "...\n" , |
180 | OutputFormat: remarks::Format::Bitstream, |
181 | ExpectedOutput: "<BLOCKINFO_BLOCK/>\n" |
182 | "<Meta BlockID=8 NumWords=12 BlockCodeSize=3>\n" |
183 | " <Container info codeid=1 abbrevid=4 op0=0 op1=2/>\n" |
184 | " <Remark version codeid=2 abbrevid=5 op0=0/>\n" |
185 | " <String table codeid=3 abbrevid=6/> blob data = " |
186 | "'inline\\x00NoDefinition\\x00foo\\x00file.c\\x00'\n" |
187 | "</Meta>\n" |
188 | "<Remark BlockID=9 NumWords=4 BlockCodeSize=4>\n" |
189 | " <Remark header codeid=5 abbrevid=4 op0=2 op1=1 op2=0 op3=2/>\n" |
190 | " <Remark debug location codeid=6 abbrevid=5 op0=3 op1=3 op2=12/>\n" |
191 | "</Remark>\n" ); |
192 | } |
193 | |
194 | TEST(Remarks, LinkingGoodStrTab) { |
195 | // Check that remarks from different entries use the same strtab. |
196 | check(InputFormat: remarks::Format::YAML, |
197 | Input: "--- !Missed\n" |
198 | "Pass: inline\n" |
199 | "Name: NoDefinition\n" |
200 | "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" |
201 | "Function: foo\n" |
202 | "...\n" , |
203 | InputFormat2: remarks::Format::YAML, |
204 | Input2: "--- !Passed\n" |
205 | "Pass: inline\n" |
206 | "Name: Ok\n" |
207 | "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" |
208 | "Function: foo\n" |
209 | "...\n" , |
210 | OutputFormat: remarks::Format::YAMLStrTab, |
211 | ExpectedOutput: StringRef("REMARKS\0\0\0\0\0\0\0\0\0\x22\0\0\0\0\0\0\0" |
212 | "inline\0NoDefinition\0foo\0file.c\0Ok\0" |
213 | "--- !Passed\n" |
214 | "Pass: 0\n" |
215 | "Name: 4\n" |
216 | "DebugLoc: { File: 3, Line: 3, Column: 12 }\n" |
217 | "Function: 2\n" |
218 | "...\n" |
219 | "--- !Missed\n" |
220 | "Pass: 0\n" |
221 | "Name: 1\n" |
222 | "DebugLoc: { File: 3, Line: 3, Column: 12 }\n" |
223 | "Function: 2\n" |
224 | "...\n" , |
225 | 304)); |
226 | } |
227 | |
228 | // Check that we propagate parsing errors. |
229 | TEST(Remarks, LinkingError) { |
230 | remarks::RemarkLinker RL; |
231 | { |
232 | Error E = RL.link(Buffer: "badyaml" , RemarkFormat: remarks::Format::YAML); |
233 | EXPECT_TRUE(static_cast<bool>(E)); |
234 | EXPECT_EQ(toString(std::move(E)), |
235 | "YAML:1:1: error: document root is not of mapping type.\n" |
236 | "\n" |
237 | "badyaml\n" |
238 | "^~~~~~~\n" |
239 | "\n" ); |
240 | } |
241 | |
242 | { |
243 | // Check that the prepend path is propagated and fails with the full path. |
244 | RL.setExternalFilePrependPath("/baddir/" ); |
245 | Error E = RL.link( |
246 | Buffer: StringRef("REMARKS\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0badfile.opt.yaml" , |
247 | 40), |
248 | RemarkFormat: remarks::Format::YAMLStrTab); |
249 | EXPECT_TRUE(static_cast<bool>(E)); |
250 | std::string ErrorMessage = toString(E: std::move(E)); |
251 | EXPECT_EQ(StringRef(ErrorMessage).lower(), |
252 | StringRef("'/baddir/badfile.opt.yaml': No such file or directory" ) |
253 | .lower()); |
254 | } |
255 | } |
256 | |