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