// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Diag Sarif output file // // Code available from: https://verilator.org // //************************************************************************* // // Copyright 2004-2025 by Wilson Snyder. This program is free software; you // can redistribute it and/or modify it under the terms of either the GNU // Lesser General Public License Version 3 or the Perl Artistic License // Version 2.0. // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 // //************************************************************************* #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT #include "V3DiagSarif.h" #include "V3File.h" #include "V3Os.h" VL_DEFINE_DEBUG_FUNCTIONS; // ###################################################################### class V3DiagSarifImp final { // MEMBERS std::deque m_messages; std::set m_codes; std::map m_codeIndex; // METHODS void calculate() { for (auto& msgr : m_messages) if (msgr.code().isNamed()) m_codes.emplace(msgr.code()); int i = 0; for (const auto& code : m_codes) m_codeIndex[code] = i++; } void putRules(V3OutJsonFile& of) const { for (const auto& code : m_codes) { of.begin().put("id", code.ascii()).put("helpUri", code.url()).end(); } } void putText(V3OutJsonFile& of, const string& clean, const string& formatted) const { const string markdown = "```\n" + formatted + "\n```\n"; of.put("text", VString::trimWhitespace(clean)).put("markdown", markdown); } void putLocation(V3OutJsonFile& of, const FileLine* fl) const { const string filename = V3Os::filenameRealPath(fl->filename()); of.begin("physicalLocation"); of.begin("artifactLocation"); if (V3Os::filenameIsRel(filename)) { // SARIF spec says rel has no // of.put("uri", "file:" + filename).put("uriBaseId", "%srcroot%"); } else { of.put("uri", "file://" + filename); } of.end() .begin("region") .put("sourceLanguage", "systemverilog") .put("startLine", fl->firstLineno()) .put("startColumn", fl->firstColumn()); if (fl->firstLineno() != fl->lastLineno()) of.put("endLine", fl->lastLineno()); of.put("endColumn", fl->lastColumn()); of.begin("snippit", '{'); putText(of, fl->prettySource(), V3Error::stripMetaText(fl->warnContextPrimary(), false)); of.end(); // snippit of.end(); // region of.end(); // artifactLocation } string substrToNextRelated(const string& str) { const size_t pos = str.find("__WARNRELATED(", 0); return (pos == std::string::npos) ? str : str.substr(0, pos); } void putResult(V3OutJsonFile& of, const VErrorMessage& msg) { of.begin(); of.put("level", msg.code().severityInfo() ? "none" : V3Error::isError(msg.code(), false) ? "error" : "warning"); string text = msg.text(); // UINFO(9, "Result raw text " << text); // We put the entire message including relatedLocations text // into the primary message text, because viewers such as // https://microsoft.github.io/sarif-web-component/ // currently appear to ignore relatedLocations. const string first_clean = V3Error::stripMetaText(text, true); const string first_fmt = V3Error::stripMetaText(text, false); of.begin("message"); putText(of, first_clean, first_fmt); of.end(); if (auto fl = msg.fileline()) { of.begin("locations", '[').begin(); putLocation(of, fl); of.end(); of.end(); } if (text.find("__WARNRELATED(") != std::string::npos) { of.begin("relatedLocations", '['); size_t pos = 0; while ((pos = text.find("__WARNRELATED(", pos)) != std::string::npos) { const size_t start = pos + std::strlen("__WARNRELATED("); size_t end = start; while (end < text.size() && text[end] != ')') ++end; const size_t index = std::atoi(text.substr(start, end + 1).c_str()); if (end < text.size()) end += std::strlen(")"); text = text.substr(end, text.size()); UASSERT_STATIC(index < msg.filelines().size(), "Error message warnRelated without fileline"); const string related_clean = V3Error::stripMetaText(substrToNextRelated(text), true); const string related_fmt = V3Error::stripMetaText(substrToNextRelated(text), false); const FileLine* fl = msg.filelines()[index]; of.begin().begin("message"); putText(of, related_clean, related_fmt); of.end(); // message putLocation(of, fl); of.end(); } of.end(); } if (msg.code().isNamed()) of.put("ruleId", msg.code().ascii()).put("ruleIndex", m_codeIndex[msg.code()]); of.end(); } // STATIC FUNCTIONS public: static V3DiagSarifImp& s() { static V3DiagSarifImp s_s; return s_s; } void pushMessage(const VErrorMessage& msg) { m_messages.push_back(msg); } void output(bool success) { V3OutJsonFile of{v3Global.opt.diagnosticsSarifOutput()}; calculate(); of.put("$schema", "https://json.schemastore.org/sarif-2.1.0-rtm.5.json") .put("version", "2.1.0"); of.begin("runs", '[').begin(); of.begin("tool") .begin("driver") .put("name", "Verilator") .put("version", VString::replaceSubstr(V3Options::version(), "Verilator ", "")) .put("informationUri", "https://verilator.org"); of.begin("rules", '['); putRules(of); of.end() // rules .end() // driver .end(); //tool of.begin("invocations", '[') .begin() .put("commandLine", v3Global.opt.allArgsString()) .put("executionSuccessful", success) .end() .end(); of.begin("results", '['); for (auto& msgr : m_messages) putResult(of, msgr); of.end(); of.end(); // runs ] of.end(); // runs } } }; void V3DiagSarif::pushMessage(const VErrorMessage& msg) VL_MT_DISABLED { if (!v3Global.opt.diagnosticsSarif()) return; V3DiagSarifImp::s().pushMessage(msg); } void V3DiagSarif::output(bool success) { if (!v3Global.opt.diagnosticsSarif()) return; UINFO(2, __FUNCTION__ << ":"); V3DiagSarifImp::s().output(success); }