diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index be7b227333c..754a38ea9f6 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -505,6 +505,11 @@ jobs: runs-on: ubuntu-22.04 # run on the latest image only + permissions: + # required by upload-sarif action + # https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/uploading-a-sarif-file-to-github + security-events: write + steps: - uses: actions/checkout@v4 @@ -549,7 +554,11 @@ jobs: if [ $ec -eq 1 ]; then exit $ec fi - + + # test sarif output + ./cppcheck --enable=style --error-exitcode=0 --platform=unix64 --inconclusive samples/incorrectLogicOperator/bad.c --output-file=samples.sarif --output-format=sarif + cat samples.sarif + # self check simplecpp ./cppcheck $selfcheck_options externals/simplecpp || ec=1 # self check lib/cli @@ -564,3 +573,12 @@ jobs: # triage ./cppcheck $selfcheck_options $cppcheck_options -DQ_MOC_OUTPUT_REVISION=68 -DQT_CHARTS_LIB --library=qt -Icmake.output/tools/triage -Igui tools/triage/*.cpp cmake.output/tools/triage || ec=1 exit $ec + + - name: Upload SARIF file + uses: github/codeql-action/upload-sarif@v3 + with: + # Path to SARIF file relative to the root of the repository + sarif_file: samples.sarif + # Optional category for the results + # Used to differentiate multiple results for one commit + category: samples diff --git a/Makefile b/Makefile index 827f5233221..afd4da64213 100644 --- a/Makefile +++ b/Makefile @@ -173,7 +173,7 @@ ifndef INCLUDE_FOR_LIB endif ifndef INCLUDE_FOR_CLI - INCLUDE_FOR_CLI=-Ilib -isystem externals/simplecpp -isystem externals/tinyxml2 + INCLUDE_FOR_CLI=-Ilib -isystem externals/picojson -isystem externals/simplecpp -isystem externals/tinyxml2 endif ifndef INCLUDE_FOR_TEST @@ -762,7 +762,7 @@ $(libcppdir)/vfvalue.o: lib/vfvalue.cpp lib/config.h lib/errortypes.h lib/mathli cli/cmdlineparser.o: cli/cmdlineparser.cpp cli/cmdlinelogger.h cli/cmdlineparser.h cli/cppcheckexecutor.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 $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/cmdlineparser.cpp -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 +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 $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/cppcheckexecutor.cpp 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 diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt index 51e9aee6718..c05743becb3 100644 --- a/cli/CMakeLists.txt +++ b/cli/CMakeLists.txt @@ -12,6 +12,7 @@ if (BUILD_CLI) else() target_include_directories(cli_objs SYSTEM PRIVATE ${tinyxml2_INCLUDE_DIRS}) endif() + target_externals_include_directories(cli_objs PRIVATE ${PROJECT_SOURCE_DIR}/externals/picojson/) target_externals_include_directories(cli_objs PRIVATE ${PROJECT_SOURCE_DIR}/externals/simplecpp/) if (NOT CMAKE_DISABLE_PRECOMPILE_HEADERS) target_precompile_headers(cli_objs PRIVATE precompiled.h) diff --git a/cli/cli.vcxproj b/cli/cli.vcxproj index f532fa46e7d..a508d3bf55b 100644 --- a/cli/cli.vcxproj +++ b/cli/cli.vcxproj @@ -85,7 +85,7 @@ - ..\lib;..\externals;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories) + ..\lib;..\externals;..\externals\picojson;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories) true ProgramDatabase Disabled @@ -114,7 +114,7 @@ - ..\lib;..\externals;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories) + ..\lib;..\externals;..\externals\picojson;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories) true ProgramDatabase Disabled @@ -143,7 +143,7 @@ - ..\lib;..\externals;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories) + ..\lib;..\externals;..\externals\picojson;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories) false MaxSpeed CPPCHECKLIB_IMPORT;TINYXML2_IMPORT;NDEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;_WIN64;%(PreprocessorDefinitions) @@ -181,7 +181,7 @@ - ..\lib;..\externals;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories) + ..\lib;..\externals;..\externals\picojson;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories) false MaxSpeed CPPCHECKLIB_IMPORT;TINYXML2_IMPORT;NDEBUG;WIN32;HAVE_RULES;_CRT_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;_WIN64;%(PreprocessorDefinitions) diff --git a/cli/cmdlineparser.cpp b/cli/cmdlineparser.cpp index 657bca9f5b6..ae15175e7e5 100644 --- a/cli/cmdlineparser.cpp +++ b/cli/cmdlineparser.cpp @@ -917,6 +917,20 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a else if (std::strncmp(argv[i], "--output-file=", 14) == 0) mSettings.outputFile = Path::simplifyPath(argv[i] + 14); + else if (std::strncmp(argv[i], "--output-format=", 16) == 0) { + const std::string format = argv[i] + 16; + if (format == "sarif") + mSettings.outputFormat = Settings::OutputFormat::sarif; + else if (format == "xml") + mSettings.outputFormat = Settings::OutputFormat::xml; + else { + mLogger.printError("argument to '--output-format=' must be 'sarif' or 'xml'."); + return Result::Fail; + } + mSettings.xml = (mSettings.outputFormat == Settings::OutputFormat::xml); + } + + // Experimental: limit execution time for extended valueflow analysis. basic valueflow analysis // is always executed. else if (std::strncmp(argv[i], "--performance-valueflow-max-time=", 33) == 0) { @@ -951,6 +965,7 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a // Write results in results.plist else if (std::strncmp(argv[i], "--plist-output=", 15) == 0) { + mSettings.outputFormat = Settings::OutputFormat::plist; mSettings.plistOutput = Path::simplifyPath(argv[i] + 15); if (mSettings.plistOutput.empty()) mSettings.plistOutput = "."; @@ -1362,12 +1377,11 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a else if (std::strcmp(argv[i], "-v") == 0 || std::strcmp(argv[i], "--verbose") == 0) mSettings.verbose = true; - else if (std::strcmp(argv[i], "--sarif") == 0) - mSettings.sarif = true; - // Write results in results.xml - else if (std::strcmp(argv[i], "--xml") == 0) + else if (std::strcmp(argv[i], "--xml") == 0) { mSettings.xml = true; + mSettings.outputFormat = Settings::OutputFormat::xml; + } // Define the XML file version (and enable XML output) else if (std::strncmp(argv[i], "--xml-version=", 14) == 0) { @@ -1383,6 +1397,7 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a mSettings.xml_version = tmp; // Enable also XML if version is set mSettings.xml = true; + mSettings.outputFormat = Settings::OutputFormat::xml; } else { diff --git a/cli/cppcheckexecutor.cpp b/cli/cppcheckexecutor.cpp index cac123b82f6..a984d6d4b06 100644 --- a/cli/cppcheckexecutor.cpp +++ b/cli/cppcheckexecutor.cpp @@ -28,14 +28,12 @@ #include "errorlogger.h" #include "errortypes.h" #include "filesettings.h" +#include "json.h" #include "settings.h" #include "singleexecutor.h" #include "suppressions.h" #include "utils.h" -#define PICOJSON_USE_INT64 -#include "../externals/picojson/picojson.h" - #if defined(HAS_THREADING_MODEL_THREAD) #include "threadexecutor.h" #endif @@ -90,13 +88,13 @@ namespace { picojson::object shortDescription; shortDescription["text"] = picojson::value(finding.shortMessage()); rule["shortDescription"] = picojson::value(shortDescription); - ret.push_back(picojson::value(rule)); + ret.emplace_back(rule); } } return ret; } - picojson::array serializeLocations(const ErrorMessage& finding) const { + static picojson::array serializeLocations(const ErrorMessage& finding) { picojson::array ret; for (const auto& location : finding.callStack) { picojson::object physicalLocation; @@ -109,7 +107,7 @@ namespace { physicalLocation["region"] = picojson::value(region); picojson::object loc; loc["physicalLocation"] = picojson::value(physicalLocation); - ret.push_back(picojson::value(loc)); + ret.emplace_back(loc); } return ret; } @@ -125,12 +123,12 @@ namespace { message["text"] = picojson::value(finding.shortMessage()); res["message"] = picojson::value(message); res["ruleId"] = picojson::value(finding.id); - results.push_back(picojson::value(res)); + results.emplace_back(res); } return results; } - picojson::value serializeRuns(std::string productName, std::string version) const { + picojson::value serializeRuns(const std::string& productName, const std::string& version) const { picojson::object driver; driver["name"] = picojson::value(productName); driver["version"] = picojson::value(version); @@ -216,8 +214,8 @@ namespace { } ~StdLogger() override { - if (mSettings.sarif) { - std::cerr << mSarifReport.serialize(mSettings.cppcheckCfgProductName); + if (mSettings.outputFormat == Settings::OutputFormat::sarif) { + reportErr(mSarifReport.serialize(mSettings.cppcheckCfgProductName)); } delete mErrorOutput; } @@ -564,7 +562,7 @@ void StdLogger::reportErr(const ErrorMessage &msg) if (!mShownErrors.insert(msg.toString(mSettings.verbose)).second) return; - if (mSettings.sarif) + if (mSettings.outputFormat == Settings::OutputFormat::sarif) mSarifReport.addFinding(msg); else if (mSettings.xml) reportErr(msg.toXML()); diff --git a/lib/settings.h b/lib/settings.h index 790e3549e3d..11ea5e502fa 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -271,6 +271,9 @@ class CPPCHECKLIB WARN_UNUSED Settings { /** @brief write results (--output-file=<file>) */ std::string outputFile; + enum class OutputFormat : std::uint8_t {text, plist, sarif, xml}; + OutputFormat outputFormat = OutputFormat::text; + Platform platform; /** @brief pid of cppcheck. Intention is that this is set in the main process. */ @@ -441,9 +444,6 @@ class CPPCHECKLIB WARN_UNUSED Settings { /** @brief Is --verbose given? */ bool verbose{}; - /** @brief write SARIF results (--sarif) */ - bool sarif{}; - /** @brief write XML results (--xml) */ bool xml{}; diff --git a/releasenotes.txt b/releasenotes.txt index 8c0edafd07f..ffee14f39fd 100644 --- a/releasenotes.txt +++ b/releasenotes.txt @@ -10,6 +10,8 @@ GUI: - Changed interface: +- SARIF output. Use --output-format=sarif to activate this. +- Add option --output-format=. Allowed formats are sarif and xml. - Deprecations: diff --git a/samples/incorrectLogicOperator/bad.c b/samples/incorrectLogicOperator/bad.c new file mode 100644 index 00000000000..11daaf4e4ce --- /dev/null +++ b/samples/incorrectLogicOperator/bad.c @@ -0,0 +1,6 @@ + +void foo(int x) { + if (x >= 0 || x <= 10) {} +} + +dummy=foo(); diff --git a/samples/incorrectLogicOperator/good.c b/samples/incorrectLogicOperator/good.c new file mode 100644 index 00000000000..a2d122fb128 --- /dev/null +++ b/samples/incorrectLogicOperator/good.c @@ -0,0 +1,6 @@ + +void foo(int x) { + if (x >= 0 && x <= 10) {} +} + +dummy=foo(); diff --git a/samples/incorrectLogicOperator/out.txt b/samples/incorrectLogicOperator/out.txt new file mode 100644 index 00000000000..cbd29f23fc2 --- /dev/null +++ b/samples/incorrectLogicOperator/out.txt @@ -0,0 +1,3 @@ +samples/incorrectLogicOperator/bad.c:3:16: warning: Logical disjunction always evaluates to true: x >= 0 || x <= 10. [incorrectLogicOperator] + if (x >= 0 || x <= 10) {} + ^ diff --git a/test/cli/helloworld_test.py b/test/cli/helloworld_test.py index fafb9d562d5..d74d02be414 100644 --- a/test/cli/helloworld_test.py +++ b/test/cli/helloworld_test.py @@ -4,6 +4,7 @@ import os import re import glob +import json from testutils import create_gui_project_file, cppcheck @@ -319,3 +320,17 @@ def test_missing_include_system(): # #11283 _, _, stderr = cppcheck(args, cwd=__script_dir) assert stderr.replace('\\', '/') == 'helloworld/main.c:1:0: information: Include file: not found. Please note: Cppcheck does not need standard library headers to get proper results. [missingIncludeSystem]\n' + + +def test_sarif(): + args = [ + '--output-format=sarif', + 'helloworld' + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + res = json.loads(stderr) + assert res['version'] == '2.1.0' + assert res['runs'][0]['results'][0]['locations'][0]['physicalLocation']['artifactLocation']['uri'] == 'helloworld/main.c' + assert res['runs'][0]['results'][0]['ruleId'] == 'zerodiv' + assert res['runs'][0]['results'][0]['message']['text'] == 'Division by zero.' diff --git a/test/testcmdlineparser.cpp b/test/testcmdlineparser.cpp index 08dbe16b4db..8ba6017c223 100644 --- a/test/testcmdlineparser.cpp +++ b/test/testcmdlineparser.cpp @@ -208,6 +208,11 @@ class TestCmdlineParser : public TestFixture { TEST_CASE(maxConfigsMissingCount); TEST_CASE(maxConfigsInvalid); TEST_CASE(maxConfigsTooSmall); + TEST_CASE(outputFormatSarif); + TEST_CASE(outputFormatXml); + TEST_CASE(outputFormatOther); + TEST_CASE(outputFormatImplicitPlist); + TEST_CASE(outputFormatImplicitXml); TEST_CASE(premiumOptions1); TEST_CASE(premiumOptions2); TEST_CASE(premiumOptions3); @@ -1247,6 +1252,41 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS("cppcheck: error: argument to '--max-configs=' must be greater than 0.\n", logger->str()); } + void outputFormatSarif() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--output-format=sarif", "file.cpp"}; + ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); + ASSERT_EQUALS_ENUM(Settings::OutputFormat::sarif, settings->outputFormat); + } + + void outputFormatXml() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--output-format=xml", "file.cpp"}; + ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); + ASSERT_EQUALS_ENUM(Settings::OutputFormat::xml, settings->outputFormat); + } + + void outputFormatOther() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--output-format=text", "file.cpp"}; + ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parser->parseFromArgs(3, argv)); + ASSERT_EQUALS("cppcheck: error: argument to '--output-format=' must be 'sarif' or 'xml'.\n", logger->str()); + } + + void outputFormatImplicitPlist() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--plist-output=.", "file.cpp"}; + ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); + ASSERT_EQUALS_ENUM(Settings::OutputFormat::plist, settings->outputFormat); + } + + void outputFormatImplicitXml() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--xml", "file.cpp"}; + ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); + ASSERT_EQUALS_ENUM(Settings::OutputFormat::xml, settings->outputFormat); + } + void premiumOptions1() { REDIRECT; asPremium(); diff --git a/test/testrunner.vcxproj b/test/testrunner.vcxproj index 5951a2770ba..89ab1d43a4c 100755 --- a/test/testrunner.vcxproj +++ b/test/testrunner.vcxproj @@ -197,7 +197,7 @@ - ..\cli;..\lib;..\externals;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories) + ..\cli;..\lib;..\externals;..\externals\picojson;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories) true ProgramDatabase Disabled @@ -226,7 +226,7 @@ - ..\cli;..\lib;..\externals;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories) + ..\cli;..\lib;..\externals;..\externals\picojson;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories) true ProgramDatabase Disabled @@ -255,7 +255,7 @@ - ..\cli;..\lib;..\externals;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories) + ..\cli;..\lib;..\externals;..\externals\picojson;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories) false MaxSpeed CPPCHECKLIB_IMPORT;SIMPLECPP_IMPORT;NDEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;_WIN64;%(PreprocessorDefinitions) @@ -295,7 +295,7 @@ - ..\cli;..\lib;..\externals;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories) + ..\cli;..\lib;..\externals;..\externals\picojson;..\externals\simplecpp;..\externals\tinyxml2;%(AdditionalIncludeDirectories) false MaxSpeed CPPCHECKLIB_IMPORT;SIMPLECPP_IMPORT;NDEBUG;WIN32;HAVE_RULES;_CRT_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;_WIN64;%(PreprocessorDefinitions) diff --git a/tools/dmake/dmake.cpp b/tools/dmake/dmake.cpp index 3b50040779b..6e11eff7caa 100644 --- a/tools/dmake/dmake.cpp +++ b/tools/dmake/dmake.cpp @@ -754,7 +754,7 @@ int main(int argc, char **argv) makeConditionalVariable(fout, "PREFIX", "/usr"); makeConditionalVariable(fout, "INCLUDE_FOR_LIB", "-Ilib -isystem externals -isystem externals/picojson -isystem externals/simplecpp -isystem externals/tinyxml2"); - makeConditionalVariable(fout, "INCLUDE_FOR_CLI", "-Ilib -isystem externals/simplecpp -isystem externals/tinyxml2"); + makeConditionalVariable(fout, "INCLUDE_FOR_CLI", "-Ilib -isystem externals/picojson -isystem externals/simplecpp -isystem externals/tinyxml2"); makeConditionalVariable(fout, "INCLUDE_FOR_TEST", "-Ilib -Icli -isystem externals/simplecpp -isystem externals/tinyxml2"); fout << "BIN=$(DESTDIR)$(PREFIX)/bin\n\n";