verilator/src/V3DiagSarif.cpp

196 lines
6.9 KiB
C++
Raw Normal View History

// -*- 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<VErrorMessage> m_messages;
std::set<V3ErrorCode> m_codes;
std::map<V3ErrorCode, int> 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);
}