Skip to content

Commit fe5ae26

Browse files
committed
Fix #9972 (Add support for SARIF output format)
1 parent 59e4eef commit fe5ae26

File tree

14 files changed

+233
-13
lines changed

14 files changed

+233
-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
@@ -762,7 +762,7 @@ $(libcppdir)/vfvalue.o: lib/vfvalue.cpp lib/config.h lib/errortypes.h lib/mathli
762762
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
763763
$(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/cmdlineparser.cpp
764764

765-
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
765+
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
766766
$(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/cppcheckexecutor.cpp
767767

768768
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: 123 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,117 @@
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+
if (ruleIds.insert(finding.id).second) {
86+
picojson::object rule;
87+
rule["id"] = picojson::value(finding.id);
88+
picojson::object shortDescription;
89+
shortDescription["text"] = picojson::value(finding.shortMessage());
90+
rule["shortDescription"] = picojson::value(shortDescription);
91+
ret.emplace_back(rule);
92+
}
93+
}
94+
return ret;
95+
}
96+
97+
static picojson::array serializeLocations(const ErrorMessage& finding) {
98+
picojson::array ret;
99+
for (const auto& location : finding.callStack) {
100+
picojson::object physicalLocation;
101+
picojson::object artifactLocation;
102+
artifactLocation["uri"] = picojson::value(location.getOrigFile(false));
103+
physicalLocation["artifactLocation"] = picojson::value(artifactLocation);
104+
picojson::object region;
105+
region["startLine"] = picojson::value(static_cast<int64_t>(location.line));
106+
region["startColumn"] = picojson::value(static_cast<int64_t>(location.column));
107+
physicalLocation["region"] = picojson::value(region);
108+
picojson::object loc;
109+
loc["physicalLocation"] = picojson::value(physicalLocation);
110+
ret.emplace_back(loc);
111+
}
112+
return ret;
113+
}
114+
115+
picojson::array serializeResults() const {
116+
picojson::array results;
117+
for (const auto& finding : mFindings) {
118+
picojson::object res;
119+
res["level"] = picojson::value(sarifLevel(finding.severity));
120+
if (!finding.callStack.empty())
121+
res["locations"] = picojson::value(serializeLocations(finding));
122+
picojson::object message;
123+
message["text"] = picojson::value(finding.shortMessage());
124+
res["message"] = picojson::value(message);
125+
res["ruleId"] = picojson::value(finding.id);
126+
results.emplace_back(res);
127+
}
128+
return results;
129+
}
130+
131+
picojson::value serializeRuns(const std::string& productName, const std::string& version) const {
132+
picojson::object driver;
133+
driver["name"] = picojson::value(productName);
134+
driver["version"] = picojson::value(version);
135+
driver["informationUri"] = picojson::value("https://cppcheck.sourceforge.io");
136+
driver["rules"] = picojson::value(serializeRules());
137+
picojson::object tool;
138+
tool["driver"] = picojson::value(driver);
139+
picojson::object run;
140+
run["tool"] = picojson::value(tool);
141+
run["results"] = picojson::value(serializeResults());
142+
picojson::array runs{picojson::value(run)};
143+
return picojson::value(runs);
144+
}
145+
146+
std::string serialize(std::string productName) const {
147+
const auto nameAndVersion = Settings::getNameAndVersion(productName);
148+
productName = nameAndVersion.first.empty() ? "Cppcheck" : nameAndVersion.first;
149+
std::string version = nameAndVersion.first.empty() ? CppCheck::version() : nameAndVersion.second;
150+
if (version.find(' ') != std::string::npos)
151+
version.erase(version.find(' '), std::string::npos);
152+
153+
picojson::object doc;
154+
doc["version"] = picojson::value("2.1.0");
155+
doc["$schema"] = picojson::value("https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json");
156+
doc["runs"] = serializeRuns(productName, version);
157+
158+
return picojson::value(doc).serialize(true);
159+
}
160+
private:
161+
162+
163+
static std::string sarifLevel(Severity severity) {
164+
switch (severity) {
165+
case Severity::error:
166+
return "error";
167+
case Severity::warning:
168+
case Severity::style:
169+
case Severity::portability:
170+
case Severity::performance:
171+
return "warning";
172+
case Severity::information:
173+
case Severity::internal:
174+
case Severity::debug:
175+
case Severity::none:
176+
return "note";
177+
}
178+
return "note";
179+
}
180+
181+
182+
183+
std::vector<ErrorMessage> mFindings;
184+
};
185+
74186
class CmdLineLoggerStd : public CmdLineLogger
75187
{
76188
public:
@@ -104,6 +216,9 @@ namespace {
104216
}
105217

106218
~StdLogger() override {
219+
if (mSettings.outputFormat == Settings::OutputFormat::sarif) {
220+
reportErr(mSarifReport.serialize(mSettings.cppcheckCfgProductName));
221+
}
107222
delete mErrorOutput;
108223
}
109224

@@ -182,6 +297,11 @@ namespace {
182297
* CTU information
183298
*/
184299
std::string mCtuInfo;
300+
301+
/**
302+
* SARIF report generator
303+
*/
304+
SarifReport mSarifReport;
185305
};
186306
}
187307

@@ -455,7 +575,9 @@ void StdLogger::reportErr(const ErrorMessage &msg)
455575
if (!mShownErrors.insert(msg.toString(mSettings.verbose)).second)
456576
return;
457577

458-
if (mSettings.xml)
578+
if (mSettings.outputFormat == Settings::OutputFormat::sarif)
579+
mSarifReport.addFinding(msg);
580+
else if (mSettings.xml)
459581
reportErr(msg.toXML());
460582
else
461583
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)