From 66667b6172e07fc4b6efa0491f201eafe5bf7706 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sun, 18 May 2025 04:46:15 +0900 Subject: [PATCH] Support SARIF JSON diagnostic output with `--diagnostics-sarif`. (#6017) --- Changes | 1 + bin/verilator | 2 + docs/guide/exe_verilator.rst | 16 +++ docs/guide/files.rst | 2 + docs/guide/install.rst | 2 +- docs/guide/warnings.rst | 4 + docs/spelling.txt | 2 + src/CMakeLists.txt | 18 +-- src/Makefile_obj.in | 1 + src/V3Ast.cpp | 2 +- src/V3DiagSarif.cpp | 195 ++++++++++++++++++++++++++ src/V3DiagSarif.h | 33 +++++ src/V3Error.cpp | 156 ++++++++++++++------- src/V3Error.h | 69 ++++++++- src/V3FileLine.cpp | 32 +++-- src/V3Graph.cpp | 2 +- src/V3Number.cpp | 2 +- src/V3Options.cpp | 5 + src/V3Options.h | 7 + src/V3String.cpp | 26 ++++ src/V3String.h | 2 + src/Verilator.cpp | 3 + test_regress/driver.py | 3 + test_regress/t/t_dist_error_format.py | 2 + test_regress/t/t_sarif.out | 23 +++ test_regress/t/t_sarif.py | 41 ++++++ test_regress/t/t_sarif.sarif.out | 188 +++++++++++++++++++++++++ test_regress/t/t_sarif.v | 22 +++ test_regress/t/t_sarif_output.py | 20 +++ 29 files changed, 797 insertions(+), 84 deletions(-) create mode 100644 src/V3DiagSarif.cpp create mode 100644 src/V3DiagSarif.h create mode 100644 test_regress/t/t_sarif.out create mode 100755 test_regress/t/t_sarif.py create mode 100644 test_regress/t/t_sarif.sarif.out create mode 100644 test_regress/t/t_sarif.v create mode 100755 test_regress/t/t_sarif_output.py diff --git a/Changes b/Changes index 57754a10c..ee7d03fba 100644 --- a/Changes +++ b/Changes @@ -15,6 +15,7 @@ Verilator 5.037 devel * Support constrained random for associative arrays (#5985) (#5986). [Yilou Wang] * Support assignments to concatenations with impure RHS (#6002). [Ryszard Rozak, Antmicro Ltd.] +* Support SARIF JSON diagnostic output with `--diagnostics-sarif`. * Add BADVLTPRAGMA on unknown Verilator pragmas (#5945). [Shou-Li Hsu] * Add PROCINITASSIGN on initial assignments to process variables (#2481). [Niraj Menon] * Fix --x-initial and --x-assign random stability (#2662) (#5958). [Todd Strader] diff --git a/bin/verilator b/bin/verilator index 143bce8a6..5ec9a5ef0 100755 --- a/bin/verilator +++ b/bin/verilator @@ -345,6 +345,8 @@ detailed descriptions of these arguments. --decorations Set output comment and spacing level --default-language Default language to parse +define+= Set preprocessor define + --diagnostics-sarif Enable SARIF diagnostics output + --diagnostics-sarif-output Set SARIF diagnostics output file --dpi-hdr-only Only produce the DPI header file --dump- Enable dumping everything in source file --dump-defines Show preprocessor defines with -E diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index debb506f8..9faeddd73 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -393,6 +393,22 @@ Summary: standard across Verilog tools while :vlopt:`-D <-D>` is similar to :command:`gcc -D`. +.. option:: --diagnostics-sarif + + Enables diagnostics output into a Static Analysis Results Interchange + Format (SARIF) file, a standard, JSON-based format for the output of + static analysis tools such as linters. See + [SARIF](http://sarifweb.azurewebsites.net/), + [sarif-tools](https://github.com/microsoft/sarif-tools), and the [SARIF + web-based viewer](https://microsoft.github.io/sarif-web-component/). + +.. option:: --diagnostics-sarif-output + + Specifies the filename for the SARIF output file (`.sarif`) of + :vlopt:`--diagnostics-sarif`. Using this option automatically sets + :vlopt:`--diagnostics-sarif`. If not specified, output defaults to + :file:`.sarif`. + .. option:: --dpi-hdr-only Only generate the DPI header file. This option does not affect on the diff --git a/docs/guide/files.rst b/docs/guide/files.rst index 18d543dfd..e04323ad9 100644 --- a/docs/guide/files.rst +++ b/docs/guide/files.rst @@ -112,6 +112,8 @@ In specific debug and other modes, it also creates: .. list-table:: + * - *{prefix}*\ .sarif + - SARIF diagnostics (from --diagnostics-sarif) * - *{prefix}*\ .tree.json - JSON tree information (from --json-only) * - *{prefix}*\ .tree.meta.json diff --git a/docs/guide/install.rst b/docs/guide/install.rst index dc6c3ecd6..d54e7c5d0 100644 --- a/docs/guide/install.rst +++ b/docs/guide/install.rst @@ -159,7 +159,7 @@ Those developing Verilator itself may also want these (see internals.rst): sudo apt-get install clang clang-format-14 cmake gdb gprof graphviz lcov sudo apt-get install python3-clang python3-distro yapf3 bear jq - sudo pip3 install sphinx sphinx_rtd_theme sphinxcontrib-spelling breathe ruff + sudo pip3 install sphinx sphinx_rtd_theme sphinxcontrib-spelling breathe ruff sarif-tools sudo pip3 install git+https://github.com/antmicro/astsee.git cpan install Pod::Perldoc diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index bafd1827f..9678be1a9 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -68,6 +68,10 @@ source code corresponding to the error, prefixed by the line number and a " | ". Following this is typically an arrow and ~ pointing at the error on the source line directly above. +Instead of parsing this text diagnostic output, tools that need to +understand Verilator's warning output should read the SARIF JSON output +created with :vlopt:`--diagnostics-sarif`. + List Of Warnings ================ diff --git a/docs/spelling.txt b/docs/spelling.txt index 7199c77ff..de38771b8 100644 --- a/docs/spelling.txt +++ b/docs/spelling.txt @@ -798,6 +798,7 @@ libtcmalloc libverilated linkers linter +linters linux liu livelock @@ -975,6 +976,7 @@ runtimes rw sVerilator saif +sarif sawatzke sc scalared diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7eee3a957..6ad72f864 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -77,6 +77,7 @@ set(HEADERS V3DfgPatternStats.h V3DfgPeephole.h V3DfgVertices.h + V3DiagSarif.h V3DupFinder.h V3EmitC.h V3EmitCBase.h @@ -95,8 +96,8 @@ set(HEADERS V3FileLine.h V3Force.h V3Fork.h - V3FunctionTraits.h V3FuncOpt.h + V3FunctionTraits.h V3Gate.h V3Global.h V3Graph.h @@ -133,8 +134,8 @@ set(HEADERS V3OptionParser.h V3Options.h V3Order.h - V3OrderInternal.h V3OrderGraph.h + V3OrderInternal.h V3OrderMoveGraph.h V3Os.h V3PairingHeap.h @@ -234,8 +235,8 @@ set(COMMON_SOURCES V3DfgPasses.cpp V3DfgPeephole.cpp V3DfgRegularize.cpp + V3DiagSarif.cpp V3DupFinder.cpp - V3Timing.cpp V3EmitCBase.cpp V3EmitCConstPool.cpp V3EmitCFunc.cpp @@ -297,6 +298,10 @@ set(COMMON_SOURCES V3OrderSerial.cpp V3Os.cpp V3Param.cpp + V3ParseGrammar.cpp + V3ParseImp.cpp + V3ParseLex.cpp + V3PreProc.cpp V3PreShell.cpp V3Premit.cpp V3ProtectLib.cpp @@ -320,13 +325,14 @@ set(COMMON_SOURCES V3StatsReport.cpp V3String.cpp V3Subst.cpp + V3TSP.cpp V3Table.cpp V3Task.cpp V3ThreadPool.cpp + V3Timing.cpp V3Trace.cpp V3TraceDecl.cpp V3Tristate.cpp - V3TSP.cpp V3Udp.cpp V3Undriven.cpp V3Unknown.cpp @@ -336,10 +342,6 @@ set(COMMON_SOURCES V3Width.cpp V3WidthCommit.cpp V3WidthSel.cpp - V3ParseImp.cpp - V3ParseGrammar.cpp - V3ParseLex.cpp - V3PreProc.cpp ) set(COVERAGE_SOURCES VlcMain.cpp) diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index ca0181d9d..12269dea2 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -247,6 +247,7 @@ RAW_OBJS_PCH_ASTNOMT = \ V3DfgPasses.o \ V3DfgPeephole.o \ V3DfgRegularize.o \ + V3DiagSarif.o \ V3DupFinder.o \ V3EmitCMain.o \ V3EmitCMake.o \ diff --git a/src/V3Ast.cpp b/src/V3Ast.cpp index f83f60fd0..26cbabc4e 100644 --- a/src/V3Ast.cpp +++ b/src/V3Ast.cpp @@ -1453,7 +1453,7 @@ void AstNode::v3errorEnd(std::ostringstream& str) const VL_RELEASE(V3Error::s(). const string instanceStrExtra = m_fileline->warnIsOff(V3Error::s().errorCode()) ? "" : instanceStr(); if (!m_fileline) { - V3Error::v3errorEnd(str, instanceStrExtra); + V3Error::v3errorEnd(str, instanceStrExtra, nullptr); } else { std::ostringstream nsstr; nsstr << str.str(); diff --git a/src/V3DiagSarif.cpp b/src/V3DiagSarif.cpp new file mode 100644 index 000000000..a7c4b5041 --- /dev/null +++ b/src/V3DiagSarif.cpp @@ -0,0 +1,195 @@ +// -*- 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 << endl << endl); + + // 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__ << ": " << endl); + V3DiagSarifImp::s().output(success); +} diff --git a/src/V3DiagSarif.h b/src/V3DiagSarif.h new file mode 100644 index 000000000..45b31256c --- /dev/null +++ b/src/V3DiagSarif.h @@ -0,0 +1,33 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Diag Sarif output file +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-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 +// +//************************************************************************* + +#ifndef VERILATOR_V3DIAGSARIF_H_ +#define VERILATOR_V3DIAGSARIF_H_ + +#include "config_build.h" +#include "verilatedos.h" + +#include + +//============================================================================ + +class V3DiagSarif final { +public: + static void pushMessage(const VErrorMessage& msg) VL_MT_DISABLED; + static void output(bool success) VL_MT_DISABLED; +}; + +#endif // Guard diff --git a/src/V3Error.cpp b/src/V3Error.cpp index 7a190c349..9f1d791c3 100644 --- a/src/V3Error.cpp +++ b/src/V3Error.cpp @@ -19,6 +19,7 @@ #include "V3Os.h" #ifndef V3ERROR_NO_GLOBAL_ # include "V3Ast.h" +# include "V3DiagSarif.h" # include "V3Global.h" # include "V3Stats.h" VL_DEFINE_DEBUG_FUNCTIONS; @@ -70,7 +71,7 @@ bool V3ErrorGuarded::isError(V3ErrorCode code, bool supp) VL_REQUIRES(m_mutex) { } string V3ErrorGuarded::msgPrefix() VL_REQUIRES(m_mutex) { - const V3ErrorCode code = m_errorCode; + const V3ErrorCode code = m_message.code(); const bool supp = m_errorSuppressed; if (supp) { return "-arning-suppressed: "; @@ -112,6 +113,7 @@ void V3ErrorGuarded::vlAbortOrExit() VL_REQUIRES(m_mutex) { } string V3ErrorGuarded::warnMoreSpaces() VL_REQUIRES(m_mutex) { + UASSERT_STATIC(!m_message.isClear(), "warnMore() outside of v3errorPrep...v3errorEnd"); return string(msgPrefix().size(), ' '); } @@ -124,42 +126,49 @@ void V3ErrorGuarded::suppressThisWarning() VL_REQUIRES(m_mutex) { void V3ErrorGuarded::v3errorPrep(V3ErrorCode code) VL_REQUIRES(m_mutex) { m_errorStr.str(""); - m_errorCode = code; + UASSERT_STATIC(m_message.isClear(), "Attempted v3errorPrep inside v3errorPrep...v3errorEnd"); + m_message.init(code); m_errorContexted = false; m_errorSuppressed = false; } -void V3ErrorGuarded::v3errorEnd(std::ostringstream& sstr, const string& extra) +void V3ErrorGuarded::v3errorEnd(std::ostringstream& sstr, const string& extra, FileLine* fileline) VL_REQUIRES(m_mutex) { static bool s_firedTooMany = false; - v3errorEndGuts(sstr, extra); + v3errorEndGuts(sstr, extra, fileline); + m_message.clear(); if (errorLimit() && errorCount() >= errorLimit() && !s_firedTooMany) { s_firedTooMany = true; // Recurses here v3errorEnd((v3errorPrep(V3ErrorCode::EC_FATALMANY), (v3errorStr() << "Exiting due to too many errors encountered; --error-limit=" << errorCount() << std::endl), - v3errorStr())); + v3errorStr()), + "", nullptr); assert(0); // LCOV_EXCL_LINE VL_UNREACHABLE; } } // cppcheck-has-bug-suppress constParameter -void V3ErrorGuarded::v3errorEndGuts(std::ostringstream& sstr, const string& extra) - VL_REQUIRES(m_mutex) { +void V3ErrorGuarded::v3errorEndGuts(std::ostringstream& sstr, const string& extra, + FileLine* fileline) VL_REQUIRES(m_mutex) { // 'extra' is appended to the message, and is is excluded in check for // duplicate messages. Currently used for reporting instance name. #if defined(__COVERITY__) || defined(__cppcheck__) if (m_errorCode == V3ErrorCode::EC_FATAL) __coverity_panic__(x); #endif + UASSERT_STATIC(!m_message.isClear(), "v3errorEnd() outside of v3errorPrep...v3errorEnd"); + m_message.fileline(fileline); + // Skip suppressed messages if (m_errorSuppressed // On debug, show only non default-off warning to prevent pages of warnings - && (!debug() || debug() < 3 || m_errorCode.defaultsOff())) + && (!debug() || debug() < 3 || m_message.code().defaultsOff())) return; - string msg = msgPrefix() + sstr.str(); + string msg + = V3Error::warnContextBegin() + msgPrefix() + V3Error::warnContextEnd() + sstr.str(); // If suppressed print only first line to reduce verbosity string firstLine = msg; @@ -182,14 +191,7 @@ void V3ErrorGuarded::v3errorEndGuts(std::ostringstream& sstr, const string& extr msg.erase(pos); } } - // Trailing newline (generally not on messages) & remove dup newlines - { - msg += '\n'; // Trailing newlines generally not put on messages so add - string::size_type pos; - while ((pos = msg.find("\n\n")) != string::npos) msg.erase(pos + 1, 1); - while ((pos = msg_additional.find("\n\n")) != string::npos) - msg_additional.erase(pos + 1, 1); - } + msg += '\n'; // Trailing newlines generally not put on messages so add if (!extra.empty() && !m_errorSuppressed) { string extraMsg = VString::replaceSubstr(extra, V3Error::warnMore(), warnMoreSpaces()); extraMsg = warnMoreSpaces() + extraMsg + "\n"; @@ -197,53 +199,64 @@ void V3ErrorGuarded::v3errorEndGuts(std::ostringstream& sstr, const string& extr msg.insert(pos + 1, extraMsg); } // Output + string text; if ( #ifndef V3ERROR_NO_GLOBAL_ - !(v3Global.opt.quietExit() && m_errorCode == V3ErrorCode::EC_FATALMANY) + !(v3Global.opt.quietExit() && m_message.code() == V3ErrorCode::EC_FATALMANY) #else true #endif - ) { - std::cerr << msg; - } - if (!m_errorSuppressed && !m_errorCode.severityInfo()) { - const bool anError = isError(m_errorCode, m_errorSuppressed); - if (m_errorCode != V3ErrorCode::EC_FATALMANY // Not verbose on final too-many-errors error - && !m_describedEachWarn[m_errorCode]) { - m_describedEachWarn[m_errorCode] = true; - if (m_errorCode >= V3ErrorCode::EC_FIRST_NAMED) { - std::cerr << warnMoreSpaces() << "... For " << (anError ? "error" : "warning") - << " description see " << m_errorCode.url() << endl; - } else if (m_errCount >= 1 && m_errorCode.severityFatal() && !m_tellInternal) { + ) + text += msg; + if (m_errorSuppressed || m_message.code().severityInfo()) { + std::cerr << V3Error::stripMetaText(text, false); + m_message.text(text); +#ifndef V3ERROR_NO_GLOBAL_ + V3DiagSarif::pushMessage(m_message); +#endif + } else { + const bool anError = isError(m_message.code(), m_errorSuppressed); + if (m_message.code() + != V3ErrorCode::EC_FATALMANY // Not verbose on final too-many-errors error + && !m_describedEachWarn[m_message.code()]) { + m_describedEachWarn[m_message.code()] = true; + if (m_message.code() >= V3ErrorCode::EC_FIRST_NAMED) { + text += warnMoreSpaces() + "... For " + (anError ? "error" : "warning") + + " description see " + m_message.code().url() + '\n'; + } else if (m_errCount >= 1 && m_message.code().severityFatal() && !m_tellInternal) { m_tellInternal = true; - std::cerr << warnMoreSpaces() - << "... This fatal error may be caused by the earlier error(s);" - " resolve those first." - << endl; + text += warnMoreSpaces() + + "... This fatal error may be caused by the earlier error(s);" + " resolve those first.\n"; } else if (!m_tellManual) { m_tellManual = true; - std::cerr << warnMoreSpaces() << "... See the manual at " << m_errorCode.url() - << " for more assistance." << endl; + text += warnMoreSpaces() + "... See the manual at " + m_message.code().url() + + " for more assistance.\n"; } - if (!m_pretendError[m_errorCode] && !m_errorCode.hardError()) { - std::cerr << warnMoreSpaces() << "... Use \"/* verilator lint_off " - << m_errorCode.ascii() - << " */\" and lint_on around source to disable this message." << endl; - if (m_errorCode.dangerous()) { - std::cerr << warnMoreSpaces() << "*** See " << m_errorCode.url() - << " before disabling this,\n"; - std::cerr << warnMoreSpaces() - << "else you may end up with different sim results." << endl; + if (!m_pretendError[m_message.code()] && !m_message.code().hardError()) { + text += warnMoreSpaces() + "... Use \"/* verilator lint_off " + + m_message.code().ascii() + + " */\" and lint_on around source to disable this message.\n"; + if (m_message.code().dangerous()) { + text += warnMoreSpaces() + "*** See " + m_message.code().url() + + " before disabling this,\n"; + text += warnMoreSpaces() + "else you may end up with different sim results.\n"; } } } - if (!msg_additional.empty()) std::cerr << msg_additional; + if (!msg_additional.empty()) text += msg_additional; + std::cerr << V3Error::stripMetaText(text, false); + m_message.text(text); +#ifndef V3ERROR_NO_GLOBAL_ + V3DiagSarif::pushMessage(m_message); +#endif + if (anError) { incErrors(); } else { incWarnings(); } - if (m_errorCode.severityFatal()) { + if (m_message.code().severityFatal()) { static bool inFatal = false; if (!inFatal) { inFatal = true; @@ -264,6 +277,7 @@ void V3ErrorGuarded::v3errorEndGuts(std::ostringstream& sstr, const string& extr } vlAbortOrExit(); } + V3DiagSarif::output(false); #endif } @@ -295,6 +309,45 @@ string V3Error::lineStr(const char* filename, int lineno) VL_PURE { return out.str(); } +string V3Error::stripMetaText(const string& text, bool stripContext) VL_PURE { + string result; + result.reserve(text.size()); + int inBegins = 0; + for (string::size_type pos = 0; pos < text.size();) { + // string::starts_with is C++20 + if (0 == text.compare(pos, std::strlen("__WARN"), "__WARN")) { + if (0 == text.compare(pos, std::strlen("__WARNRELATED("), "__WARNRELATED(")) { + while (pos < text.size() && text[pos] != ')') ++pos; + if (pos < text.size() && text[pos] == ')') ++pos; + continue; + } + if (0 + == text.compare(pos, std::strlen(V3Error::WARN_CONTEXT_BEGIN), + V3Error::WARN_CONTEXT_BEGIN)) { + pos += std::strlen(V3Error::WARN_CONTEXT_BEGIN); + ++inBegins; + continue; + } + if (0 + == text.compare(pos, std::strlen(V3Error::WARN_CONTEXT_END), + V3Error::WARN_CONTEXT_END)) { + pos += std::strlen(V3Error::WARN_CONTEXT_END); + // No assert, is not VL_PURE + // UASSERT_STATIC(inBegins, "warnContextEnd() outside of warnContextBegin()"); + --inBegins; + continue; + } + } + if (!stripContext || inBegins == 0) { + // Remove double newlines + if (!(text[pos] == '\n' && !result.empty() && result[result.size() - 1] == '\n')) + result += text[pos]; + } + ++pos; + } + return result; +} + void V3Error::abortIfWarnings() { const bool exwarn = warnFatal() && warnCount(); if (errorCount() && exwarn) { @@ -324,16 +377,17 @@ std::ostringstream& V3Error::v3errorPrepFileLine(V3ErrorCode code, const char* f v3errorPrep(code) << file << ":" << std::dec << line << ": "; return v3errorStr(); } -void V3Error::v3errorEnd(std::ostringstream& sstr, const string& extra) VL_RELEASE(s().m_mutex) { - s().v3errorEnd(sstr, extra); +void V3Error::v3errorEnd(std::ostringstream& sstr, const string& extra, FileLine* fileline) + VL_RELEASE(s().m_mutex) { + s().v3errorEnd(sstr, extra, fileline); V3Error::s().m_mutex.unlock(); } void v3errorEnd(std::ostringstream& sstr) VL_RELEASE(V3Error::s().m_mutex) { - V3Error::v3errorEnd(sstr); + V3Error::v3errorEnd(sstr, "", nullptr); } void v3errorEndFatal(std::ostringstream& sstr) VL_RELEASE(V3Error::s().m_mutex) { - V3Error::v3errorEnd(sstr); + V3Error::v3errorEnd(sstr, "", nullptr); assert(0); // LCOV_EXCL_LINE VL_UNREACHABLE; } diff --git a/src/V3Error.h b/src/V3Error.h index f9132b491..e7c52d1e9 100644 --- a/src/V3Error.h +++ b/src/V3Error.h @@ -316,6 +316,42 @@ inline std::ostream& operator<<(std::ostream& os, const V3ErrorCode& rhs) { // ###################################################################### +class VErrorMessage final { + // TYPES + using FileLines = std::deque; + // MEMBERS + V3ErrorCode m_code; // Which warning + string m_text; // Warning message text + FileLine* m_fileline; // Primary warning fileline + FileLines m_filelines; // Additional referenced filelines +public: + // CONSTRUCTORS + VErrorMessage() { clear(); } + ~VErrorMessage() = default; + void clear() { init(V3ErrorCode::EC_MIN); } + void init(V3ErrorCode code) { + m_code = code; + m_text = ""; + m_fileline = nullptr; + m_filelines.clear(); + } + // ACCESSORS + V3ErrorCode code() const { return m_code; } + bool isClear() const { return m_code == V3ErrorCode::EC_MIN; } + string text() const { return m_text; } + void text(const string& msg) { m_text = msg; } + FileLine* fileline() const { return m_fileline; } + void fileline(FileLine* fl) { m_fileline = fl; } + FileLines filelines() const { return m_filelines; } + // Returns identifier for use in warnRelated() + int pushFileline(const FileLine* fl) { + m_filelines.emplace_back(fl); + return m_filelines.size() - 1; + } +}; + +// ###################################################################### + class V3ErrorGuarded final { // Should only be used by V3ErrorGuarded::m_mutex is already locked // contains guarded members @@ -328,11 +364,11 @@ public: private: static constexpr unsigned MAX_ERRORS = 50; // Fatal after this may errors + // MEMBERS // Tell user to see manual, 0=not yet, 1=doit, 2=disable bool m_tellManual VL_GUARDED_BY(m_mutex) = false; bool m_tellInternal VL_GUARDED_BY(m_mutex) = false; - V3ErrorCode m_errorCode VL_GUARDED_BY(m_mutex) - = V3ErrorCode::EC_FATAL; // Error string being formed will abort + VErrorMessage m_message VL_GUARDED_BY(m_mutex); // Message being formed bool m_errorSuppressed VL_GUARDED_BY(m_mutex) = false; // Error being formed should be suppressed MessagesSet m_messages VL_GUARDED_BY(m_mutex); // Errors outputted, to remove dups @@ -351,10 +387,13 @@ private: bool m_warnFatal VL_GUARDED_BY(m_mutex) = true; // Option: --warnFatal Warnings are fatal std::ostringstream m_errorStr VL_GUARDED_BY(m_mutex); // Error string being formed + // METHODS void v3errorPrep(V3ErrorCode code) VL_REQUIRES(m_mutex); std::ostringstream& v3errorStr() VL_REQUIRES(m_mutex) { return m_errorStr; } - void v3errorEnd(std::ostringstream& sstr, const string& extra = "") VL_REQUIRES(m_mutex); - void v3errorEndGuts(std::ostringstream& sstr, const string& extra) VL_REQUIRES(m_mutex); + void v3errorEnd(std::ostringstream& sstr, const string& extra, FileLine* fileline) + VL_REQUIRES(m_mutex); + void v3errorEndGuts(std::ostringstream& sstr, const string& extra, FileLine* fileline) + VL_REQUIRES(m_mutex); public: V3RecursiveMutex m_mutex; // Make sure only single thread is in class @@ -387,7 +426,7 @@ public: void errorLimit(int level) VL_REQUIRES(m_mutex) { m_errorLimit = level; } bool warnFatal() VL_REQUIRES(m_mutex) { return m_warnFatal; } void warnFatal(bool flag) VL_REQUIRES(m_mutex) { m_warnFatal = flag; } - V3ErrorCode errorCode() VL_REQUIRES(m_mutex) { return m_errorCode; } + V3ErrorCode errorCode() VL_REQUIRES(m_mutex) { return m_message.code(); } bool errorContexted() VL_REQUIRES(m_mutex) { return m_errorContexted; } int warnCount() VL_REQUIRES(m_mutex) { return m_warnCount; } bool errorSuppressed() VL_REQUIRES(m_mutex) { return m_errorSuppressed; } @@ -399,6 +438,10 @@ public: m_describedEachWarn[code] = flag; } void suppressThisWarning() VL_REQUIRES(m_mutex); + string warnRelated(const FileLine* fl) VL_REQUIRES(m_mutex) { + const int id = m_message.pushFileline(fl); + return "__WARNRELATED("s + std::to_string(id) + ")"; + } string warnContextNone() VL_REQUIRES(m_mutex) { errorContexted(true); return ""; @@ -479,6 +522,10 @@ public: s().incWarnings(); } static void init(); + static bool isError(V3ErrorCode code, bool supp) VL_MT_SAFE_EXCLUDES(s().m_mutex) { + const V3RecursiveLockGuard guard{s().m_mutex}; + return s().isError(code, supp); + } static void abortIfErrors() { if (errorCount()) abortIfWarnings(); } @@ -493,6 +540,7 @@ public: s().pretendError(code, flag); } static string lineStr(const char* filename, int lineno) VL_PURE; + static string stripMetaText(const string& text, bool stripMeta) VL_PURE; static V3ErrorCode errorCode() VL_MT_SAFE_EXCLUDES(s().m_mutex) { const V3RecursiveLockGuard guard{s().m_mutex}; return s().errorCode(); @@ -503,7 +551,14 @@ public: } // When printing an error/warning, print prefix for multiline message - static string warnMore() VL_MT_SAFE { return "__WARNMORE__"; } + static constexpr const char* WARN_MORE = "__WARNMORE__"; + static string warnMore() VL_MT_SAFE { return WARN_MORE; } + // When printing an error/warning, mark beginning of context information (can nest) + static constexpr const char* WARN_CONTEXT_BEGIN = "__WARNBEGIN__"; + static string warnContextBegin() VL_MT_SAFE { return WARN_CONTEXT_BEGIN; } + // When printing an error/warning, mark end of context information (can nest) + static constexpr const char* WARN_CONTEXT_END = "__WARNEND__"; + static string warnContextEnd() VL_MT_SAFE { return WARN_CONTEXT_END; } // This function marks place in error message from which point message // should be printed after information on the error code. // The post-processing is done in v3errorEnd function. @@ -521,7 +576,7 @@ public: VL_ACQUIRE(s().m_mutex); static std::ostringstream& v3errorStr() VL_REQUIRES(s().m_mutex) { return s().v3errorStr(); } // static, but often overridden in classes. - static void v3errorEnd(std::ostringstream& sstr, const string& extra = "") + static void v3errorEnd(std::ostringstream& sstr, const string& extra, FileLine* fileline) VL_RELEASE(s().m_mutex); static void vlAbort(); }; diff --git a/src/V3FileLine.cpp b/src/V3FileLine.cpp index 72daf9d05..ffa8fdc45 100644 --- a/src/V3FileLine.cpp +++ b/src/V3FileLine.cpp @@ -224,7 +224,7 @@ string FileLine::xmlDetailedLocation() const { string FileLine::lineDirectiveStrg(int enterExit) const { return "`line "s + cvtToStr(lastLineno()) + " \"" - + V3OutFormatter::quoteNameControls(filename()) + "\" " + cvtToStr(enterExit) + "\n"; + + V3OutFormatter::quoteNameControls(filename()) + "\" " + cvtToStr(enterExit) + '\n'; } void FileLine::lineDirective(const char* textp, int& enterExitRef) { @@ -422,15 +422,15 @@ void FileLine::v3errorEnd(std::ostringstream& sstr, const string& extra) // duplicate messages. Currently used for reporting instance name. std::ostringstream nsstr; // sstr with fileline prefix and context std::ostringstream wsstr; // sstr for waiver (no fileline) with context - if (lastLineno()) nsstr << this; + if (lastLineno()) nsstr << V3Error::warnContextBegin() << this << V3Error::warnContextEnd(); nsstr << sstr.str(); wsstr << sstr.str(); - nsstr << "\n"; - wsstr << "\n"; + nsstr << '\n'; + wsstr << '\n'; std::ostringstream extrass; // extra spaced out for prefix if (!extra.empty()) { - extrass << std::setw(ascii().length()) << " " - << ": " << extra; + extrass << V3Error::warnContextBegin() << std::setw(ascii().length()) << " " + << ": " << V3Error::warnContextEnd() << extra; } if (warnIsOff(V3Error::s().errorCode())) { V3Error::s().suppressThisWarning(); @@ -440,26 +440,29 @@ void FileLine::v3errorEnd(std::ostringstream& sstr, const string& extra) wsstr << add; nsstr << add; } - m_waive = V3Config::waive(this, V3Error::s().errorCode(), wsstr.str()); + const string waiverText = V3Error::stripMetaText(wsstr.str(), false); + m_waive = V3Config::waive(this, V3Error::s().errorCode(), waiverText); if (m_waive) { V3Error::s().suppressThisWarning(); } else { - V3Waiver::addEntry(V3Error::s().errorCode(), filename(), wsstr.str()); + V3Waiver::addEntry(V3Error::s().errorCode(), filename(), waiverText); } } - V3Error::v3errorEnd(nsstr, extrass.str()); + V3Error::v3errorEnd(nsstr, extrass.str(), this); } string FileLine::warnMore() const VL_REQUIRES(V3Error::s().m_mutex) { if (lastLineno()) { - return V3Error::warnMore() + string(ascii().size(), ' ') + ": "; + return V3Error::warnContextBegin() + V3Error::warnMore() + string(ascii().size(), ' ') + + ": " + V3Error::warnContextEnd(); } else { return V3Error::warnMore(); } } string FileLine::warnOther() const VL_REQUIRES(V3Error::s().m_mutex) { if (lastLineno()) { - return V3Error::warnMore() + ascii() + ": "; + return V3Error::s().warnRelated(this) + V3Error::warnContextBegin() + V3Error::warnMore() + + ascii() + ": " + V3Error::warnContextEnd(); } else { return V3Error::warnMore(); } @@ -501,7 +504,7 @@ string FileLine::warnContext() const { && sourceLine.length() >= static_cast(lastColumn() - 1)) { string linestr = cvtToStr(firstLineno()); while (linestr.size() < 5) linestr = ' ' + linestr; - out += linestr + " | " + sourceLine + "\n"; + out += linestr + " | " + sourceLine + '\n'; out += std::string(linestr.size(), ' ') + " | "; out += string((firstColumn() - 1), ' ') + '^'; // Can't use UASSERT_OBJ used in warnings already inside the error end handler @@ -509,10 +512,11 @@ string FileLine::warnContext() const { // Note lastColumn() can be <= firstColumn() in some weird preproc expansions out += string((lastColumn() - firstColumn() - 1), '~'); } - out += "\n"; + out += '\n'; } + return V3Error::warnContextBegin() + out + V3Error::warnContextEnd(); } - return out; + return ""; } string FileLine::warnContextParent() const VL_REQUIRES(V3Error::s().m_mutex) { diff --git a/src/V3Graph.cpp b/src/V3Graph.cpp index 6b7b79dd9..7efb029b1 100644 --- a/src/V3Graph.cpp +++ b/src/V3Graph.cpp @@ -112,7 +112,7 @@ void V3GraphVertex::v3errorEnd(std::ostringstream& str) const VL_RELEASE(V3Error if (FileLine* const flp = fileline()) { flp->v3errorEnd(nsstr); } else { - V3Error::v3errorEnd(nsstr); + V3Error::v3errorEnd(nsstr, "", nullptr); } } void V3GraphVertex::v3errorEndFatal(std::ostringstream& str) const diff --git a/src/V3Number.cpp b/src/V3Number.cpp index 031a253ab..612c0ccf6 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -80,7 +80,7 @@ void V3Number::v3errorEnd(const std::ostringstream& str) const VL_RELEASE(V3Erro } else if (m_fileline) { m_fileline->v3errorEnd(nsstr); } else { - V3Error::v3errorEnd(nsstr); + V3Error::v3errorEnd(nsstr, "", nullptr); } } diff --git a/src/V3Options.cpp b/src/V3Options.cpp index cba4e7d83..b23eda621 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1276,6 +1276,11 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, DECL_OPTION("-decoration", CbCall, [this, fl]() { decorations(fl, "medium"); }); DECL_OPTION("-decorations", CbVal, [this, fl](const char* optp) { decorations(fl, optp); }); DECL_OPTION("-no-decoration", CbCall, [this, fl]() { decorations(fl, "none"); }); + DECL_OPTION("-diagnostics-sarif", OnOff, &m_diagnosticsSarif); + DECL_OPTION("-diagnostics-sarif-output", CbVal, [this](const char* optp) { + m_diagnosticsSarifOutput = optp; + m_diagnosticsSarif = true; + }); DECL_OPTION("-dpi-hdr-only", OnOff, &m_dpiHdrOnly); DECL_OPTION("-dump-", CbPartialMatch, [this](const char* optp) { m_dumpLevel[optp] = 3; }); DECL_OPTION("-no-dump-", CbPartialMatch, [this](const char* optp) { m_dumpLevel[optp] = 0; }); diff --git a/src/V3Options.h b/src/V3Options.h index 9ab1b61e3..0fa4e94f2 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -252,6 +252,7 @@ private: bool m_debugWidth = false; // main switch: --debug-width bool m_decoration = true; // main switch: --decoration bool m_decorationNodes = false; // main switch: --decoration=nodes + bool m_diagnosticsSarif = false; // main switch: --diagnostics-sarif bool m_dpiHdrOnly = false; // main switch: --dpi-hdr-only bool m_emitAccessors = false; // main switch: --emit-accessors bool m_exe = false; // main switch: --exe @@ -354,6 +355,7 @@ private: int m_compLimitParens = 240; // compiler selection; number of nested parens string m_buildDepBin; // main switch: --build-dep-bin {filename} + string m_diagnosticsSarifOutput; // main switch: --diagnostics-sarif-output string m_exeName; // main switch: -o {name} string m_flags; // main switch: -f {name} string m_hierParamsFile; // main switch: --hierarchical-params-file @@ -519,6 +521,7 @@ public: bool debugWidth() const VL_PURE { return m_debugWidth; } bool decoration() const VL_MT_SAFE { return m_decoration; } bool decorationNodes() const VL_MT_SAFE { return m_decorationNodes; } + bool diagnosticsSarif() const VL_MT_SAFE { return m_diagnosticsSarif; } bool dpiHdrOnly() const { return m_dpiHdrOnly; } bool dumpDefines() const { return m_dumpLevel.count("defines") && m_dumpLevel.at("defines"); } bool dumpTreeDot() const { @@ -632,6 +635,10 @@ public: int compLimitMembers() const VL_MT_SAFE { return m_compLimitMembers; } int compLimitParens() const { return m_compLimitParens; } + string diagnosticsSarifOutput() const VL_MT_SAFE { + return m_diagnosticsSarifOutput.empty() ? makeDir() + "/" + prefix() + ".sarif" + : m_diagnosticsSarifOutput; + } string exeName() const { return m_exeName != "" ? m_exeName : prefix(); } string hierParamFile() const { return m_hierParamsFile; } string jsonOnlyOutput() const { return m_jsonOnlyOutput; } diff --git a/src/V3String.cpp b/src/V3String.cpp index 29307bdea..27c1d5a06 100644 --- a/src/V3String.cpp +++ b/src/V3String.cpp @@ -230,6 +230,32 @@ string VString::removeWhitespace(const string& str) { return result; } +string VString::trimWhitespace(const string& str) { + string result; + result.reserve(str.size()); + string add; + bool newline = false; + for (const char c : str) { + if (newline && std::isspace(c)) continue; + if (c == '\n') { + add = "\n"; + newline = true; + continue; + } + if (std::isspace(c)) { + add += c; + continue; + } + if (!add.empty()) { + result += add; + newline = false; + add.clear(); + } + result += c; + } + return result; +} + bool VString::isIdentifier(const string& str) { for (const char c : str) { if (!isIdentifierChar(c)) return false; diff --git a/src/V3String.h b/src/V3String.h index 65bfa8d94..f40b46b8a 100644 --- a/src/V3String.h +++ b/src/V3String.h @@ -116,6 +116,8 @@ public: static string spaceUnprintable(const string& str) VL_PURE; // Remove any whitespace static string removeWhitespace(const string& str); + // Trim leading/trailing whitespace on each line + static string trimWhitespace(const string& str); // Return true if only identifer or "" static bool isIdentifier(const string& str); // Return true if char is valid character in C identifiers diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 8905aacb9..2d3753092 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -42,6 +42,7 @@ #include "V3DepthBlock.h" #include "V3Descope.h" #include "V3DfgOptimizer.h" +#include "V3DiagSarif.h" #include "V3EmitC.h" #include "V3EmitCMain.h" #include "V3EmitCMake.h" @@ -861,6 +862,8 @@ int main(int argc, char** argv) { execBuildJob(); } + V3DiagSarif::output(true); + // Explicitly release resources V3PreShell::shutdown(); v3Global.shutdown(); diff --git a/test_regress/driver.py b/test_regress/driver.py index d69c66da5..be045681c 100755 --- a/test_regress/driver.py +++ b/test_regress/driver.py @@ -2294,11 +2294,14 @@ class VlTest: line = re.sub(r'\r', '<#013>', line) line = re.sub(r'Command Failed[^\n]+', 'Command Failed', line) line = re.sub(r'Version: Verilator[^\n]+', 'Version: Verilator ###', line) + line = re.sub(r'"version": "[^"]+"', '"version": "###"', line) line = re.sub(r'CPU Time: +[0-9.]+ seconds[^\n]+', 'CPU Time: ###', line) line = re.sub(r'\?v=[0-9.]+', '?v=latest', line) # warning URL line = re.sub(r'_h[0-9a-f]{8}_', '_h########_', line) line = re.sub(r'%Error: /[^: ]+/([^/:])', r'%Error: .../\1', line) # Avoid absolute paths + line = re.sub(r'("file://)/[^: ]+/([^/:])', r'\1/.../\2', + line) # Avoid absolute paths line = re.sub(r' \/[^ ]+\/verilated_std.sv', ' verilated_std.sv', line) # (line, n) = re.subn(r'Exiting due to.*', r"Exiting due to", line) diff --git a/test_regress/t/t_dist_error_format.py b/test_regress/t/t_dist_error_format.py index 154ae7084..22d43d46b 100755 --- a/test_regress/t/t_dist_error_format.py +++ b/test_regress/t/t_dist_error_format.py @@ -22,6 +22,8 @@ def formats(): warns = {} lnmatch = 0 for filename in test.glob_some(files): + if re.search(r'\.sarif\.out', filename): + continue wholefile = test.file_contents(filename) filename = os.path.basename(filename) if re.search(r'(Exiting due to|%Error|%Warning)', wholefile): diff --git a/test_regress/t/t_sarif.out b/test_regress/t/t_sarif.out new file mode 100644 index 000000000..3f98e74a8 --- /dev/null +++ b/test_regress/t/t_sarif.out @@ -0,0 +1,23 @@ +%Warning-MODDUP: t/t_sarif.v:21:8: Duplicate declaration of module: 't' + 21 | module t; + | ^ + t/t_sarif.v:7:8: ... Location of original declaration + 7 | module t( + | ^ + ... For warning description see https://verilator.org/warn/MODDUP?v=latest + ... Use "/* verilator lint_off MODDUP */" and lint_on around source to disable this message. +%Warning-WIDTHTRUNC: t/t_sarif.v:12:23: Operator ASSIGNW expects 2 bits on the Assign RHS, but Assign RHS's CONST '5'h1f' generates 5 bits. + : ... note: In instance 't' + 12 | wire [1:0] trunced = 5'b11111; + | ^ + ... For warning description see https://verilator.org/warn/WIDTHTRUNC?v=latest + ... Use "/* verilator lint_off WIDTHTRUNC */" and lint_on around source to disable this message. +%Warning-MULTIDRIVEN: t/t_sarif.v:10:18: Signal has multiple driving blocks with different clocking: 'multidriven' + t/t_sarif.v:15:6: ... Location of first driving block + 15 | multidriven <= '1; + | ^~~~~~~~~~~ + t/t_sarif.v:17:6: ... Location of other driving block + 17 | multidriven <= '0; + | ^~~~~~~~~~~ + ... For warning description see https://verilator.org/warn/MULTIDRIVEN?v=latest + ... Use "/* verilator lint_off MULTIDRIVEN */" and lint_on around source to disable this message. diff --git a/test_regress/t/t_sarif.py b/test_regress/t/t_sarif.py new file mode 100755 index 000000000..7780ff8e0 --- /dev/null +++ b/test_regress/t/t_sarif.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2024 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 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(verilator_flags2=['-Wno-fatal --diagnostics-sarif'], + expect_filename=test.golden_filename) + +sarif_filename = test.obj_dir + "/" + test.vm_prefix + ".sarif" + +# Make sure V3Error meta comments aren't in any outputs +test.file_grep_not(test.compile_log_filename, r'__WARN') +test.file_grep_not(sarif_filename, r'__WARN') + +test.files_identical(sarif_filename, "t/" + test.name + ".sarif.out", "logfile") + +# Check that sarif parses +nout = test.run_capture("sarif --version", check=False) +version_match = re.search(r'SARIF tools', nout, re.IGNORECASE) +if not version_match: + test.skip("sarif is not installed") + +html_filename = test.obj_dir + "/validation.html" + +test.run(cmd=['sarif', 'html', sarif_filename, '--output', html_filename]) + +# Validator: +# https://sarifweb.azurewebsites.net/Validation + +# Rewrite +# test.run(cmd=['sarif copy t/t_sarif.out --output ' + test.obj_dir + '/t_sarif.out.rewrite']) + +test.passes() diff --git a/test_regress/t/t_sarif.sarif.out b/test_regress/t/t_sarif.sarif.out new file mode 100644 index 000000000..58f0f611e --- /dev/null +++ b/test_regress/t/t_sarif.sarif.out @@ -0,0 +1,188 @@ +{ + "$schema": "https://json.schemastore.org/sarif-2.1.0-rtm.5.json", + "version": "###", + "runs": [ + { + "tool": { + "driver": { + "name": "Verilator", + "version": "###", + "informationUri": "https://verilator.org", + "rules": [ + { + "id": "MODDUP", + "helpUri": "https://verilator.org/warn/MODDUP?v=latest" + }, + { + "id": "MULTIDRIVEN", + "helpUri": "https://verilator.org/warn/MULTIDRIVEN?v=latest" + }, + { + "id": "WIDTHTRUNC", + "helpUri": "https://verilator.org/warn/WIDTHTRUNC?v=latest" + } + ] + } + }, + "invocations": [ + { + "commandLine": "--prefix Vt_sarif -cc -Mdir obj_vlt/t_sarif --fdedup --debug-check --comp-limit-members 10 --x-assign unique -Wno-fatal --diagnostics-sarif --clk clk -f input.vc +define+TEST_OBJ_DIR=obj_vlt/t_sarif +define+TEST_DUMPFILE=obj_vlt/t_sarif/simx.vcd t/t_sarif.v +librescan +notimingchecks +libext+.v -y t +incdir+t", + "executionSuccessful": true + } + ], + "results": [ + { + "level": "warning", + "message": { + "text": "Duplicate declaration of module: 't'\n... Location of original declaration\n... For warning description see https://verilator.org/warn/MODDUP?v=latest\n... Use \"/* verilator lint_off MODDUP */\" and lint_on around source to disable this message.", + "markdown": "```\n%Warning-MODDUP: t/t_sarif.v:21:8: Duplicate declaration of module: 't'\n 21 | module t; \n | ^\n t/t_sarif.v:7:8: ... Location of original declaration\n 7 | module t(\n | ^\n ... For warning description see https://verilator.org/warn/MODDUP?v=latest\n ... Use \"/* verilator lint_off MODDUP */\" and lint_on around source to disable this message.\n\n```\n" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///.../t_sarif.v" + }, + "region": { + "sourceLanguage": "systemverilog", + "startLine": 21, + "startColumn": 8, + "endColumn": 9, + "snippit": { + "text": "module t;", + "markdown": "```\n 21 | module t; \n | ^\n\n```\n" + } + } + } + } + ], + "relatedLocations": [ + { + "message": { + "text": "... Location of original declaration\n... For warning description see https://verilator.org/warn/MODDUP?v=latest\n... Use \"/* verilator lint_off MODDUP */\" and lint_on around source to disable this message.", + "markdown": "```\n t/t_sarif.v:7:8: ... Location of original declaration\n 7 | module t(\n | ^\n ... For warning description see https://verilator.org/warn/MODDUP?v=latest\n ... Use \"/* verilator lint_off MODDUP */\" and lint_on around source to disable this message.\n\n```\n" + }, + "physicalLocation": { + "artifactLocation": { + "uri": "file:///.../t_sarif.v" + }, + "region": { + "sourceLanguage": "systemverilog", + "startLine": 7, + "startColumn": 8, + "endColumn": 9, + "snippit": { + "text": "module t(", + "markdown": "```\n 7 | module t(\n | ^\n\n```\n" + } + } + } + } + ], + "ruleId": "MODDUP", + "ruleIndex": 0 + }, + { + "level": "warning", + "message": { + "text": "Operator ASSIGNW expects 2 bits on the Assign RHS, but Assign RHS's CONST '5'h1f' generates 5 bits.\n... note: In instance 't'\n... For warning description see https://verilator.org/warn/WIDTHTRUNC?v=latest\n... Use \"/* verilator lint_off WIDTHTRUNC */\" and lint_on around source to disable this message.", + "markdown": "```\n%Warning-WIDTHTRUNC: t/t_sarif.v:12:23: Operator ASSIGNW expects 2 bits on the Assign RHS, but Assign RHS's CONST '5'h1f' generates 5 bits.\n : ... note: In instance 't'\n 12 | wire [1:0] trunced = 5'b11111; \n | ^\n ... For warning description see https://verilator.org/warn/WIDTHTRUNC?v=latest\n ... Use \"/* verilator lint_off WIDTHTRUNC */\" and lint_on around source to disable this message.\n\n```\n" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///.../t_sarif.v" + }, + "region": { + "sourceLanguage": "systemverilog", + "startLine": 12, + "startColumn": 23, + "endColumn": 24, + "snippit": { + "text": " wire [1:0] trunced = 5'b11111;", + "markdown": "```\n 12 | wire [1:0] trunced = 5'b11111; \n | ^\n\n```\n" + } + } + } + } + ], + "ruleId": "WIDTHTRUNC", + "ruleIndex": 2 + }, + { + "level": "warning", + "message": { + "text": "Signal has multiple driving blocks with different clocking: 'multidriven'\n... Location of first driving block\n... Location of other driving block\n... For warning description see https://verilator.org/warn/MULTIDRIVEN?v=latest\n... Use \"/* verilator lint_off MULTIDRIVEN */\" and lint_on around source to disable this message.", + "markdown": "```\n%Warning-MULTIDRIVEN: t/t_sarif.v:10:18: Signal has multiple driving blocks with different clocking: 'multidriven'\n t/t_sarif.v:15:6: ... Location of first driving block\n 15 | multidriven <= '1;\n | ^~~~~~~~~~~\n t/t_sarif.v:17:6: ... Location of other driving block\n 17 | multidriven <= '0;\n | ^~~~~~~~~~~\n ... For warning description see https://verilator.org/warn/MULTIDRIVEN?v=latest\n ... Use \"/* verilator lint_off MULTIDRIVEN */\" and lint_on around source to disable this message.\n\n```\n" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///.../t_sarif.v" + }, + "region": { + "sourceLanguage": "systemverilog", + "startLine": 10, + "startColumn": 18, + "endColumn": 29, + "snippit": { + "text": " output logic multidriven);", + "markdown": "```\n 10 | output logic multidriven);\n | ^~~~~~~~~~~\n\n```\n" + } + } + } + } + ], + "relatedLocations": [ + { + "message": { + "text": "... Location of first driving block", + "markdown": "```\n t/t_sarif.v:15:6: ... Location of first driving block\n 15 | multidriven <= '1;\n | ^~~~~~~~~~~\n\n```\n" + }, + "physicalLocation": { + "artifactLocation": { + "uri": "file:///.../t_sarif.v" + }, + "region": { + "sourceLanguage": "systemverilog", + "startLine": 15, + "startColumn": 6, + "endColumn": 17, + "snippit": { + "text": " multidriven <= '1;", + "markdown": "```\n 15 | multidriven <= '1;\n | ^~~~~~~~~~~\n\n```\n" + } + } + } + }, + { + "message": { + "text": "... Location of other driving block\n... For warning description see https://verilator.org/warn/MULTIDRIVEN?v=latest\n... Use \"/* verilator lint_off MULTIDRIVEN */\" and lint_on around source to disable this message.", + "markdown": "```\n t/t_sarif.v:17:6: ... Location of other driving block\n 17 | multidriven <= '0;\n | ^~~~~~~~~~~\n ... For warning description see https://verilator.org/warn/MULTIDRIVEN?v=latest\n ... Use \"/* verilator lint_off MULTIDRIVEN */\" and lint_on around source to disable this message.\n\n```\n" + }, + "physicalLocation": { + "artifactLocation": { + "uri": "file:///.../t_sarif.v" + }, + "region": { + "sourceLanguage": "systemverilog", + "startLine": 17, + "startColumn": 6, + "endColumn": 17, + "snippit": { + "text": " multidriven <= '0;", + "markdown": "```\n 17 | multidriven <= '0;\n | ^~~~~~~~~~~\n\n```\n" + } + } + } + } + ], + "ruleId": "MULTIDRIVEN", + "ruleIndex": 1 + } + ] + } + ] +} diff --git a/test_regress/t/t_sarif.v b/test_regress/t/t_sarif.v new file mode 100644 index 000000000..7af1b0fab --- /dev/null +++ b/test_regress/t/t_sarif.v @@ -0,0 +1,22 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2009 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t( + input clk1, + input clk2, + output logic multidriven); + + wire [1:0] trunced = 5'b11111; // Warned + + always @ (posedge clk1) + multidriven <= '1; + always @ (posedge clk2) + multidriven <= '0; + +endmodule + +module t; // BAD duplicate +endmodule diff --git a/test_regress/t/t_sarif_output.py b/test_regress/t/t_sarif_output.py new file mode 100755 index 000000000..c1db584cd --- /dev/null +++ b/test_regress/t/t_sarif_output.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2024 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 + +import vltest_bootstrap + +test.scenarios('vlt') +test.top_filename = "t/t_sarif.v" + +test.lint( + verilator_flags2=['-Wno-fatal', '--diagnostics-sarif-output', test.obj_dir + "/my.sarif"]) + +test.file_grep(test.obj_dir + "/my.sarif", "t_sarif.v") + +test.passes()