|
28 | 28 | #include "errorlogger.h" |
29 | 29 | #include "errortypes.h" |
30 | 30 | #include "filesettings.h" |
| 31 | +#include "json.h" |
31 | 32 | #include "settings.h" |
32 | 33 | #include "singleexecutor.h" |
33 | 34 | #include "suppressions.h" |
|
71 | 72 | #endif |
72 | 73 |
|
73 | 74 | 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 | + |
74 | 221 | class CmdLineLoggerStd : public CmdLineLogger |
75 | 222 | { |
76 | 223 | public: |
@@ -104,6 +251,9 @@ namespace { |
104 | 251 | } |
105 | 252 |
|
106 | 253 | ~StdLogger() override { |
| 254 | + if (mSettings.outputFormat == Settings::OutputFormat::sarif) { |
| 255 | + reportErr(mSarifReport.serialize(mSettings.cppcheckCfgProductName)); |
| 256 | + } |
107 | 257 | delete mErrorOutput; |
108 | 258 | } |
109 | 259 |
|
@@ -182,6 +332,11 @@ namespace { |
182 | 332 | * CTU information |
183 | 333 | */ |
184 | 334 | std::string mCtuInfo; |
| 335 | + |
| 336 | + /** |
| 337 | + * SARIF report generator |
| 338 | + */ |
| 339 | + SarifReport mSarifReport; |
185 | 340 | }; |
186 | 341 | } |
187 | 342 |
|
@@ -455,7 +610,9 @@ void StdLogger::reportErr(const ErrorMessage &msg) |
455 | 610 | if (!mShownErrors.insert(msg.toString(mSettings.verbose)).second) |
456 | 611 | return; |
457 | 612 |
|
458 | | - if (mSettings.xml) |
| 613 | + if (mSettings.outputFormat == Settings::OutputFormat::sarif) |
| 614 | + mSarifReport.addFinding(msg); |
| 615 | + else if (mSettings.xml) |
459 | 616 | reportErr(msg.toXML()); |
460 | 617 | else |
461 | 618 | reportErr(msg.toString(mSettings.verbose, mSettings.templateFormat, mSettings.templateLocation)); |
|
0 commit comments