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 =
222 dyn_cast_if_present<yaml::SequenceNode>(Val: RemarkField.getValue());
223 if (!Args)
224 return error(Message: "wrong value type for key.", Node&: RemarkField);
225
226 for (yaml::Node &Arg : *Args) {
227 if (Expected<Argument> MaybeArg = parseArg(Node&: Arg))
228 TheRemark.Args.push_back(Elt: *MaybeArg);
229 else
230 return MaybeArg.takeError();
231 }
232 } else {
233 return error(Message: "unknown key.", Node&: RemarkField);
234 }
235 }
236
237 // Check if any of the mandatory fields are missing.
238 if (TheRemark.RemarkType == Type::Unknown || TheRemark.PassName.empty() ||
239 TheRemark.RemarkName.empty() || TheRemark.FunctionName.empty())
240 return error(Message: "Type, Pass, Name or Function missing.",
241 Node&: *RemarkEntry.getRoot());
242
243 return std::move(Result);
244}
245
246Expected<Type> YAMLRemarkParser::parseType(yaml::MappingNode &Node) {
247 auto Type = StringSwitch<remarks::Type>(Node.getRawTag())
248 .Case(S: "!Passed", Value: remarks::Type::Passed)
249 .Case(S: "!Missed", Value: remarks::Type::Missed)
250 .Case(S: "!Analysis", Value: remarks::Type::Analysis)
251 .Case(S: "!AnalysisFPCommute", Value: remarks::Type::AnalysisFPCommute)
252 .Case(S: "!AnalysisAliasing", Value: remarks::Type::AnalysisAliasing)
253 .Case(S: "!Failure", Value: remarks::Type::Failure)
254 .Default(Value: remarks::Type::Unknown);
255 if (Type == remarks::Type::Unknown)
256 return error(Message: "expected a remark tag.", Node);
257 return Type;
258}
259
260Expected<StringRef> YAMLRemarkParser::parseKey(yaml::KeyValueNode &Node) {
261 if (auto *Key = dyn_cast_if_present<yaml::ScalarNode>(Val: Node.getKey()))
262 return Key->getRawValue();
263
264 return error(Message: "key is not a string.", Node);
265}
266
267Expected<StringRef> YAMLRemarkParser::parseStr(yaml::KeyValueNode &Node) {
268 auto *Value = dyn_cast_if_present<yaml::ScalarNode>(Val: Node.getValue());
269 yaml::BlockScalarNode *ValueBlock;
270 StringRef Result;
271 if (!Value) {
272 // Try to parse the value as a block node.
273 ValueBlock = dyn_cast_if_present<yaml::BlockScalarNode>(Val: Node.getValue());
274 if (!ValueBlock)
275 return error(Message: "expected a value of scalar type.", Node);
276 // The block value lives in the YAML document, which next() frees before
277 // returning the remark.
278 Result = ValueBlock->getValue().copy(A&: Alloc);
279 } else
280 Result = Value->getRawValue();
281
282 Result.consume_front(Prefix: "\'");
283 Result.consume_back(Suffix: "\'");
284
285 return Result;
286}
287
288Expected<unsigned> YAMLRemarkParser::parseUnsigned(yaml::KeyValueNode &Node) {
289 SmallVector<char, 4> Tmp;
290 auto *Value = dyn_cast_if_present<yaml::ScalarNode>(Val: Node.getValue());
291 if (!Value)
292 return error(Message: "expected a value of scalar type.", Node);
293 unsigned UnsignedValue = 0;
294 if (Value->getValue(Storage&: Tmp).getAsInteger(Radix: 10, Result&: UnsignedValue))
295 return error(Message: "expected a value of integer type.", Node&: *Value);
296 return UnsignedValue;
297}
298
299Expected<RemarkLocation>
300YAMLRemarkParser::parseDebugLoc(yaml::KeyValueNode &Node) {
301 auto *DebugLoc = dyn_cast_if_present<yaml::MappingNode>(Val: Node.getValue());
302 if (!DebugLoc)
303 return error(Message: "expected a value of mapping type.", Node);
304
305 std::optional<StringRef> File;
306 std::optional<unsigned> Line;
307 std::optional<unsigned> Column;
308
309 for (yaml::KeyValueNode &DLNode : *DebugLoc) {
310 Expected<StringRef> MaybeKey = parseKey(Node&: DLNode);
311 if (!MaybeKey)
312 return MaybeKey.takeError();
313 StringRef KeyName = *MaybeKey;
314
315 if (KeyName == "File") {
316 if (Expected<StringRef> MaybeStr = parseStr(Node&: DLNode))
317 File = *MaybeStr;
318 else
319 return MaybeStr.takeError();
320 } else if (KeyName == "Column") {
321 if (Expected<unsigned> MaybeU = parseUnsigned(Node&: DLNode))
322 Column = *MaybeU;
323 else
324 return MaybeU.takeError();
325 } else if (KeyName == "Line") {
326 if (Expected<unsigned> MaybeU = parseUnsigned(Node&: DLNode))
327 Line = *MaybeU;
328 else
329 return MaybeU.takeError();
330 } else {
331 return error(Message: "unknown entry in DebugLoc map.", Node&: DLNode);
332 }
333 }
334
335 // If any of the debug loc fields is missing, return an error.
336 if (!File || !Line || !Column)
337 return error(Message: "DebugLoc node incomplete.", Node);
338
339 return RemarkLocation{.SourceFilePath: *File, .SourceLine: *Line, .SourceColumn: *Column};
340}
341
342Expected<Argument> YAMLRemarkParser::parseArg(yaml::Node &Node) {
343 auto *ArgMap = dyn_cast<yaml::MappingNode>(Val: &Node);
344 if (!ArgMap)
345 return error(Message: "expected a value of mapping type.", Node);
346
347 std::optional<StringRef> KeyStr;
348 std::optional<StringRef> ValueStr;
349 std::optional<RemarkLocation> Loc;
350
351 for (yaml::KeyValueNode &ArgEntry : *ArgMap) {
352 Expected<StringRef> MaybeKey = parseKey(Node&: ArgEntry);
353 if (!MaybeKey)
354 return MaybeKey.takeError();
355 StringRef KeyName = *MaybeKey;
356
357 // Try to parse debug locs.
358 if (KeyName == "DebugLoc") {
359 // Can't have multiple DebugLoc entries per argument.
360 if (Loc)
361 return error(Message: "only one DebugLoc entry is allowed per argument.",
362 Node&: ArgEntry);
363
364 if (Expected<RemarkLocation> MaybeLoc = parseDebugLoc(Node&: ArgEntry)) {
365 Loc = *MaybeLoc;
366 continue;
367 } else
368 return MaybeLoc.takeError();
369 }
370
371 // If we already have a string, error out.
372 if (ValueStr)
373 return error(Message: "only one string entry is allowed per argument.", Node&: ArgEntry);
374
375 // Try to parse the value.
376 if (Expected<StringRef> MaybeStr = parseStr(Node&: ArgEntry))
377 ValueStr = *MaybeStr;
378 else
379 return MaybeStr.takeError();
380
381 // Keep the key from the string.
382 KeyStr = KeyName;
383 }
384
385 if (!KeyStr)
386 return error(Message: "argument key is missing.", Node&: *ArgMap);
387 if (!ValueStr)
388 return error(Message: "argument value is missing.", Node&: *ArgMap);
389
390 Argument Arg;
391 Arg.Key = *KeyStr;
392 Arg.Val = *ValueStr;
393 Arg.Loc = Loc;
394 return Arg;
395}
396
397Expected<std::unique_ptr<Remark>> YAMLRemarkParser::next() {
398 if (YAMLIt == Stream.end())
399 return make_error<EndOfFileError>();
400
401 Expected<std::unique_ptr<Remark>> MaybeResult = parseRemark(RemarkEntry&: *YAMLIt);
402 if (!MaybeResult) {
403 // Avoid garbage input, set the iterator to the end.
404 YAMLIt = Stream.end();
405 return MaybeResult.takeError();
406 }
407
408 ++YAMLIt;
409
410 return std::move(*MaybeResult);
411}
412