Skip to content

Commit c49b941

Browse files
danmarmario-campos
andauthored
Fix #9972 (Add support for SARIF output format) (danmar#6863)
Co-authored-by: Mario Campos <[email protected]>
1 parent b272152 commit c49b941

File tree

14 files changed

+272
-13
lines changed

14 files changed

+272
-13
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ ifndef INCLUDE_FOR_LIB
173173
endif
174174

175175
ifndef INCLUDE_FOR_CLI
176-
INCLUDE_FOR_CLI=-Ilib -isystem externals/simplecpp -isystem externals/tinyxml2
176+
INCLUDE_FOR_CLI=-Ilib -isystem externals/picojson -isystem externals/simplecpp -isystem externals/tinyxml2
177177
endif
178178

179179
ifndef INCLUDE_FOR_TEST
@@ -752,7 +752,7 @@ $(libcppdir)/vfvalue.o: lib/vfvalue.cpp lib/config.h lib/errortypes.h lib/mathli
752752
cli/cmdlineparser.o: cli/cmdlineparser.cpp cli/cmdlinelogger.h cli/cmdlineparser.h cli/filelister.h externals/tinyxml2/tinyxml2.h lib/addoninfo.h lib/analyzerinfo.h lib/check.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/pathmatch.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/utils.h lib/xml.h
753753
$(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/cmdlineparser.cpp
754754

755-
cli/cppcheckexecutor.o: cli/cppcheckexecutor.cpp cli/cmdlinelogger.h cli/cmdlineparser.h cli/cppcheckexecutor.h cli/cppcheckexecutorseh.h cli/executor.h cli/processexecutor.h cli/signalhandler.h cli/singleexecutor.h cli/threadexecutor.h lib/addoninfo.h lib/analyzerinfo.h lib/check.h lib/checkersreport.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h
755+
cli/cppcheckexecutor.o: cli/cppcheckexecutor.cpp cli/cmdlinelogger.h cli/cmdlineparser.h cli/cppcheckexecutor.h cli/cppcheckexecutorseh.h cli/executor.h cli/processexecutor.h cli/signalhandler.h cli/singleexecutor.h cli/threadexecutor.h externals/picojson/picojson.h lib/addoninfo.h lib/analyzerinfo.h lib/check.h lib/checkersreport.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/json.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h
756756
$(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/cppcheckexecutor.cpp
757757

758758
cli/cppcheckexecutorseh.o: cli/cppcheckexecutorseh.cpp cli/cppcheckexecutor.h cli/cppcheckexecutorseh.h lib/config.h lib/filesettings.h lib/path.h lib/platform.h lib/standards.h lib/utils.h

cli/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ if (BUILD_CLI)
1212
else()
1313
target_include_directories(cli_objs SYSTEM PRIVATE ${tinyxml2_INCLUDE_DIRS})
1414
endif()
15+
target_externals_include_directories(cli_objs PRIVATE ${PROJECT_SOURCE_DIR}/externals/picojson/)
1516
target_externals_include_directories(cli_objs PRIVATE ${PROJECT_SOURCE_DIR}/externals/simplecpp/)
1617
if (NOT CMAKE_DISABLE_PRECOMPILE_HEADERS)
1718
target_precompile_headers(cli_objs PRIVATE precompiled.h)

cli/cli.vcxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
</PropertyGroup>
8686
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
8787
<ClCompile>
88-
<AdditionalIncludeDirectories>..\lib;..\externals;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
88+
<AdditionalIncludeDirectories>..\lib;..\externals;..\externals\picojson;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
8989
<BufferSecurityCheck>true</BufferSecurityCheck>
9090
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
9191
<Optimization>Disabled</Optimization>
@@ -114,7 +114,7 @@
114114
</ItemDefinitionGroup>
115115
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug-PCRE|x64'">
116116
<ClCompile>
117-
<AdditionalIncludeDirectories>..\lib;..\externals;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
117+
<AdditionalIncludeDirectories>..\lib;..\externals;..\externals\picojson;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
118118
<BufferSecurityCheck>true</BufferSecurityCheck>
119119
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
120120
<Optimization>Disabled</Optimization>
@@ -143,7 +143,7 @@
143143
</ItemDefinitionGroup>
144144
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
145145
<ClCompile>
146-
<AdditionalIncludeDirectories>..\lib;..\externals;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
146+
<AdditionalIncludeDirectories>..\lib;..\externals;..\externals\picojson;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
147147
<BufferSecurityCheck>false</BufferSecurityCheck>
148148
<Optimization>MaxSpeed</Optimization>
149149
<PreprocessorDefinitions>CPPCHECKLIB_IMPORT;TINYXML2_IMPORT;NDEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;_WIN64;%(PreprocessorDefinitions)</PreprocessorDefinitions>
@@ -181,7 +181,7 @@
181181
</ItemDefinitionGroup>
182182
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release-PCRE|x64'">
183183
<ClCompile>
184-
<AdditionalIncludeDirectories>..\lib;..\externals;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
184+
<AdditionalIncludeDirectories>..\lib;..\externals;..\externals\picojson;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
185185
<BufferSecurityCheck>false</BufferSecurityCheck>
186186
<Optimization>MaxSpeed</Optimization>
187187
<PreprocessorDefinitions>CPPCHECKLIB_IMPORT;TINYXML2_IMPORT;NDEBUG;WIN32;HAVE_RULES;_CRT_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;_WIN64;%(PreprocessorDefinitions)</PreprocessorDefinitions>

cli/cmdlineparser.cpp

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,20 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a
915915
else if (std::strncmp(argv[i], "--output-file=", 14) == 0)
916916
mSettings.outputFile = Path::simplifyPath(argv[i] + 14);
917917

918+
else if (std::strncmp(argv[i], "--output-format=", 16) == 0) {
919+
const std::string format = argv[i] + 16;
920+
if (format == "sarif")
921+
mSettings.outputFormat = Settings::OutputFormat::sarif;
922+
else if (format == "xml")
923+
mSettings.outputFormat = Settings::OutputFormat::xml;
924+
else {
925+
mLogger.printError("argument to '--output-format=' must be 'sarif' or 'xml'.");
926+
return Result::Fail;
927+
}
928+
mSettings.xml = (mSettings.outputFormat == Settings::OutputFormat::xml);
929+
}
930+
931+
918932
// Experimental: limit execution time for extended valueflow analysis. basic valueflow analysis
919933
// is always executed.
920934
else if (std::strncmp(argv[i], "--performance-valueflow-max-time=", 33) == 0) {
@@ -949,6 +963,7 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a
949963

950964
// Write results in results.plist
951965
else if (std::strncmp(argv[i], "--plist-output=", 15) == 0) {
966+
mSettings.outputFormat = Settings::OutputFormat::plist;
952967
mSettings.plistOutput = Path::simplifyPath(argv[i] + 15);
953968
if (mSettings.plistOutput.empty())
954969
mSettings.plistOutput = ".";
@@ -1361,8 +1376,10 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a
13611376
mSettings.verbose = true;
13621377

13631378
// Write results in results.xml
1364-
else if (std::strcmp(argv[i], "--xml") == 0)
1379+
else if (std::strcmp(argv[i], "--xml") == 0) {
13651380
mSettings.xml = true;
1381+
mSettings.outputFormat = Settings::OutputFormat::xml;
1382+
}
13661383

13671384
// Define the XML file version (and enable XML output)
13681385
else if (std::strncmp(argv[i], "--xml-version=", 14) == 0) {
@@ -1378,6 +1395,7 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a
13781395
mSettings.xml_version = tmp;
13791396
// Enable also XML if version is set
13801397
mSettings.xml = true;
1398+
mSettings.outputFormat = Settings::OutputFormat::xml;
13811399
}
13821400

13831401
else {
@@ -1623,6 +1641,10 @@ void CmdLineParser::printHelp() const
16231641
" is 2. A larger value will mean more errors can be found\n"
16241642
" but also means the analysis will be slower.\n"
16251643
" --output-file=<file> Write results to file, rather than standard error.\n"
1644+
" --output-format=<format>\n"
1645+
" Specify the output format. The available formats are:\n"
1646+
" * sarif\n"
1647+
" * xml\n"
16261648
" --platform=<type>, --platform=<file>\n"
16271649
" Specifies platform specific types and sizes. The\n"
16281650
" available builtin platforms are:\n"

cli/cppcheckexecutor.cpp

Lines changed: 158 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "errorlogger.h"
2929
#include "errortypes.h"
3030
#include "filesettings.h"
31+
#include "json.h"
3132
#include "settings.h"
3233
#include "singleexecutor.h"
3334
#include "suppressions.h"
@@ -71,6 +72,152 @@
7172
#endif
7273

7374
namespace {
75+
class SarifReport {
76+
public:
77+
void addFinding(ErrorMessage msg) {
78+
mFindings.push_back(std::move(msg));
79+
}
80+
81+
picojson::array serializeRules() const {
82+
picojson::array ret;
83+
std::set<std::string> ruleIds;
84+
for (const auto& finding : mFindings) {
85+
// github only supports findings with locations
86+
if (finding.callStack.empty())
87+
continue;
88+
if (ruleIds.insert(finding.id).second) {
89+
picojson::object rule;
90+
rule["id"] = picojson::value(finding.id);
91+
// rule.shortDescription.text
92+
picojson::object shortDescription;
93+
shortDescription["text"] = picojson::value(finding.shortMessage());
94+
rule["shortDescription"] = picojson::value(shortDescription);
95+
// rule.fullDescription.text
96+
picojson::object fullDescription;
97+
fullDescription["text"] = picojson::value(finding.verboseMessage());
98+
rule["fullDescription"] = picojson::value(fullDescription);
99+
// rule.help.text
100+
picojson::object help;
101+
help["text"] = picojson::value(finding.verboseMessage()); // FIXME provide proper help text
102+
rule["help"] = picojson::value(help);
103+
// rule.properties.precision, rule.properties.problem.severity
104+
picojson::object properties;
105+
properties["precision"] = picojson::value(sarifPrecision(finding));
106+
double securitySeverity = 0;
107+
if (finding.severity == Severity::error && !ErrorLogger::isCriticalErrorId(finding.id))
108+
securitySeverity = 9.9; // We see undefined behavior
109+
//else if (finding.severity == Severity::warning)
110+
// securitySeverity = 5.1; // We see potential undefined behavior
111+
if (securitySeverity > 0.5) {
112+
properties["security-severity"] = picojson::value(securitySeverity);
113+
const picojson::array tags{picojson::value("security")};
114+
properties["tags"] = picojson::value(tags);
115+
}
116+
rule["properties"] = picojson::value(properties);
117+
118+
ret.emplace_back(rule);
119+
}
120+
}
121+
return ret;
122+
}
123+
124+
static picojson::array serializeLocations(const ErrorMessage& finding) {
125+
picojson::array ret;
126+
for (const auto& location : finding.callStack) {
127+
picojson::object physicalLocation;
128+
picojson::object artifactLocation;
129+
artifactLocation["uri"] = picojson::value(location.getfile(false));
130+
physicalLocation["artifactLocation"] = picojson::value(artifactLocation);
131+
picojson::object region;
132+
region["startLine"] = picojson::value(static_cast<int64_t>(location.line));
133+
region["startColumn"] = picojson::value(static_cast<int64_t>(location.column));
134+
region["endLine"] = region["startLine"];
135+
region["endColumn"] = region["startColumn"];
136+
physicalLocation["region"] = picojson::value(region);
137+
picojson::object loc;
138+
loc["physicalLocation"] = picojson::value(physicalLocation);
139+
ret.emplace_back(loc);
140+
}
141+
return ret;
142+
}
143+
144+
picojson::array serializeResults() const {
145+
picojson::array results;
146+
for (const auto& finding : mFindings) {
147+
// github only supports findings with locations
148+
if (finding.callStack.empty())
149+
continue;
150+
picojson::object res;
151+
res["level"] = picojson::value(sarifSeverity(finding));
152+
res["locations"] = picojson::value(serializeLocations(finding));
153+
picojson::object message;
154+
message["text"] = picojson::value(finding.shortMessage());
155+
res["message"] = picojson::value(message);
156+
res["ruleId"] = picojson::value(finding.id);
157+
results.emplace_back(res);
158+
}
159+
return results;
160+
}
161+
162+
picojson::value serializeRuns(const std::string& productName, const std::string& version) const {
163+
picojson::object driver;
164+
driver["name"] = picojson::value(productName);
165+
driver["semanticVersion"] = picojson::value(version);
166+
driver["informationUri"] = picojson::value("https://cppcheck.sourceforge.io");
167+
driver["rules"] = picojson::value(serializeRules());
168+
picojson::object tool;
169+
tool["driver"] = picojson::value(driver);
170+
picojson::object run;
171+
run["tool"] = picojson::value(tool);
172+
run["results"] = picojson::value(serializeResults());
173+
picojson::array runs{picojson::value(run)};
174+
return picojson::value(runs);
175+
}
176+
177+
std::string serialize(std::string productName) const {
178+
const auto nameAndVersion = Settings::getNameAndVersion(productName);
179+
productName = nameAndVersion.first.empty() ? "Cppcheck" : nameAndVersion.first;
180+
std::string version = nameAndVersion.first.empty() ? CppCheck::version() : nameAndVersion.second;
181+
if (version.find(' ') != std::string::npos)
182+
version.erase(version.find(' '), std::string::npos);
183+
184+
picojson::object doc;
185+
doc["version"] = picojson::value("2.1.0");
186+
doc["$schema"] = picojson::value("https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json");
187+
doc["runs"] = serializeRuns(productName, version);
188+
189+
return picojson::value(doc).serialize(true);
190+
}
191+
private:
192+
193+
static std::string sarifSeverity(const ErrorMessage& errmsg) {
194+
if (ErrorLogger::isCriticalErrorId(errmsg.id))
195+
return "error";
196+
switch (errmsg.severity) {
197+
case Severity::error:
198+
case Severity::warning:
199+
case Severity::style:
200+
case Severity::portability:
201+
case Severity::performance:
202+
return "warning";
203+
case Severity::information:
204+
case Severity::internal:
205+
case Severity::debug:
206+
case Severity::none:
207+
return "note";
208+
}
209+
return "note";
210+
}
211+
212+
static std::string sarifPrecision(const ErrorMessage& errmsg) {
213+
if (errmsg.certainty == Certainty::inconclusive)
214+
return "medium";
215+
return "high";
216+
}
217+
218+
std::vector<ErrorMessage> mFindings;
219+
};
220+
74221
class CmdLineLoggerStd : public CmdLineLogger
75222
{
76223
public:
@@ -104,6 +251,9 @@ namespace {
104251
}
105252

106253
~StdLogger() override {
254+
if (mSettings.outputFormat == Settings::OutputFormat::sarif) {
255+
reportErr(mSarifReport.serialize(mSettings.cppcheckCfgProductName));
256+
}
107257
delete mErrorOutput;
108258
}
109259

@@ -182,6 +332,11 @@ namespace {
182332
* CTU information
183333
*/
184334
std::string mCtuInfo;
335+
336+
/**
337+
* SARIF report generator
338+
*/
339+
SarifReport mSarifReport;
185340
};
186341
}
187342

@@ -455,7 +610,9 @@ void StdLogger::reportErr(const ErrorMessage &msg)
455610
if (!mShownErrors.insert(msg.toString(mSettings.verbose)).second)
456611
return;
457612

458-
if (mSettings.xml)
613+
if (mSettings.outputFormat == Settings::OutputFormat::sarif)
614+
mSarifReport.addFinding(msg);
615+
else if (mSettings.xml)
459616
reportErr(msg.toXML());
460617
else
461618
reportErr(msg.toString(mSettings.verbose, mSettings.templateFormat, mSettings.templateLocation));

lib/settings.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,9 @@ class CPPCHECKLIB WARN_UNUSED Settings {
273273
/** @brief write results (--output-file=&lt;file&gt;) */
274274
std::string outputFile;
275275

276+
enum class OutputFormat : std::uint8_t {text, plist, sarif, xml};
277+
OutputFormat outputFormat = OutputFormat::text;
278+
276279
Platform platform;
277280

278281
/** @brief pid of cppcheck. Intention is that this is set in the main process. */

releasenotes.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ GUI:
1010
-
1111

1212
Changed interface:
13+
- SARIF output. Use --output-format=sarif to activate this.
14+
- Add option --output-format=<format>. Allowed formats are sarif and xml.
1315
-
1416

1517
Deprecations:
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
void foo(int x) {
3+
if (x >= 0 || x <= 10) {}
4+
}
5+
6+
dummy=foo();
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
void foo(int x) {
3+
if (x >= 0 && x <= 10) {}
4+
}
5+
6+
dummy=foo();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
samples\incorrectLogicOperator\bad.c:3:16: warning: Logical disjunction always evaluates to true: x >= 0 || x <= 10. [incorrectLogicOperator]
2+
if (x >= 0 || x <= 10) {}
3+
^

0 commit comments

Comments
 (0)