1//===- YAMLRemarkParser.cpp -----------------------------------------------===//
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// This file provides utility methods used by clients that want to use the
10// parser for remark diagnostics in LLVM.
11//
12//===----------------------------------------------------------------------===//
13
14#include "YAMLRemarkParser.h"
15#include "llvm/ADT/SmallString.h"
16#include "llvm/ADT/StringSwitch.h"
17#include "llvm/Support/Endian.h"
18#include "llvm/Support/Path.h"
19#include <optional>
20
21using namespace llvm;
22using namespace llvm::remarks;
23
24char YAMLParseError::ID = 0;
25
26static void handleDiagnostic(const SMDiagnostic &Diag, void *Ctx) {
27 assert(Ctx && "Expected non-null Ctx in diagnostic handler.");
28 std::string &Message = *static_cast<std::string *>(Ctx);
29 assert(Message.empty() && "Expected an empty string.");
30 raw_string_ostream OS(Message);
31 Diag.print(/*ProgName=*/nullptr, S&: OS, /*ShowColors*/ false,
32 /*ShowKindLabels*/ ShowKindLabel: true);
33 OS << '\n';
34}
35
36YAMLParseError::YAMLParseError(StringRef Msg, SourceMgr &SM,
37 yaml::Stream &Stream, yaml::Node &Node) {
38 // 1) Set up a diagnostic handler to avoid errors being printed out to
39 // stderr.
40 // 2) Use the stream to print the error with the associated node.
41 // 3) The stream will use the source manager to print the error, which will
42 // call the diagnostic handler.
43 // 4) The diagnostic handler will stream the error directly into this object's
44 // Message member, which is used when logging is asked for.
45 auto OldDiagHandler = SM.getDiagHandler();
46 auto OldDiagCtx = SM.getDiagContext();
47 SM.setDiagHandler(DH: handleDiagnostic, Ctx: &Message);
48 Stream.printError(N: &Node, Msg: Twine(Msg) + Twine('\n'));
49 // Restore the old handlers.
50 SM.setDiagHandler(DH: OldDiagHandler, Ctx: OldDiagCtx);
51}
52
53static SourceMgr setupSM(std::string &LastErrorMessage) {
54 SourceMgr SM;
55 SM.setDiagHandler(DH: handleDiagnostic, Ctx: &LastErrorMessage);
56 return SM;
57}
58
59// Parse the magic number. This function returns true if this represents remark
60// metadata, false otherwise.
61static Expected<bool> parseMagic(StringRef &Buf) {
62 if (!Buf.consume_front(Prefix: remarks::Magic))
63 return false;
64
65 if (Buf.size() < 1 || !Buf.consume_front(Prefix: StringRef("\0", 1)))
66 return createStringError(EC: std::errc::illegal_byte_sequence,
67 Fmt: "Expecting \\0 after magic number.");
68 return true;
69}
70
71static Expected<uint64_t> parseVersion(StringRef &Buf) {
72 if (Buf.size() < sizeof(uint64_t))
73 return createStringError(EC: std::errc::illegal_byte_sequence,
74 Fmt: "Expecting version number.");
75
76 uint64_t Version =
77 support::endian::read<uint64_t, llvm::endianness::little>(P: Buf.data());
78 if (Version != remarks::CurrentRemarkVersion)
79 return createStringError(EC: std::errc::illegal_byte_sequence,
80 Fmt: "Mismatching remark version. Got %" PRId64
81 ", expected %" PRId64 ".",
82 Vals: Version, Vals: remarks::CurrentRemarkVersion);
83 Buf = Buf.drop_front(N: sizeof(uint64_t));
84 return Version;
85}
86
87static Expected<uint64_t> parseStrTabSize(StringRef &Buf) {
88 if (Buf.size() < sizeof(uint64_t))
89 return createStringError(EC: std::errc::illegal_byte_sequence,
90 Fmt: "Expecting string table size.");
91 uint64_t StrTabSize =
92 support::endian::read<uint64_t, llvm::endianness::little>(P: Buf.data());
93 Buf = Buf.drop_front(N: sizeof(uint64_t));
94 return StrTabSize;
95}
96
97Expected<std::unique_ptr<YAMLRemarkParser>> remarks::createYAMLParserFromMeta(
98 StringRef Buf, std::optional<StringRef> ExternalFilePrependPath) {
99 // We now have a magic number. The metadata has to be correct.
100 Expected<bool> isMeta = parseMagic(Buf);
101 if (!isMeta)
102 return isMeta.takeError();
103 // If it's not recognized as metadata, roll back.
104 std::unique_ptr<MemoryBuffer> SeparateBuf;
105 if (*isMeta) {
106 Expected<uint64_t> Version = parseVersion(Buf);
107 if (!Version)
108 return Version.takeError();
109
110 Expected<uint64_t> StrTabSize = parseStrTabSize(Buf);
111 if (!StrTabSize)
112 return StrTabSize.takeError();
113
114 if (*StrTabSize != 0) {
115 return createStringError(EC: std::errc::illegal_byte_sequence,
116 Fmt: "String table unsupported for YAML format.");
117 }
118 // If it starts with "---", there is no external file.
119 if (!Buf.starts_with(Prefix: "---")) {
120 // At this point, we expect Buf to contain the external file path.
121 StringRef ExternalFilePath = Buf;
122 SmallString<80> FullPath;
123 if (ExternalFilePrependPath)
124 FullPath = *ExternalFilePrependPath;
125 sys::path::append(path&: FullPath, a: ExternalFilePath);
126
127 // Try to open the file and start parsing from there.
128 ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
129 MemoryBuffer::getFile(Filename: FullPath);
130 if (std::error_code EC = BufferOrErr.getError())
131 return createFileError(F: FullPath, EC);
132
133 // Keep the buffer alive.
134 SeparateBuf = std::move(*BufferOrErr);
135 Buf = SeparateBuf->getBuffer();
136 }
137 }
138
139 std::unique_ptr<YAMLRemarkParser> Result =
140 std::make_unique<YAMLRemarkParser>(args&: Buf);
141 if (SeparateBuf)
142 Result->SeparateBuf = std::move(SeparateBuf);
143 return std::move(Result);
144}
145
146YAMLRemarkParser::YAMLRemarkParser(StringRef Buf)
147 : RemarkParser{Format::YAML}, SM(setupSM(LastErrorMessage)),
148 Stream(Buf, SM), YAMLIt(Stream.begin()) {}
149
150Error YAMLRemarkParser::error(StringRef Message, yaml::Node &Node) {
151 return make_error<YAMLParseError>(Args&: Message, Args&: SM, Args&: Stream, Args&: Node);
152}
153
154Error YAMLRemarkParser::error() {
155 if (LastErrorMessage.empty())
156 return Error::success();
157 Error E = make_error<YAMLParseError>(Args&: LastErrorMessage);
158 LastErrorMessage.clear();
159 return E;
160}
161
162Expected<std::unique_ptr<Remark>>
163YAMLRemarkParser::parseRemark(yaml::Document &RemarkEntry) {
164 if (Error E = error())
165 return std::move(E);
166
167 yaml::Node *YAMLRoot = RemarkEntry.getRoot();
168 if (!YAMLRoot) {
169 return createStringError(EC: std::make_error_code(e: std::errc::invalid_argument),
170 S: "not a valid YAML file.");
171 }
172
173 auto *Root = dyn_cast<yaml::MappingNode>(Val: YAMLRoot);
174 if (!Root)
175 return error(Message: "document root is not of mapping type.", Node&: *YAMLRoot);
176
177 std::unique_ptr<Remark> Result = std::make_unique<Remark>();
178 Remark &TheRemark = *Result;
179
180 // First, the type. It needs special handling since is not part of the
181 // key-value stream.
182 Expected<Type> T = parseType(Node&: *Root);
183 if (!T)
184 return T.takeError();
185
186 TheRemark.RemarkType = *T;
187
188 // Then, parse the fields, one by one.
189 for (yaml::KeyValueNode &RemarkField : *Root) {
190 Expected<StringRef> MaybeKey = parseKey(Node&: RemarkField);
191 if (!MaybeKey)
192 return MaybeKey.takeError();
193 StringRef KeyName = *MaybeKey;
194
195 if (KeyName == "Pass") {
196 if (Expected<StringRef> MaybeStr = parseStr(Node&: RemarkField))
197 TheRemark.PassName = *MaybeStr;
198 else
199 return MaybeStr.takeError();
200 } else if (KeyName == "Name") {
201 if (Expected<StringRef> MaybeStr = parseStr(Node&: RemarkField))
202 TheRemark.RemarkName = *MaybeStr;
203 else
204 return MaybeStr.takeError();
205 } else if (KeyName == "Function") {
206 if (Expected<StringRef> MaybeStr = parseStr(Node&: RemarkField))
207 TheRemark.FunctionName = *MaybeStr;
208 else
209 return MaybeStr.takeError();
210 } else if (KeyName == "Hotness") {
211 if (Expected<unsigned> MaybeU = parseUnsigned(Node&: RemarkField))
212 TheRemark.Hotness = *MaybeU;
213 else
214 return MaybeU.takeError();
215 } else if (KeyName == "DebugLoc") {
216 if (Expected<RemarkLocation> MaybeLoc = parseDebugLoc(Node&: RemarkField))
217 TheRemark.Loc = *MaybeLoc;
218 else
219 return MaybeLoc.takeError();
220 } else if (KeyName == "Args") {
221 auto *Args = dyn_cast<yaml::SequenceNode>(Val: RemarkField.getValue());
222 if (!Args)
223 return error(Message: "wrong value type for key.", Node&: RemarkField);
224
225 for (yaml::Node &Arg : *Args) {
226 if (Expected<Argument> MaybeArg = parseArg(Node&: Arg))
227 TheRemark.Args.push_back(Elt: *MaybeArg);
228 else
229 return MaybeArg.takeError();
230 }
231 } else {
232 return error(Message: "unknown key.", Node&: RemarkField);
233 }
234 }
235
236 // Check if any of the mandatory fields are missing.
237 if (TheRemark.RemarkType == Type::Unknown || TheRemark.PassName.empty() ||
238 TheRemark.RemarkName.empty() || TheRemark.FunctionName.empty())
239 return error(Message: "Type, Pass, Name or Function missing.",
240 Node&: *RemarkEntry.getRoot());
241
242 return std::move(Result);
243}
244
245Expected<Type> YAMLRemarkParser::parseType(yaml::MappingNode &Node) {
246 auto Type = StringSwitch<remarks::Type>(Node.getRawTag())
247 .Case(S: "!Passed", Value: remarks::Type::Passed)
248 .Case(S: "!Missed", Value: remarks::Type::Missed)
249 .Case(S: "!Analysis", Value: remarks::Type::Analysis)
250 .Case(S: "!AnalysisFPCommute", Value: remarks::Type::AnalysisFPCommute)
251 .Case(S: "!AnalysisAliasing", Value: remarks::Type::AnalysisAliasing)
252 .Case(S: "!Failure", Value: remarks::Type::Failure)
253 .Default(Value: remarks::Type::Unknown);
254 if (Type == remarks::Type::Unknown)
255 return error(Message: "expected a remark tag.", Node);
256 return Type;
257}
258
259Expected<StringRef> YAMLRemarkParser::parseKey(yaml::KeyValueNode &Node) {
260 if (auto *Key = dyn_cast<yaml::ScalarNode>(Val: Node.getKey()))
261 return Key->getRawValue();
262
263 return error(Message: "key is not a string.", Node);
264}
265
266Expected<StringRef> YAMLRemarkParser::parseStr(yaml::KeyValueNode &Node) {
267 auto *Value = dyn_cast<yaml::ScalarNode>(Val: Node.getValue());
268 yaml::BlockScalarNode *ValueBlock;
269 StringRef Result;
270 if (!Value) {
271 // Try to parse the value as a block node.
272 ValueBlock = dyn_cast<yaml::BlockScalarNode>(Val: Node.getValue());
273 if (!ValueBlock)
274 return error(Message: "expected a value of scalar type.", Node);
275 Result = ValueBlock->getValue();
276 } else
277 Result = Value->getRawValue();
278
279 Result.consume_front(Prefix: "\'");
280 Result.consume_back(Suffix: "\'");
281
282 return Result;
283}
284
285Expected<unsigned> YAMLRemarkParser::parseUnsigned(yaml::KeyValueNode &Node) {
286 SmallVector<char, 4> Tmp;
287 auto *Value = dyn_cast<yaml::ScalarNode>(Val: Node.getValue());
288 if (!Value)
289 return error(Message: "expected a value of scalar type.", Node);
290 unsigned UnsignedValue = 0;
291 if (Value->getValue(Storage&: Tmp).getAsInteger(Radix: 10, Result&: UnsignedValue))
292 return error(Message: "expected a value of integer type.", Node&: *Value);
293 return UnsignedValue;
294}
295
296Expected<RemarkLocation>
297YAMLRemarkParser::parseDebugLoc(yaml::KeyValueNode &Node) {
298 auto *DebugLoc = dyn_cast<yaml::MappingNode>(Val: Node.getValue());
299 if (!DebugLoc)
300 return error(Message: "expected a value of mapping type.", Node);
301
302 std::optional<StringRef> File;
303 std::optional<unsigned> Line;
304 std::optional<unsigned> Column;
305
306 for (yaml::KeyValueNode &DLNode : *DebugLoc) {
307 Expected<StringRef> MaybeKey = parseKey(Node&: DLNode);
308 if (!MaybeKey)
309 return MaybeKey.takeError();
310 StringRef KeyName = *MaybeKey;
311
312 if (KeyName == "File") {
313 if (Expected<StringRef> MaybeStr = parseStr(Node&: DLNode))
314 File = *MaybeStr;
315 else
316 return MaybeStr.takeError();
317 } else if (KeyName == "Column") {
318 if (Expected<unsigned> MaybeU = parseUnsigned(Node&: DLNode))
319 Column = *MaybeU;
320 else
321 return MaybeU.takeError();
322 } else if (KeyName == "Line") {
323 if (Expected<unsigned> MaybeU = parseUnsigned(Node&: DLNode))
324 Line = *MaybeU;
325 else
326 return MaybeU.takeError();
327 } else {
328 return error(Message: "unknown entry in DebugLoc map.", Node&: DLNode);
329 }
330 }
331
332 // If any of the debug loc fields is missing, return an error.
333 if (!File || !Line || !Column)
334 return error(Message: "DebugLoc node incomplete.", Node);
335
336 return RemarkLocation{.SourceFilePath: *File, .SourceLine: *Line, .SourceColumn: *Column};
337}
338
339Expected<Argument> YAMLRemarkParser::parseArg(yaml::Node &Node) {
340 auto *ArgMap = dyn_cast<yaml::MappingNode>(Val: &Node);
341 if (!ArgMap)
342 return error(Message: "expected a value of mapping type.", Node);
343
344 std::optional<StringRef> KeyStr;
345 std::optional<StringRef> ValueStr;
346 std::optional<RemarkLocation> Loc;
347
348 for (yaml::KeyValueNode &ArgEntry : *ArgMap) {
349 Expected<StringRef> MaybeKey = parseKey(Node&: ArgEntry);
350 if (!MaybeKey)
351 return MaybeKey.takeError();
352 StringRef KeyName = *MaybeKey;
353
354 // Try to parse debug locs.
355 if (KeyName == "DebugLoc") {
356 // Can't have multiple DebugLoc entries per argument.
357 if (Loc)
358 return error(Message: "only one DebugLoc entry is allowed per argument.",
359 Node&: ArgEntry);
360
361 if (Expected<RemarkLocation> MaybeLoc = parseDebugLoc(Node&: ArgEntry)) {
362 Loc = *MaybeLoc;
363 continue;
364 } else
365 return MaybeLoc.takeError();
366 }
367
368 // If we already have a string, error out.
369 if (ValueStr)
370 return error(Message: "only one string entry is allowed per argument.", Node&: ArgEntry);
371
372 // Try to parse the value.
373 if (Expected<StringRef> MaybeStr = parseStr(Node&: ArgEntry))
374 ValueStr = *MaybeStr;
375 else
376 return MaybeStr.takeError();
377
378 // Keep the key from the string.
379 KeyStr = KeyName;
380 }
381
382 if (!KeyStr)
383 return error(Message: "argument key is missing.", Node&: *ArgMap);
384 if (!ValueStr)
385 return error(Message: "argument value is missing.", Node&: *ArgMap);
386
387 Argument Arg;
388 Arg.Key = *KeyStr;
389 Arg.Val = *ValueStr;
390 Arg.Loc = Loc;
391 return Arg;
392}
393
394Expected<std::unique_ptr<Remark>> YAMLRemarkParser::next() {
395 if (YAMLIt == Stream.end())
396 return make_error<EndOfFileError>();
397
398 Expected<std::unique_ptr<Remark>> MaybeResult = parseRemark(RemarkEntry&: *YAMLIt);
399 if (!MaybeResult) {
400 // Avoid garbage input, set the iterator to the end.
401 YAMLIt = Stream.end();
402 return MaybeResult.takeError();
403 }
404
405 ++YAMLIt;
406
407 return std::move(*MaybeResult);
408}
409