//===- YAMLRemarkParser.cpp -----------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This file provides utility methods used by clients that want to use the // parser for remark diagnostics in LLVM. // //===----------------------------------------------------------------------===// #include "YAMLRemarkParser.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Remarks/RemarkParser.h" #include "llvm/Support/Endian.h" using namespace llvm; using namespace llvm::remarks; char YAMLParseError::ID = 0; static void handleDiagnostic(const SMDiagnostic &Diag, void *Ctx) { assert(Ctx && "Expected non-null Ctx in diagnostic handler."); std::string &Message = *static_cast(Ctx); assert(Message.empty() && "Expected an empty string."); raw_string_ostream OS(Message); Diag.print(/*ProgName=*/nullptr, OS, /*ShowColors*/ false, /*ShowKindLabels*/ true); OS << '\n'; OS.flush(); } YAMLParseError::YAMLParseError(StringRef Msg, SourceMgr &SM, yaml::Stream &Stream, yaml::Node &Node) { // 1) Set up a diagnostic handler to avoid errors being printed out to // stderr. // 2) Use the stream to print the error with the associated node. // 3) The stream will use the source manager to print the error, which will // call the diagnostic handler. // 4) The diagnostic handler will stream the error directly into this object's // Message member, which is used when logging is asked for. auto OldDiagHandler = SM.getDiagHandler(); auto OldDiagCtx = SM.getDiagContext(); SM.setDiagHandler(handleDiagnostic, &Message); Stream.printError(&Node, Twine(Msg) + Twine('\n')); // Restore the old handlers. SM.setDiagHandler(OldDiagHandler, OldDiagCtx); } static SourceMgr setupSM(std::string &LastErrorMessage) { SourceMgr SM; SM.setDiagHandler(handleDiagnostic, &LastErrorMessage); return SM; } // Parse the magic number. This function returns true if this represents remark // metadata, false otherwise. static Expected parseMagic(StringRef &Buf) { if (!Buf.consume_front(remarks::Magic)) return false; if (Buf.size() < 1 || !Buf.consume_front(StringRef("\0", 1))) return createStringError(std::errc::illegal_byte_sequence, "Expecting \\0 after magic number."); return true; } static Expected parseVersion(StringRef &Buf) { if (Buf.size() < sizeof(uint64_t)) return createStringError(std::errc::illegal_byte_sequence, "Expecting version number."); uint64_t Version = support::endian::read( Buf.data()); if (Version != remarks::CurrentRemarkVersion) return createStringError(std::errc::illegal_byte_sequence, "Mismatching remark version. Got %" PRId64 ", expected %" PRId64 ".", Version, remarks::CurrentRemarkVersion); Buf = Buf.drop_front(sizeof(uint64_t)); return Version; } static Expected parseStrTabSize(StringRef &Buf) { if (Buf.size() < sizeof(uint64_t)) return createStringError(std::errc::illegal_byte_sequence, "Expecting string table size."); uint64_t StrTabSize = support::endian::read( Buf.data()); Buf = Buf.drop_front(sizeof(uint64_t)); return StrTabSize; } static Expected parseStrTab(StringRef &Buf, uint64_t StrTabSize) { if (Buf.size() < StrTabSize) return createStringError(std::errc::illegal_byte_sequence, "Expecting string table."); // Attach the string table to the parser. ParsedStringTable Result(StringRef(Buf.data(), StrTabSize)); Buf = Buf.drop_front(StrTabSize); return Expected(std::move(Result)); } Expected> remarks::createYAMLParserFromMeta(StringRef Buf, Optional StrTab) { // We now have a magic number. The metadata has to be correct. Expected isMeta = parseMagic(Buf); if (!isMeta) return isMeta.takeError(); // If it's not recognized as metadata, roll back. std::unique_ptr SeparateBuf; if (*isMeta) { Expected Version = parseVersion(Buf); if (!Version) return Version.takeError(); Expected StrTabSize = parseStrTabSize(Buf); if (!StrTabSize) return StrTabSize.takeError(); // If the size of string table is not 0, try to build one. if (*StrTabSize != 0) { if (StrTab) return createStringError(std::errc::illegal_byte_sequence, "String table already provided."); Expected MaybeStrTab = parseStrTab(Buf, *StrTabSize); if (!MaybeStrTab) return MaybeStrTab.takeError(); StrTab = std::move(*MaybeStrTab); } // If it starts with "---", there is no external file. if (!Buf.startswith("---")) { // At this point, we expect Buf to contain the external file path. // Try to open the file and start parsing from there. ErrorOr> BufferOrErr = MemoryBuffer::getFile(Buf); if (std::error_code EC = BufferOrErr.getError()) return errorCodeToError(EC); // Keep the buffer alive. SeparateBuf = std::move(*BufferOrErr); Buf = SeparateBuf->getBuffer(); } } std::unique_ptr Result = StrTab ? std::make_unique(Buf, std::move(*StrTab)) : std::make_unique(Buf); if (SeparateBuf) Result->SeparateBuf = std::move(SeparateBuf); return std::move(Result); } YAMLRemarkParser::YAMLRemarkParser(StringRef Buf) : YAMLRemarkParser(Buf, None) {} YAMLRemarkParser::YAMLRemarkParser(StringRef Buf, Optional StrTab) : RemarkParser{Format::YAML}, StrTab(std::move(StrTab)), LastErrorMessage(), SM(setupSM(LastErrorMessage)), Stream(Buf, SM), YAMLIt(Stream.begin()) {} Error YAMLRemarkParser::error(StringRef Message, yaml::Node &Node) { return make_error(Message, SM, Stream, Node); } Error YAMLRemarkParser::error() { if (LastErrorMessage.empty()) return Error::success(); Error E = make_error(LastErrorMessage); LastErrorMessage.clear(); return E; } Expected> YAMLRemarkParser::parseRemark(yaml::Document &RemarkEntry) { if (Error E = error()) return std::move(E); yaml::Node *YAMLRoot = RemarkEntry.getRoot(); if (!YAMLRoot) { return createStringError(std::make_error_code(std::errc::invalid_argument), "not a valid YAML file."); } auto *Root = dyn_cast(YAMLRoot); if (!Root) return error("document root is not of mapping type.", *YAMLRoot); std::unique_ptr Result = std::make_unique(); Remark &TheRemark = *Result; // First, the type. It needs special handling since is not part of the // key-value stream. Expected T = parseType(*Root); if (!T) return T.takeError(); else TheRemark.RemarkType = *T; // Then, parse the fields, one by one. for (yaml::KeyValueNode &RemarkField : *Root) { Expected MaybeKey = parseKey(RemarkField); if (!MaybeKey) return MaybeKey.takeError(); StringRef KeyName = *MaybeKey; if (KeyName == "Pass") { if (Expected MaybeStr = parseStr(RemarkField)) TheRemark.PassName = *MaybeStr; else return MaybeStr.takeError(); } else if (KeyName == "Name") { if (Expected MaybeStr = parseStr(RemarkField)) TheRemark.RemarkName = *MaybeStr; else return MaybeStr.takeError(); } else if (KeyName == "Function") { if (Expected MaybeStr = parseStr(RemarkField)) TheRemark.FunctionName = *MaybeStr; else return MaybeStr.takeError(); } else if (KeyName == "Hotness") { if (Expected MaybeU = parseUnsigned(RemarkField)) TheRemark.Hotness = *MaybeU; else return MaybeU.takeError(); } else if (KeyName == "DebugLoc") { if (Expected MaybeLoc = parseDebugLoc(RemarkField)) TheRemark.Loc = *MaybeLoc; else return MaybeLoc.takeError(); } else if (KeyName == "Args") { auto *Args = dyn_cast(RemarkField.getValue()); if (!Args) return error("wrong value type for key.", RemarkField); for (yaml::Node &Arg : *Args) { if (Expected MaybeArg = parseArg(Arg)) TheRemark.Args.push_back(*MaybeArg); else return MaybeArg.takeError(); } } else { return error("unknown key.", RemarkField); } } // Check if any of the mandatory fields are missing. if (TheRemark.RemarkType == Type::Unknown || TheRemark.PassName.empty() || TheRemark.RemarkName.empty() || TheRemark.FunctionName.empty()) return error("Type, Pass, Name or Function missing.", *RemarkEntry.getRoot()); return std::move(Result); } Expected YAMLRemarkParser::parseType(yaml::MappingNode &Node) { auto Type = StringSwitch(Node.getRawTag()) .Case("!Passed", remarks::Type::Passed) .Case("!Missed", remarks::Type::Missed) .Case("!Analysis", remarks::Type::Analysis) .Case("!AnalysisFPCommute", remarks::Type::AnalysisFPCommute) .Case("!AnalysisAliasing", remarks::Type::AnalysisAliasing) .Case("!Failure", remarks::Type::Failure) .Default(remarks::Type::Unknown); if (Type == remarks::Type::Unknown) return error("expected a remark tag.", Node); return Type; } Expected YAMLRemarkParser::parseKey(yaml::KeyValueNode &Node) { if (auto *Key = dyn_cast(Node.getKey())) return Key->getRawValue(); return error("key is not a string.", Node); } Expected YAMLRemarkParser::parseStr(yaml::KeyValueNode &Node) { auto *Value = dyn_cast(Node.getValue()); if (!Value) return error("expected a value of scalar type.", Node); StringRef Result = Value->getRawValue(); if (Result.front() == '\'') Result = Result.drop_front(); if (Result.back() == '\'') Result = Result.drop_back(); return Result; } Expected YAMLRemarkParser::parseUnsigned(yaml::KeyValueNode &Node) { SmallVector Tmp; auto *Value = dyn_cast(Node.getValue()); if (!Value) return error("expected a value of scalar type.", Node); unsigned UnsignedValue = 0; if (Value->getValue(Tmp).getAsInteger(10, UnsignedValue)) return error("expected a value of integer type.", *Value); return UnsignedValue; } Expected YAMLRemarkParser::parseDebugLoc(yaml::KeyValueNode &Node) { auto *DebugLoc = dyn_cast(Node.getValue()); if (!DebugLoc) return error("expected a value of mapping type.", Node); Optional File; Optional Line; Optional Column; for (yaml::KeyValueNode &DLNode : *DebugLoc) { Expected MaybeKey = parseKey(DLNode); if (!MaybeKey) return MaybeKey.takeError(); StringRef KeyName = *MaybeKey; if (KeyName == "File") { if (Expected MaybeStr = parseStr(DLNode)) File = *MaybeStr; else return MaybeStr.takeError(); } else if (KeyName == "Column") { if (Expected MaybeU = parseUnsigned(DLNode)) Column = *MaybeU; else return MaybeU.takeError(); } else if (KeyName == "Line") { if (Expected MaybeU = parseUnsigned(DLNode)) Line = *MaybeU; else return MaybeU.takeError(); } else { return error("unknown entry in DebugLoc map.", DLNode); } } // If any of the debug loc fields is missing, return an error. if (!File || !Line || !Column) return error("DebugLoc node incomplete.", Node); return RemarkLocation{*File, *Line, *Column}; } Expected YAMLRemarkParser::parseArg(yaml::Node &Node) { auto *ArgMap = dyn_cast(&Node); if (!ArgMap) return error("expected a value of mapping type.", Node); Optional KeyStr; Optional ValueStr; Optional Loc; for (yaml::KeyValueNode &ArgEntry : *ArgMap) { Expected MaybeKey = parseKey(ArgEntry); if (!MaybeKey) return MaybeKey.takeError(); StringRef KeyName = *MaybeKey; // Try to parse debug locs. if (KeyName == "DebugLoc") { // Can't have multiple DebugLoc entries per argument. if (Loc) return error("only one DebugLoc entry is allowed per argument.", ArgEntry); if (Expected MaybeLoc = parseDebugLoc(ArgEntry)) { Loc = *MaybeLoc; continue; } else return MaybeLoc.takeError(); } // If we already have a string, error out. if (ValueStr) return error("only one string entry is allowed per argument.", ArgEntry); // Try to parse the value. if (Expected MaybeStr = parseStr(ArgEntry)) ValueStr = *MaybeStr; else return MaybeStr.takeError(); // Keep the key from the string. KeyStr = KeyName; } if (!KeyStr) return error("argument key is missing.", *ArgMap); if (!ValueStr) return error("argument value is missing.", *ArgMap); return Argument{*KeyStr, *ValueStr, Loc}; } Expected> YAMLRemarkParser::next() { if (YAMLIt == Stream.end()) return make_error(); Expected> MaybeResult = parseRemark(*YAMLIt); if (!MaybeResult) { // Avoid garbage input, set the iterator to the end. YAMLIt = Stream.end(); return MaybeResult.takeError(); } ++YAMLIt; return std::move(*MaybeResult); } Expected YAMLStrTabRemarkParser::parseStr(yaml::KeyValueNode &Node) { auto *Value = dyn_cast(Node.getValue()); if (!Value) return error("expected a value of scalar type.", Node); StringRef Result; // If we have a string table, parse it as an unsigned. unsigned StrID = 0; if (Expected MaybeStrID = parseUnsigned(Node)) StrID = *MaybeStrID; else return MaybeStrID.takeError(); if (Expected Str = (*StrTab)[StrID]) Result = *Str; else return Str.takeError(); if (Result.front() == '\'') Result = Result.drop_front(); if (Result.back() == '\'') Result = Result.drop_back(); return Result; }