/*
* Cppcheck - A tool for static C/C++ code analysis
* Copyright (C) 2007-2024 Cppcheck team.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "cppcheckexecutor.h"
#include "analyzerinfo.h"
#include "checkersreport.h"
#include "cmdlinelogger.h"
#include "cmdlineparser.h"
#include "color.h"
#include "config.h"
#include "cppcheck.h"
#include "errorlogger.h"
#include "errortypes.h"
#include "filesettings.h"
#include "json.h"
#include "settings.h"
#include "singleexecutor.h"
#include "suppressions.h"
#include "utils.h"
#if defined(HAS_THREADING_MODEL_THREAD)
#include "threadexecutor.h"
#endif
#if defined(HAS_THREADING_MODEL_FORK)
#include "processexecutor.h"
#endif
#include
#include
#include
#include
#include // EXIT_SUCCESS and EXIT_FAILURE
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef USE_UNIX_SIGNAL_HANDLING
#include "signalhandler.h"
#endif
#ifdef USE_WINDOWS_SEH
#include "cppcheckexecutorseh.h"
#endif
#ifdef _WIN32
#include
#endif
#if !defined(WIN32) && !defined(__MINGW32__)
#include // WIFEXITED and friends
#endif
namespace {
class SarifReport {
public:
void addFinding(ErrorMessage msg) {
mFindings.push_back(std::move(msg));
}
picojson::array serializeRules() const {
picojson::array ret;
std::set ruleIds;
for (const auto& finding : mFindings) {
// github only supports findings with locations
if (finding.callStack.empty())
continue;
if (ruleIds.insert(finding.id).second) {
picojson::object rule;
rule["id"] = picojson::value(finding.id);
// rule.shortDescription.text
picojson::object shortDescription;
shortDescription["text"] = picojson::value(finding.shortMessage());
rule["shortDescription"] = picojson::value(shortDescription);
// rule.fullDescription.text
picojson::object fullDescription;
fullDescription["text"] = picojson::value(finding.verboseMessage());
rule["fullDescription"] = picojson::value(fullDescription);
// rule.help.text
picojson::object help;
help["text"] = picojson::value(finding.verboseMessage()); // FIXME provide proper help text
rule["help"] = picojson::value(help);
// rule.properties.precision, rule.properties.problem.severity
picojson::object properties;
properties["precision"] = picojson::value(sarifPrecision(finding));
double securitySeverity = 0;
if (finding.severity == Severity::error && !ErrorLogger::isCriticalErrorId(finding.id))
securitySeverity = 9.9; // We see undefined behavior
//else if (finding.severity == Severity::warning)
// securitySeverity = 5.1; // We see potential undefined behavior
if (securitySeverity > 0.5) {
properties["security-severity"] = picojson::value(securitySeverity);
const picojson::array tags{picojson::value("security")};
properties["tags"] = picojson::value(tags);
}
rule["properties"] = picojson::value(properties);
ret.emplace_back(rule);
}
}
return ret;
}
static picojson::array serializeLocations(const ErrorMessage& finding) {
picojson::array ret;
for (const auto& location : finding.callStack) {
picojson::object physicalLocation;
picojson::object artifactLocation;
artifactLocation["uri"] = picojson::value(location.getfile(false));
physicalLocation["artifactLocation"] = picojson::value(artifactLocation);
picojson::object region;
region["startLine"] = picojson::value(static_cast(location.line));
region["startColumn"] = picojson::value(static_cast(location.column));
region["endLine"] = region["startLine"];
region["endColumn"] = region["startColumn"];
physicalLocation["region"] = picojson::value(region);
picojson::object loc;
loc["physicalLocation"] = picojson::value(physicalLocation);
ret.emplace_back(loc);
}
return ret;
}
picojson::array serializeResults() const {
picojson::array results;
for (const auto& finding : mFindings) {
// github only supports findings with locations
if (finding.callStack.empty())
continue;
picojson::object res;
res["level"] = picojson::value(sarifSeverity(finding));
res["locations"] = picojson::value(serializeLocations(finding));
picojson::object message;
message["text"] = picojson::value(finding.shortMessage());
res["message"] = picojson::value(message);
res["ruleId"] = picojson::value(finding.id);
results.emplace_back(res);
}
return results;
}
picojson::value serializeRuns(const std::string& productName, const std::string& version) const {
picojson::object driver;
driver["name"] = picojson::value(productName);
driver["semanticVersion"] = picojson::value(version);
driver["informationUri"] = picojson::value("https://cppcheck.sourceforge.io");
driver["rules"] = picojson::value(serializeRules());
picojson::object tool;
tool["driver"] = picojson::value(driver);
picojson::object run;
run["tool"] = picojson::value(tool);
run["results"] = picojson::value(serializeResults());
picojson::array runs{picojson::value(run)};
return picojson::value(runs);
}
std::string serialize(std::string productName) const {
const auto nameAndVersion = Settings::getNameAndVersion(productName);
productName = nameAndVersion.first.empty() ? "Cppcheck" : nameAndVersion.first;
std::string version = nameAndVersion.first.empty() ? CppCheck::version() : nameAndVersion.second;
if (version.find(' ') != std::string::npos)
version.erase(version.find(' '), std::string::npos);
picojson::object doc;
doc["version"] = picojson::value("2.1.0");
doc["$schema"] = picojson::value("https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json");
doc["runs"] = serializeRuns(productName, version);
return picojson::value(doc).serialize(true);
}
private:
static std::string sarifSeverity(const ErrorMessage& errmsg) {
if (ErrorLogger::isCriticalErrorId(errmsg.id))
return "error";
switch (errmsg.severity) {
case Severity::error:
case Severity::warning:
case Severity::style:
case Severity::portability:
case Severity::performance:
return "warning";
case Severity::information:
case Severity::internal:
case Severity::debug:
case Severity::none:
return "note";
}
return "note";
}
static std::string sarifPrecision(const ErrorMessage& errmsg) {
if (errmsg.certainty == Certainty::inconclusive)
return "medium";
return "high";
}
std::vector mFindings;
};
class CmdLineLoggerStd : public CmdLineLogger
{
public:
CmdLineLoggerStd() = default;
void printMessage(const std::string &message) override
{
printRaw("cppcheck: " + message);
}
void printError(const std::string &message) override
{
printMessage("error: " + message);
}
void printRaw(const std::string &message) override
{
std::cout = 0)
stdLogger.resetLatestProgressOutputTime();
if (settings.xml) {
stdLogger.reportErr(ErrorMessage::getXMLHeader(settings.cppcheckCfgProductName, settings.xml_version));
}
if (!settings.buildDir.empty()) {
std::list fileNames;
for (std::list::const_iterator i = mFiles.cbegin(); i != mFiles.cend(); ++i)
fileNames.emplace_back(i->path());
AnalyzerInformation::writeFilesTxt(settings.buildDir, fileNames, settings.userDefines, mFileSettings);
}
if (!settings.checkersReportFilename.empty())
std::remove(settings.checkersReportFilename.c_str());
CppCheck cppcheck(stdLogger, true, executeCommand);
cppcheck.settings() = settings; // this is a copy
auto& suppressions = cppcheck.settings().supprs.nomsg;
unsigned int returnValue = 0;
if (settings.useSingleJob()) {
// Single process
SingleExecutor executor(cppcheck, mFiles, mFileSettings, settings, suppressions, stdLogger);
returnValue = executor.check();
} else {
#if defined(HAS_THREADING_MODEL_THREAD)
if (settings.executor == Settings::ExecutorType::Thread) {
ThreadExecutor executor(mFiles, mFileSettings, settings, suppressions, stdLogger, CppCheckExecutor::executeCommand);
returnValue = executor.check();
}
#endif
#if defined(HAS_THREADING_MODEL_FORK)
if (settings.executor == Settings::ExecutorType::Process) {
ProcessExecutor executor(mFiles, mFileSettings, settings, suppressions, stdLogger, CppCheckExecutor::executeCommand);
returnValue = executor.check();
}
#endif
}
returnValue |= cppcheck.analyseWholeProgram(settings.buildDir, mFiles, mFileSettings, stdLogger.getCtuInfo());
if (settings.severity.isEnabled(Severity::information) || settings.checkConfiguration) {
const bool err = reportSuppressions(settings, suppressions, settings.checks.isEnabled(Checks::unusedFunction), mFiles, mFileSettings, stdLogger);
if (err && returnValue == 0)
returnValue = settings.exitCode;
}
if (!settings.checkConfiguration) {
cppcheck.tooManyConfigsError(emptyString,0U);
}
stdLogger.writeCheckersReport();
if (settings.xml) {
stdLogger.reportErr(ErrorMessage::getXMLFooter(settings.xml_version));
}
if (settings.safety && stdLogger.hasCriticalErrors())
return EXIT_FAILURE;
if (returnValue)
return settings.exitCode;
return EXIT_SUCCESS;
}
void StdLogger::writeCheckersReport()
{
const bool summary = mSettings.safety || mSettings.severity.isEnabled(Severity::information);
const bool xmlReport = mSettings.xml && mSettings.xml_version == 3;
const bool textReport = !mSettings.checkersReportFilename.empty();
if (!summary && !xmlReport && !textReport)
return;
CheckersReport checkersReport(mSettings, mActiveCheckers);
const auto& suppressions = mSettings.supprs.nomsg.getSuppressions();
const bool summarySuppressed = std::any_of(suppressions.cbegin(), suppressions.cend(), [](const SuppressionList::Suppression& s) {
return s.errorId == "checkersReport";
});
if (summary && !summarySuppressed) {
ErrorMessage msg;
msg.severity = Severity::information;
msg.id = "checkersReport";
const int activeCheckers = checkersReport.getActiveCheckersCount();
const int totalCheckers = checkersReport.getAllCheckersCount();
std::string what;
if (mCriticalErrors.empty())
what = std::to_string(activeCheckers) + "/" + std::to_string(totalCheckers);
else
what = "There was critical errors";
if (!xmlReport && !textReport)
what += " (use --checkers-report= to see details)";
msg.setmsg("Active checkers: " + what);
reportErr(msg);
}
if (textReport) {
std::ofstream fout(mSettings.checkersReportFilename);
if (fout.is_open())
fout