410 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8//9// This file provides utility methods used by clients that want to use the10// 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, OS, /*ShowColors*/ false,32 /*ShowKindLabels*/ 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 to40 // 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 will43 // call the diagnostic handler.44 // 4) The diagnostic handler will stream the error directly into this object's45 // Message member, which is used when logging is asked for.46 auto OldDiagHandler = SM.getDiagHandler();47 auto OldDiagCtx = SM.getDiagContext();48 SM.setDiagHandler(handleDiagnostic, &Message);49 Stream.printError(&Node, Twine(Msg) + Twine('\n'));50 // Restore the old handlers.51 SM.setDiagHandler(OldDiagHandler, OldDiagCtx);52}53 54static SourceMgr setupSM(std::string &LastErrorMessage) {55 SourceMgr SM;56 SM.setDiagHandler(handleDiagnostic, &LastErrorMessage);57 return SM;58}59 60// Parse the magic number. This function returns true if this represents remark61// metadata, false otherwise.62static Expected<bool> parseMagic(StringRef &Buf) {63 if (!Buf.consume_front(remarks::Magic))64 return false;65 66 if (Buf.size() < 1 || !Buf.consume_front(StringRef("\0", 1)))67 return createStringError(std::errc::illegal_byte_sequence,68 "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(std::errc::illegal_byte_sequence,75 "Expecting version number.");76 77 uint64_t Version =78 support::endian::read<uint64_t, llvm::endianness::little>(Buf.data());79 if (Version != remarks::CurrentRemarkVersion)80 return createStringError(std::errc::illegal_byte_sequence,81 "Mismatching remark version. Got %" PRId6482 ", expected %" PRId64 ".",83 Version, remarks::CurrentRemarkVersion);84 Buf = Buf.drop_front(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(std::errc::illegal_byte_sequence,91 "Expecting string table size.");92 uint64_t StrTabSize =93 support::endian::read<uint64_t, llvm::endianness::little>(Buf.data());94 Buf = Buf.drop_front(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(std::errc::illegal_byte_sequence,117 "String table unsupported for YAML format.");118 }119 // If it starts with "---", there is no external file.120 if (!Buf.starts_with("---")) {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(FullPath, ExternalFilePath);127 128 // Try to open the file and start parsing from there.129 ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =130 MemoryBuffer::getFile(FullPath);131 if (std::error_code EC = BufferOrErr.getError())132 return createFileError(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>(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>(Message, SM, Stream, Node);153}154 155Error YAMLRemarkParser::error() {156 if (LastErrorMessage.empty())157 return Error::success();158 Error E = make_error<YAMLParseError>(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(std::make_error_code(std::errc::invalid_argument),171 "not a valid YAML file.");172 }173 174 auto *Root = dyn_cast<yaml::MappingNode>(YAMLRoot);175 if (!Root)176 return error("document root is not of mapping type.", *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 the182 // key-value stream.183 Expected<Type> T = parseType(*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(RemarkField);192 if (!MaybeKey)193 return MaybeKey.takeError();194 StringRef KeyName = *MaybeKey;195 196 if (KeyName == "Pass") {197 if (Expected<StringRef> MaybeStr = parseStr(RemarkField))198 TheRemark.PassName = *MaybeStr;199 else200 return MaybeStr.takeError();201 } else if (KeyName == "Name") {202 if (Expected<StringRef> MaybeStr = parseStr(RemarkField))203 TheRemark.RemarkName = *MaybeStr;204 else205 return MaybeStr.takeError();206 } else if (KeyName == "Function") {207 if (Expected<StringRef> MaybeStr = parseStr(RemarkField))208 TheRemark.FunctionName = *MaybeStr;209 else210 return MaybeStr.takeError();211 } else if (KeyName == "Hotness") {212 if (Expected<unsigned> MaybeU = parseUnsigned(RemarkField))213 TheRemark.Hotness = *MaybeU;214 else215 return MaybeU.takeError();216 } else if (KeyName == "DebugLoc") {217 if (Expected<RemarkLocation> MaybeLoc = parseDebugLoc(RemarkField))218 TheRemark.Loc = *MaybeLoc;219 else220 return MaybeLoc.takeError();221 } else if (KeyName == "Args") {222 auto *Args = dyn_cast<yaml::SequenceNode>(RemarkField.getValue());223 if (!Args)224 return error("wrong value type for key.", RemarkField);225 226 for (yaml::Node &Arg : *Args) {227 if (Expected<Argument> MaybeArg = parseArg(Arg))228 TheRemark.Args.push_back(*MaybeArg);229 else230 return MaybeArg.takeError();231 }232 } else {233 return error("unknown key.", 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("Type, Pass, Name or Function missing.",241 *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("!Passed", remarks::Type::Passed)249 .Case("!Missed", remarks::Type::Missed)250 .Case("!Analysis", remarks::Type::Analysis)251 .Case("!AnalysisFPCommute", remarks::Type::AnalysisFPCommute)252 .Case("!AnalysisAliasing", remarks::Type::AnalysisAliasing)253 .Case("!Failure", remarks::Type::Failure)254 .Default(remarks::Type::Unknown);255 if (Type == remarks::Type::Unknown)256 return error("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>(Node.getKey()))262 return Key->getRawValue();263 264 return error("key is not a string.", Node);265}266 267Expected<StringRef> YAMLRemarkParser::parseStr(yaml::KeyValueNode &Node) {268 auto *Value = dyn_cast<yaml::ScalarNode>(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>(Node.getValue());274 if (!ValueBlock)275 return error("expected a value of scalar type.", Node);276 Result = ValueBlock->getValue();277 } else278 Result = Value->getRawValue();279 280 Result.consume_front("\'");281 Result.consume_back("\'");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>(Node.getValue());289 if (!Value)290 return error("expected a value of scalar type.", Node);291 unsigned UnsignedValue = 0;292 if (Value->getValue(Tmp).getAsInteger(10, UnsignedValue))293 return error("expected a value of integer type.", *Value);294 return UnsignedValue;295}296 297Expected<RemarkLocation>298YAMLRemarkParser::parseDebugLoc(yaml::KeyValueNode &Node) {299 auto *DebugLoc = dyn_cast<yaml::MappingNode>(Node.getValue());300 if (!DebugLoc)301 return error("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(DLNode);309 if (!MaybeKey)310 return MaybeKey.takeError();311 StringRef KeyName = *MaybeKey;312 313 if (KeyName == "File") {314 if (Expected<StringRef> MaybeStr = parseStr(DLNode))315 File = *MaybeStr;316 else317 return MaybeStr.takeError();318 } else if (KeyName == "Column") {319 if (Expected<unsigned> MaybeU = parseUnsigned(DLNode))320 Column = *MaybeU;321 else322 return MaybeU.takeError();323 } else if (KeyName == "Line") {324 if (Expected<unsigned> MaybeU = parseUnsigned(DLNode))325 Line = *MaybeU;326 else327 return MaybeU.takeError();328 } else {329 return error("unknown entry in DebugLoc map.", 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("DebugLoc node incomplete.", Node);336 337 return RemarkLocation{*File, *Line, *Column};338}339 340Expected<Argument> YAMLRemarkParser::parseArg(yaml::Node &Node) {341 auto *ArgMap = dyn_cast<yaml::MappingNode>(&Node);342 if (!ArgMap)343 return error("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(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("only one DebugLoc entry is allowed per argument.",360 ArgEntry);361 362 if (Expected<RemarkLocation> MaybeLoc = parseDebugLoc(ArgEntry)) {363 Loc = *MaybeLoc;364 continue;365 } else366 return MaybeLoc.takeError();367 }368 369 // If we already have a string, error out.370 if (ValueStr)371 return error("only one string entry is allowed per argument.", ArgEntry);372 373 // Try to parse the value.374 if (Expected<StringRef> MaybeStr = parseStr(ArgEntry))375 ValueStr = *MaybeStr;376 else377 return MaybeStr.takeError();378 379 // Keep the key from the string.380 KeyStr = KeyName;381 }382 383 if (!KeyStr)384 return error("argument key is missing.", *ArgMap);385 if (!ValueStr)386 return error("argument value is missing.", *ArgMap);387 388 Argument Arg;389 Arg.Key = *KeyStr;390 Arg.Val = *ValueStr;391 Arg.Loc = Loc;392 return Arg;393}394 395Expected<std::unique_ptr<Remark>> YAMLRemarkParser::next() {396 if (YAMLIt == Stream.end())397 return make_error<EndOfFileError>();398 399 Expected<std::unique_ptr<Remark>> MaybeResult = parseRemark(*YAMLIt);400 if (!MaybeResult) {401 // Avoid garbage input, set the iterator to the end.402 YAMLIt = Stream.end();403 return MaybeResult.takeError();404 }405 406 ++YAMLIt;407 408 return std::move(*MaybeResult);409}410