Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #13216: Add checkers-report block to xml output #6914

Merged
merged 5 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions cli/cmdlineparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a
return Result::Fail;
{
XMLErrorMessagesLogger xmlLogger;
std::cout << ErrorMessage::getXMLHeader(mSettings.cppcheckCfgProductName);
std::cout << ErrorMessage::getXMLHeader(mSettings.cppcheckCfgProductName, mSettings.xml_version);
CppCheck::getErrorMessages(xmlLogger);
std::cout << ErrorMessage::getXMLFooter() << std::endl;
}
Expand Down Expand Up @@ -1386,9 +1386,9 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a
int tmp;
if (!parseNumberArg(argv[i], 14, tmp))
return Result::Fail;
if (tmp != 2) {
// We only have xml version 2
mLogger.printError("'--xml-version' can only be 2.");
if (tmp != 2 && tmp != 3) {
// We only have xml version 2 and 3
mLogger.printError("'--xml-version' can only be 2 or 3.");
return Result::Fail;
}

Expand Down
9 changes: 7 additions & 2 deletions cli/cppcheckexecutor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ int CppCheckExecutor::check_internal(const Settings& settings) const
stdLogger.resetLatestProgressOutputTime();

if (settings.xml) {
stdLogger.reportErr(ErrorMessage::getXMLHeader(settings.cppcheckCfgProductName));
stdLogger.reportErr(ErrorMessage::getXMLHeader(settings.cppcheckCfgProductName, settings.xml_version));
}

if (!settings.buildDir.empty()) {
Expand Down Expand Up @@ -466,7 +466,7 @@ int CppCheckExecutor::check_internal(const Settings& settings) const
stdLogger.writeCheckersReport();

if (settings.xml) {
stdLogger.reportErr(ErrorMessage::getXMLFooter());
stdLogger.reportErr(ErrorMessage::getXMLFooter(settings.xml_version));
}

if (settings.safety && stdLogger.hasCriticalErrors())
Expand Down Expand Up @@ -512,6 +512,11 @@ void StdLogger::writeCheckersReport()
if (fout.is_open())
fout << checkersReport.getReport(mCriticalErrors);
}

if (mSettings.xml && mSettings.xml_version == 3) {
reportErr(" </errors>\n");
danmar marked this conversation as resolved.
Show resolved Hide resolved
reportErr(checkersReport.getXmlReport(mCriticalErrors));
}
}

#ifdef _WIN32
Expand Down
136 changes: 136 additions & 0 deletions lib/checkersreport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,139 @@ std::string CheckersReport::getReport(const std::string& criticalErrors) const

return fout.str();
}

std::string CheckersReport::getXmlReport(const std::string& criticalErrors) const
{
std::ostringstream fout;

fout << " <critical-errors>";
if (!criticalErrors.empty())
fout << criticalErrors << std::endl;
fout << "</critical-errors>" << std::endl;

fout << " <checkers-report>" << std::endl;
fout << " <open-source-checkers>" << std::endl;
danmar marked this conversation as resolved.
Show resolved Hide resolved

std::size_t maxCheckerSize = 0;
danmar marked this conversation as resolved.
Show resolved Hide resolved
for (const auto& checkReq: checkers::allCheckers) {
const std::string& checker = checkReq.first;
maxCheckerSize = std::max(checker.size(), maxCheckerSize);
}
for (const auto& checkReq: checkers::allCheckers) {
fout << " <checker active=\"";
const std::string& checker = checkReq.first;
const bool active = mActiveCheckers.count(checkReq.first) > 0;
const std::string& req = checkReq.second;
fout << (active ? "Yes" : "No") << "\"" << " id=\"" << checker << "\"";
if (!active && !req.empty())
fout << " require=\"" + req << "\"";
danmar marked this conversation as resolved.
Show resolved Hide resolved
fout << "/>" << std::endl;
}

fout << " </open-source-checkers>" << std::endl;

const bool cppcheckPremium = isCppcheckPremium(mSettings);

auto reportSection = [&fout, cppcheckPremium]
(const std::string& title,
const Settings& settings,
const std::set<std::string>& activeCheckers,
const std::map<std::string, std::string>& premiumCheckers,
const std::string& substring) {
fout << " <" << title << ">";
if (!cppcheckPremium) {
fout << "Not available, Cppcheck Premium is not used</" << title << ">" << std::endl;
return;
}
fout << std::endl;
int maxCheckerSize = 0;
for (const auto& checkReq: premiumCheckers) {
const std::string& checker = checkReq.first;
if (checker.find(substring) != std::string::npos && checker.size() > maxCheckerSize)
maxCheckerSize = checker.size();
}
for (const auto& checkReq: premiumCheckers) {
const std::string& checker = checkReq.first;
if (checker.find(substring) == std::string::npos)
continue;
std::string req = checkReq.second;
bool active = cppcheckPremium && activeCheckers.count(checker) > 0;
if (substring == "::") {
if (req == "warning")
active &= settings.severity.isEnabled(Severity::warning);
else if (req == "style")
active &= settings.severity.isEnabled(Severity::style);
else if (req == "portability")
active &= settings.severity.isEnabled(Severity::portability);
else if (!req.empty())
active = false; // FIXME: handle req
}
fout << " <checker active=\"" << (active ? "Yes" : "No") << "\" id=\"" << checker << "\"";
if (!cppcheckPremium) {
if (!req.empty())
req = "premium," + req;
else
req = "premium";
}
if (!active)
fout << " require=\"" << req << "\"";
fout << "/>" << std::endl;
}
fout << " </" << title << ">" << std::endl;
};

reportSection("premium-checkers", mSettings, mActiveCheckers, checkers::premiumCheckers, "::");
reportSection("autosar", mSettings, mActiveCheckers, checkers::premiumCheckers, "Autosar: ");
reportSection("cert-c", mSettings, mActiveCheckers, checkers::premiumCheckers, "Cert C: ");
reportSection("cert-cpp", mSettings, mActiveCheckers, checkers::premiumCheckers, "Cert C++: ");

int misra = 0;
if (mSettings.premiumArgs.find("misra-c-2012") != std::string::npos)
misra = 2012;
else if (mSettings.premiumArgs.find("misra-c-2023") != std::string::npos)
misra = 2023;
else if (mSettings.addons.count("misra"))
misra = 2012;

if (misra == 0) {
fout << " <misra-c> Misra is not enabled </misra-c>" << std::endl;
} else {
fout << " <misra-c-" << misra << ">" << std::endl;
for (const checkers::MisraInfo& info: checkers::misraC2012Directives) {
const std::string directive = "Dir " + std::to_string(info.a) + "." + std::to_string(info.b);
const bool active = isMisraRuleActive(mActiveCheckers, directive);
fout << " <checker active=\"";
fout << (active ? "Yes" : "No") << "\" id=\"" << "Misra C " << misra << ": " << directive << "\"";
std::string extra;
if (misra == 2012 && info.amendment >= 1)
extra = " amendment=\"" + std::to_string(info.amendment) + "\"";
if (!extra.empty())
fout << extra;
fout << "/>" << std::endl;
}
for (const checkers::MisraInfo& info: checkers::misraC2012Rules) {
const std::string rule = std::to_string(info.a) + "." + std::to_string(info.b);
const bool active = isMisraRuleActive(mActiveCheckers, rule);
fout << " <checker active=\"";
fout << (active ? "Yes" : "No") << "\" id=\"" << "Misra C " << misra << ": " << rule << "\"";
std::string extra;
if (misra == 2012 && info.amendment >= 1)
extra = " amendment=\"" + std::to_string(info.amendment) + "\"";
danmar marked this conversation as resolved.
Show resolved Hide resolved
std::string reqs;
if (info.amendment >= 3)
reqs += "premium";
if (!active && !reqs.empty())
extra += " require=\"" + reqs + "\"";
if (!extra.empty())
fout << extra;
fout << "/>" << std::endl;
}
fout << " </misra-c-" << misra << ">" << std::endl;
}

reportSection("misra-cpp-2008", mSettings, mActiveCheckers, checkers::premiumCheckers, "Misra C++ 2008: ");
reportSection("misra-cpp-2023", mSettings, mActiveCheckers, checkers::premiumCheckers, "Misra C++ 2023: ");

fout << " </checkers-report>";
return fout.str();
}
1 change: 1 addition & 0 deletions lib/checkersreport.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class CPPCHECKLIB CheckersReport {
int getAllCheckersCount();

std::string getReport(const std::string& criticalErrors) const;
std::string getXmlReport(const std::string& criticalErrors) const;

private:
const Settings& mSettings;
Expand Down
8 changes: 4 additions & 4 deletions lib/errorlogger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ void ErrorMessage::deserialize(const std::string &data)
}
}

std::string ErrorMessage::getXMLHeader(std::string productName)
std::string ErrorMessage::getXMLHeader(std::string productName, int _xmlVersion)
danmar marked this conversation as resolved.
Show resolved Hide resolved
{
const auto nameAndVersion = Settings::getNameAndVersion(productName);
productName = nameAndVersion.first;
Expand All @@ -437,7 +437,7 @@ std::string ErrorMessage::getXMLHeader(std::string productName)
// header
printer.OpenElement("results", false);

printer.PushAttribute("version", 2);
printer.PushAttribute("version", _xmlVersion);
printer.OpenElement("cppcheck", false);
if (!productName.empty())
printer.PushAttribute("product-name", productName.c_str());
Expand All @@ -448,9 +448,9 @@ std::string ErrorMessage::getXMLHeader(std::string productName)
return std::string(printer.CStr()) + '>';
}

std::string ErrorMessage::getXMLFooter()
std::string ErrorMessage::getXMLFooter(int _xmlVersion)
{
return " </errors>\n</results>";
return _xmlVersion == 3? "</results>" : " </errors>\n</results>";
}

// There is no utf-8 support around but the strings should at least be safe for to tinyxml2.
Expand Down
4 changes: 2 additions & 2 deletions lib/errorlogger.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ class CPPCHECKLIB ErrorMessage {
*/
std::string toXML() const;

static std::string getXMLHeader(std::string productName);
static std::string getXMLFooter();
static std::string getXMLHeader(std::string productName, int _xmlVersion = 2);
static std::string getXMLFooter(int _xmlVersion = 2);

/**
* Format the error message into a string.
Expand Down
18 changes: 18 additions & 0 deletions test/cli/other_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import sys
import pytest
import json
import xml.etree.ElementTree as ET
danmar marked this conversation as resolved.
Show resolved Hide resolved

from testutils import cppcheck, assert_cppcheck, cppcheck_ex

Expand Down Expand Up @@ -2229,3 +2230,20 @@ def test_ignore_project_2(tmpdir):
exitcode, stdout, _ = cppcheck(args, cwd=tmpdir)
assert exitcode == 1, stdout
assert stdout.splitlines() == lines_exp

def test_xml_checkers_report(tmpdir):
test_file = os.path.join(tmpdir, 'test.c')
test_xml = os.path.join(tmpdir, 'results.xml')
with open(test_file, 'wt') as f:
danmar marked this conversation as resolved.
Show resolved Hide resolved
f.write("""
int main(int argc)
{
}
""")

args = ['--xml-version=3', '--enable=all', '-j2', "--output-file=" + test_xml, test_file]

exitcode, _, stderr = cppcheck(args)
assert exitcode == 0
assert stderr == ""
assert ET.parse(test_xml)
4 changes: 2 additions & 2 deletions test/testcmdlineparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1807,10 +1807,10 @@ class TestCmdlineParser : public TestFixture {

void xmlverunknown() {
REDIRECT;
const char * const argv[] = {"cppcheck", "--xml", "--xml-version=3", "file.cpp"};
const char * const argv[] = {"cppcheck", "--xml", "--xml-version=4", "file.cpp"};
// FAils since unknown XML format version
ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parser->parseFromArgs(4, argv));
ASSERT_EQUALS("cppcheck: error: '--xml-version' can only be 2.\n", logger->str());
ASSERT_EQUALS("cppcheck: error: '--xml-version' can only be 2 or 3.\n", logger->str());
}

void xmlverinvalid() {
Expand Down
Loading