|
33 | 33 | #include "errortypes.h" |
34 | 34 | #include "filesettings.h" |
35 | 35 | #include "json.h" |
| 36 | +#include "sarifreport.h" |
36 | 37 | #include "settings.h" |
37 | 38 | #include "singleexecutor.h" |
38 | 39 | #include "suppressions.h" |
|
78 | 79 | #endif |
79 | 80 |
|
80 | 81 | namespace { |
81 | | - class SarifReport { |
82 | | - public: |
83 | | - void addFinding(ErrorMessage msg) { |
84 | | - mFindings.push_back(std::move(msg)); |
85 | | - } |
86 | | - |
87 | | - picojson::array serializeRules() const { |
88 | | - picojson::array ret; |
89 | | - std::set<std::string> ruleIds; |
90 | | - for (const auto& finding : mFindings) { |
91 | | - // github only supports findings with locations |
92 | | - if (finding.callStack.empty()) |
93 | | - continue; |
94 | | - if (ruleIds.insert(finding.id).second) { |
95 | | - // setting name and description to empty strings will make github default |
96 | | - // to the instance specific violation message and not rule description, |
97 | | - // this makes it so not all the violations have the same description. |
98 | | - picojson::object rule; |
99 | | - rule["id"] = picojson::value(finding.id); |
100 | | - // rule.name |
101 | | - rule["name"] = picojson::value(""); |
102 | | - // rule.shortDescription.text |
103 | | - picojson::object shortDescription; |
104 | | - shortDescription["text"] = picojson::value(""); |
105 | | - rule["shortDescription"] = picojson::value(shortDescription); |
106 | | - // rule.fullDescription.text |
107 | | - picojson::object fullDescription; |
108 | | - fullDescription["text"] = picojson::value(""); |
109 | | - rule["fullDescription"] = picojson::value(fullDescription); |
110 | | - // rule.help.text |
111 | | - picojson::object help; |
112 | | - help["text"] = picojson::value(""); |
113 | | - rule["help"] = picojson::value(help); |
114 | | - // rule.properties.precision, rule.properties.problem.severity |
115 | | - picojson::object properties; |
116 | | - properties["precision"] = picojson::value(sarifPrecision(finding)); |
117 | | - // rule.properties.security-severity, rule.properties.tags |
118 | | - picojson::array tags; |
119 | | - |
120 | | - // If we have a CWE ID, treat it as security-related (CWE is the authoritative source for security weaknesses) |
121 | | - if (finding.cwe.id > 0) { |
122 | | - double securitySeverity = 0; |
123 | | - if (finding.severity == Severity::error && !ErrorLogger::isCriticalErrorId(finding.id)) { |
124 | | - securitySeverity = 9.9; // critical = 9.0+ |
125 | | - } |
126 | | - else if (finding.severity == Severity::warning) { |
127 | | - securitySeverity = 8.5; // high = 7.0 to 8.9 |
128 | | - } |
129 | | - else if (finding.severity == Severity::performance || finding.severity == Severity::portability || |
130 | | - finding.severity == Severity::style) { |
131 | | - securitySeverity = 5.5; // medium = 4.0 to 6.9 |
132 | | - } |
133 | | - else if (finding.severity == Severity::information || finding.severity == Severity::internal || |
134 | | - finding.severity == Severity::debug || finding.severity == Severity::none) { |
135 | | - securitySeverity = 2.0; // low = 0.1 to 3.9 |
136 | | - } |
137 | | - if (securitySeverity > 0.0) { |
138 | | - std::ostringstream ss; |
139 | | - ss << securitySeverity; |
140 | | - properties["security-severity"] = picojson::value(ss.str()); |
141 | | - tags.emplace_back("external/cwe/cwe-" + std::to_string(finding.cwe.id)); |
142 | | - tags.emplace_back("security"); |
143 | | - } |
144 | | - } |
145 | | - |
146 | | - // Add tags array if it has any content |
147 | | - if (!tags.empty()) { |
148 | | - properties["tags"] = picojson::value(tags); |
149 | | - } |
150 | | - |
151 | | - // Set problem.severity for use with github |
152 | | - const std::string problemSeverity = sarifSeverity(finding); |
153 | | - properties["problem.severity"] = picojson::value(problemSeverity); |
154 | | - rule["properties"] = picojson::value(properties); |
155 | | - // rule.defaultConfiguration.level |
156 | | - picojson::object defaultConfiguration; |
157 | | - defaultConfiguration["level"] = picojson::value(sarifSeverity(finding)); |
158 | | - rule["defaultConfiguration"] = picojson::value(defaultConfiguration); |
159 | | - |
160 | | - ret.emplace_back(rule); |
161 | | - } |
162 | | - } |
163 | | - return ret; |
164 | | - } |
165 | | - |
166 | | - static picojson::array serializeLocations(const ErrorMessage& finding) { |
167 | | - picojson::array ret; |
168 | | - for (const auto& location : finding.callStack) { |
169 | | - picojson::object physicalLocation; |
170 | | - picojson::object artifactLocation; |
171 | | - artifactLocation["uri"] = picojson::value(location.getfile(false)); |
172 | | - physicalLocation["artifactLocation"] = picojson::value(artifactLocation); |
173 | | - picojson::object region; |
174 | | - region["startLine"] = picojson::value(static_cast<int64_t>(location.line < 1 ? 1 : location.line)); |
175 | | - region["startColumn"] = picojson::value(static_cast<int64_t>(location.column < 1 ? 1 : location.column)); |
176 | | - region["endLine"] = region["startLine"]; |
177 | | - region["endColumn"] = region["startColumn"]; |
178 | | - physicalLocation["region"] = picojson::value(region); |
179 | | - picojson::object loc; |
180 | | - loc["physicalLocation"] = picojson::value(physicalLocation); |
181 | | - ret.emplace_back(loc); |
182 | | - } |
183 | | - return ret; |
184 | | - } |
185 | | - |
186 | | - picojson::array serializeResults() const { |
187 | | - picojson::array results; |
188 | | - for (const auto& finding : mFindings) { |
189 | | - // github only supports findings with locations |
190 | | - if (finding.callStack.empty()) |
191 | | - continue; |
192 | | - picojson::object res; |
193 | | - res["level"] = picojson::value(sarifSeverity(finding)); |
194 | | - res["locations"] = picojson::value(serializeLocations(finding)); |
195 | | - picojson::object message; |
196 | | - message["text"] = picojson::value(finding.shortMessage()); |
197 | | - res["message"] = picojson::value(message); |
198 | | - res["ruleId"] = picojson::value(finding.id); |
199 | | - results.emplace_back(res); |
200 | | - } |
201 | | - return results; |
202 | | - } |
203 | | - |
204 | | - picojson::value serializeRuns(const std::string& productName, const std::string& version) const { |
205 | | - picojson::object driver; |
206 | | - driver["name"] = picojson::value(productName); |
207 | | - driver["semanticVersion"] = picojson::value(version); |
208 | | - driver["informationUri"] = picojson::value("https://cppcheck.sourceforge.io"); |
209 | | - driver["rules"] = picojson::value(serializeRules()); |
210 | | - picojson::object tool; |
211 | | - tool["driver"] = picojson::value(driver); |
212 | | - picojson::object run; |
213 | | - run["tool"] = picojson::value(tool); |
214 | | - run["results"] = picojson::value(serializeResults()); |
215 | | - picojson::array runs{picojson::value(run)}; |
216 | | - return picojson::value(runs); |
217 | | - } |
218 | | - |
219 | | - std::string serialize(std::string productName) const { |
220 | | - const auto nameAndVersion = Settings::getNameAndVersion(productName); |
221 | | - productName = nameAndVersion.first.empty() ? "Cppcheck" : nameAndVersion.first; |
222 | | - std::string version = nameAndVersion.first.empty() ? CppCheck::version() : nameAndVersion.second; |
223 | | - if (version.find(' ') != std::string::npos) |
224 | | - version.erase(version.find(' '), std::string::npos); |
225 | | - |
226 | | - picojson::object doc; |
227 | | - doc["version"] = picojson::value("2.1.0"); |
228 | | - doc["$schema"] = picojson::value("https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json"); |
229 | | - doc["runs"] = serializeRuns(productName, version); |
230 | | - |
231 | | - return picojson::value(doc).serialize(true); |
232 | | - } |
233 | | - |
234 | | - private: |
235 | | - static std::string sarifSeverity(const ErrorMessage& errmsg) { |
236 | | - if (ErrorLogger::isCriticalErrorId(errmsg.id)) |
237 | | - return "error"; |
238 | | - switch (errmsg.severity) { |
239 | | - case Severity::error: |
240 | | - case Severity::warning: |
241 | | - return "error"; |
242 | | - case Severity::style: |
243 | | - case Severity::portability: |
244 | | - case Severity::performance: |
245 | | - return "warning"; |
246 | | - case Severity::information: |
247 | | - case Severity::internal: |
248 | | - case Severity::debug: |
249 | | - case Severity::none: |
250 | | - return "note"; |
251 | | - } |
252 | | - return "note"; |
253 | | - } |
254 | | - |
255 | | - static std::string sarifPrecision(const ErrorMessage& errmsg) { |
256 | | - if (errmsg.certainty == Certainty::inconclusive) |
257 | | - return "medium"; |
258 | | - return "high"; |
259 | | - } |
260 | | - |
261 | | - std::vector<ErrorMessage> mFindings; |
262 | | - }; |
263 | | - |
264 | 82 | class CmdLineLoggerStd : public CmdLineLogger |
265 | 83 | { |
266 | 84 | public: |
|
0 commit comments