From 7a36c015b5e2b8e4b6c95670b64752a1ea8ab19f Mon Sep 17 00:00:00 2001 From: firewave Date: Thu, 8 Jan 2026 19:20:31 +0100 Subject: [PATCH 1/3] improved errorhandling of analyzer information loading --- cli/cmdlineparser.cpp | 3 + lib/analyzerinfo.cpp | 130 ++++++++++++++++++++++++++--------- lib/analyzerinfo.h | 9 ++- lib/checkunusedfunctions.cpp | 7 +- lib/cppcheck.cpp | 21 +++--- lib/errorlogger.cpp | 1 + lib/settings.h | 3 + test/cli/other_test.py | 100 +++++++++++++++++++++++++++ test/testcmdlineparser.cpp | 8 +++ 9 files changed, 238 insertions(+), 44 deletions(-) diff --git a/cli/cmdlineparser.cpp b/cli/cmdlineparser.cpp index 7c18e428023..c017e238409 100644 --- a/cli/cmdlineparser.cpp +++ b/cli/cmdlineparser.cpp @@ -621,6 +621,9 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a mSettings.cppHeaderProbe = true; } + else if (std::strcmp(argv[i], "--debug-analyzerinfo") == 0) + mSettings.debugainfo = true; + else if (std::strcmp(argv[i], "--debug-ast") == 0) mSettings.debugast = true; diff --git a/lib/analyzerinfo.cpp b/lib/analyzerinfo.cpp index ea6c414b7b0..d484f7d50a9 100644 --- a/lib/analyzerinfo.cpp +++ b/lib/analyzerinfo.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -82,29 +83,56 @@ void AnalyzerInformation::close() } } -bool AnalyzerInformation::skipAnalysis(const tinyxml2::XMLDocument &analyzerInfoDoc, std::size_t hash, std::list &errors) +bool AnalyzerInformation::skipAnalysis(const tinyxml2::XMLDocument &analyzerInfoDoc, std::size_t hash, std::list &errors, bool debug) { const tinyxml2::XMLElement * const rootNode = analyzerInfoDoc.FirstChildElement(); - if (rootNode == nullptr) + if (rootNode == nullptr) { + if (debug) + std::cout << "discarding cached result - no root node found" << std::endl; return false; + } - const char *attr = rootNode->Attribute("hash"); - if (!attr || attr != std::to_string(hash)) + if (strcmp(rootNode->Name(), "analyzerinfo") != 0) { + if (debug) + std::cout << "discarding cached result - unexpected root node" << std::endl; return false; + } - // Check for invalid license error or internal error, in which case we should retry analysis - for (const tinyxml2::XMLElement *e = rootNode->FirstChildElement(); e; e = e->NextSiblingElement()) { - if (std::strcmp(e->Name(), "error") == 0 && - (e->Attribute("id", "premium-invalidLicense") || - e->Attribute("id", "premium-internalError") || - e->Attribute("id", "internalError") - )) - return false; + const char * const attr = rootNode->Attribute("hash"); + if (!attr) { + if (debug) + std::cout << "discarding cached result - no 'hash' attribute found" << std::endl; + return false; + } + if (attr != std::to_string(hash)) { + if (debug) + std::cout << "discarding cached result - hash mismatch" << std::endl; + return false; } for (const tinyxml2::XMLElement *e = rootNode->FirstChildElement(); e; e = e->NextSiblingElement()) { - if (std::strcmp(e->Name(), "error") == 0) - errors.emplace_back(e); + if (std::strcmp(e->Name(), "error") != 0) + continue; + + // TODO: discarding results on internalError doesn't make sense since that won't fix itself + // Check for invalid license error or internal error, in which case we should retry analysis + static const std::array s_ids{ + "premium-invalidLicense", + "premium-internalError", + "internalError" + }; + for (const auto* id : s_ids) + { + // cppcheck-suppress useStlAlgorithm + if (e->Attribute("id", id)) { + if (debug) + std::cout << "discarding cached result - '" << id << "' encountered" << std::endl; + errors.clear(); + return false; + } + } + + errors.emplace_back(e); } return true; @@ -138,27 +166,43 @@ std::string AnalyzerInformation::getAnalyzerInfoFile(const std::string &buildDir filename = sourcefile; else filename = sourcefile.substr(pos + 1); + // TODO: is this correct? the above code will return files ending in '.aN'. Also does not consider the ID return Path::join(buildDir, std::move(filename)) + ".analyzerinfo"; } -bool AnalyzerInformation::analyzeFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, std::size_t fsFileId, std::size_t hash, std::list &errors) +bool AnalyzerInformation::analyzeFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, std::size_t fsFileId, std::size_t hash, std::list &errors, bool debug) { + if (mOutputStream.is_open()) + throw std::runtime_error("analyzer information file is already open"); + if (buildDir.empty() || sourcefile.empty()) return true; - close(); const std::string analyzerInfoFile = AnalyzerInformation::getAnalyzerInfoFile(buildDir,sourcefile,cfg,fsFileId); - tinyxml2::XMLDocument analyzerInfoDoc; - const tinyxml2::XMLError xmlError = analyzerInfoDoc.LoadFile(analyzerInfoFile.c_str()); - if (xmlError == tinyxml2::XML_SUCCESS && skipAnalysis(analyzerInfoDoc, hash, errors)) - return false; + { + tinyxml2::XMLDocument analyzerInfoDoc; + const tinyxml2::XMLError xmlError = analyzerInfoDoc.LoadFile(analyzerInfoFile.c_str()); + if (xmlError == tinyxml2::XML_SUCCESS) { + if (skipAnalysis(analyzerInfoDoc, hash, errors, debug)) { + if (debug) + std::cout << "skipping analysis - loaded " << errors.size() << " cached finding(s) from '" << analyzerInfoFile << "'" << std::endl; + return false; + } + } + else if (xmlError != tinyxml2::XML_ERROR_FILE_NOT_FOUND) { + if (debug) + std::cout << "discarding cached result - failed to load '" << analyzerInfoFile << "' (" << tinyxml2::XMLDocument::ErrorIDToName(xmlError) << ")" << std::endl; + } + else if (debug) + std::cout << "no cached result '" << analyzerInfoFile << "' found" << std::endl; + } mOutputStream.open(analyzerInfoFile); - if (mOutputStream.is_open()) { - mOutputStream << "\n"; - mOutputStream << "\n"; - } + if (!mOutputStream.is_open()) + throw std::runtime_error("failed to open '" + analyzerInfoFile + "'"); + mOutputStream << "\n"; + mOutputStream << "\n"; return true; } @@ -175,6 +219,7 @@ void AnalyzerInformation::setFileInfo(const std::string &check, const std::strin mOutputStream << " \n" << fileInfo << " \n"; } +// TODO: report detailed errors? bool AnalyzerInformation::Info::parse(const std::string& filesTxtLine) { const std::string::size_type sep1 = filesTxtLine.find(sep); if (sep1 == std::string::npos) @@ -202,37 +247,58 @@ bool AnalyzerInformation::Info::parse(const std::string& filesTxtLine) { return true; } -// TODO: bail out on unexpected data -void AnalyzerInformation::processFilesTxt(const std::string& buildDir, const std::function& handler) +std::string AnalyzerInformation::processFilesTxt(const std::string& buildDir, const std::function& handler, bool debug) { const std::string filesTxt(buildDir + "/files.txt"); std::ifstream fin(filesTxt.c_str()); std::string filesTxtLine; while (std::getline(fin, filesTxtLine)) { AnalyzerInformation::Info filesTxtInfo; - if (!filesTxtInfo.parse(filesTxtLine)) { - return; - } + if (!filesTxtInfo.parse(filesTxtLine)) + return "failed to parse '" + filesTxtLine + "' from '" + filesTxt + "'"; + + if (filesTxtInfo.afile.empty()) + return "empty afile from '" + filesTxt + "'"; const std::string xmlfile = buildDir + '/' + filesTxtInfo.afile; tinyxml2::XMLDocument doc; const tinyxml2::XMLError error = doc.LoadFile(xmlfile.c_str()); + if (error == tinyxml2::XML_ERROR_FILE_NOT_FOUND) { + /* FIXME: this can currently not be reported as an error because: + * - --clang does not generate any analyzer information - see #14456 + * - markup files might not generate analyzer information + * - files with preprocessor errors might not generate analyzer information + */ + if (debug) + std::cout << "'" + xmlfile + "' from '" + filesTxt + "' not found"; + continue; + } + if (error != tinyxml2::XML_SUCCESS) - return; + return "failed to load '" + xmlfile + "' from '" + filesTxt + "'"; const tinyxml2::XMLElement * const rootNode = doc.FirstChildElement(); if (rootNode == nullptr) - return; + return "no root node found in '" + xmlfile + "' from '" + filesTxt + "'"; + + if (strcmp(rootNode->Name(), "analyzerinfo") != 0) + return "unexpected root node in '" + xmlfile + "' from '" + filesTxt + "'"; for (const tinyxml2::XMLElement *e = rootNode->FirstChildElement(); e; e = e->NextSiblingElement()) { if (std::strcmp(e->Name(), "FileInfo") != 0) continue; const char *checkattr = e->Attribute("check"); - if (checkattr == nullptr) + if (checkattr == nullptr) { + if (debug) + std::cout << "'check' attribute missing in 'FileInfo' in '" << xmlfile << "' from '" << filesTxt + "'"; continue; + } handler(checkattr, e, filesTxtInfo); } } + + // TODO: error on empty file? + return ""; } diff --git a/lib/analyzerinfo.h b/lib/analyzerinfo.h index 1547ef50f16..5435be62560 100644 --- a/lib/analyzerinfo.h +++ b/lib/analyzerinfo.h @@ -61,7 +61,10 @@ class CPPCHECKLIB AnalyzerInformation { /** Close current TU.analyzerinfo file */ void close(); - bool analyzeFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, std::size_t fsFileId, std::size_t hash, std::list &errors); + /** + * @throws std::runtime_error thrown if the output file is already open or the output file cannot be opened + */ + bool analyzeFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, std::size_t fsFileId, std::size_t hash, std::list &errors, bool debug = false); void reportErr(const ErrorMessage &msg); void setFileInfo(const std::string &check, const std::string &fileInfo); static std::string getAnalyzerInfoFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, std::size_t fsFileId); @@ -77,14 +80,14 @@ class CPPCHECKLIB AnalyzerInformation { std::string sourceFile; }; - static void processFilesTxt(const std::string& buildDir, const std::function& handler); + static std::string processFilesTxt(const std::string& buildDir, const std::function& handler, bool debug = false); protected: static std::string getFilesTxt(const std::list &sourcefiles, const std::list &fileSettings); static std::string getAnalyzerInfoFileFromFilesTxt(std::istream& filesTxt, const std::string &sourcefile, const std::string &cfg, int fsFileId); - static bool skipAnalysis(const tinyxml2::XMLDocument &analyzerInfoDoc, std::size_t hash, std::list &errors); + static bool skipAnalysis(const tinyxml2::XMLDocument &analyzerInfoDoc, std::size_t hash, std::list &errors, bool debug = false); private: std::ofstream mOutputStream; diff --git a/lib/checkunusedfunctions.cpp b/lib/checkunusedfunctions.cpp index f6daec51f20..8f7c3ab3812 100644 --- a/lib/checkunusedfunctions.cpp +++ b/lib/checkunusedfunctions.cpp @@ -482,7 +482,12 @@ void CheckUnusedFunctions::analyseWholeProgram(const Settings &settings, ErrorLo } }; - AnalyzerInformation::processFilesTxt(buildDir, handler); + const std::string err = AnalyzerInformation::processFilesTxt(buildDir, handler, settings.debugainfo); + if (!err.empty()) { + const ErrorMessage errmsg({}, "", Severity::error, err, "internalError", Certainty::normal); + errorLogger.reportErr(errmsg); + return; + } for (auto decl = decls.cbegin(); decl != decls.cend(); ++decl) { const std::string &functionName = stripTemplateParameters(decl->first); diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index de3bcd1530a..ac335373784 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -973,7 +973,7 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str mLogger->setAnalyzerInfo(nullptr); std::list errors; - analyzerInformation->analyzeFile(mSettings.buildDir, file.spath(), cfgname, file.fsFileId(), hash, errors); + analyzerInformation->analyzeFile(mSettings.buildDir, file.spath(), cfgname, file.fsFileId(), hash, errors, mSettings.debugainfo); analyzerInformation->setFileInfo("CheckUnusedFunctions", mUnusedFunctionsCheck->analyzerInfo(tokenizer)); analyzerInformation->close(); } @@ -1019,7 +1019,7 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str // Calculate hash so it can be compared with old hash / future hashes const std::size_t hash = calculateHash(preprocessor, file.spath()); std::list errors; - if (!analyzerInformation->analyzeFile(mSettings.buildDir, file.spath(), cfgname, file.fsFileId(), hash, errors)) { + if (!analyzerInformation->analyzeFile(mSettings.buildDir, file.spath(), cfgname, file.fsFileId(), hash, errors, mSettings.debugainfo)) { while (!errors.empty()) { mErrorLogger.reportErr(errors.front()); errors.pop_front(); @@ -1860,12 +1860,17 @@ unsigned int CppCheck::analyseWholeProgram(const std::string &buildDir, const st } }; - AnalyzerInformation::processFilesTxt(buildDir, handler); - - // Analyse the tokens - // cppcheck-suppress shadowFunction - TODO: fix this - for (Check *check : Check::instances()) - check->analyseWholeProgram(ctuFileInfo, fileInfoList, mSettings, mErrorLogger); + const std::string err = AnalyzerInformation::processFilesTxt(buildDir, handler, mSettings.debugainfo); + if (!err.empty()) { + const ErrorMessage errmsg({}, "", Severity::error, err, "internalError", Certainty::normal); + mErrorLogger.reportErr(errmsg); + } + else { + // Analyse the tokens + // cppcheck-suppress shadowFunction - TODO: fix this + for (Check *check : Check::instances()) + check->analyseWholeProgram(ctuFileInfo, fileInfoList, mSettings, mErrorLogger); + } for (Check::FileInfo *fi : fileInfoList) delete fi; diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp index e62ed6b806d..13f66c13d1a 100644 --- a/lib/errorlogger.cpp +++ b/lib/errorlogger.cpp @@ -162,6 +162,7 @@ ErrorMessage::ErrorMessage(ErrorPath errorPath, const TokenList *tokenList, Seve // hash = calculateWarningHash(tokenList, hashWarning.str()); } +// TODO: improve errorhandling? ErrorMessage::ErrorMessage(const tinyxml2::XMLElement * const errmsg) : severity(Severity::none), cwe(0U), diff --git a/lib/settings.h b/lib/settings.h index 04541cda08c..6521ba4581d 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -189,6 +189,9 @@ class CPPCHECKLIB WARN_UNUSED Settings { /** @brief Are we running from DACA script? */ bool daca{}; + /** @brief Is --debug-analyzerinfo given? */ + bool debugainfo{}; + /** @brief Is --debug-ast given? */ bool debugast{}; diff --git a/test/cli/other_test.py b/test/cli/other_test.py index 744a68aac60..7f882268754 100644 --- a/test/cli/other_test.py +++ b/test/cli/other_test.py @@ -4081,3 +4081,103 @@ def test_active_unusedfunction_only_misra_builddir(tmp_path): 'CheckUnusedFunctions::check' ] __test_active_checkers(tmp_path, 1, 1175, use_unusedfunction_only=True, use_misra=True, checkers_exp=checkers_exp) + + +def test_analyzerinfo(tmp_path): + test_file = tmp_path / 'test.c' + with open(test_file, "w") as f: + f.write( +"""void f() +{ + (void)(*((int*)0)); +} +""") + + build_dir = tmp_path / 'b1' + os.makedirs(build_dir) + + test_a1_file = build_dir / 'test.a1' + + args = [ + '-q', + '--debug-analyzerinfo', + '--template=simple', + '--cppcheck-build-dir={}'.format(build_dir), + '--enable=all', + str(test_file) + ] + + stderr_exp = [ + '{}:3:14: error: Null pointer dereference: (int*)0 [nullPointer]'.format(test_file), + "{}:1:6: style: The function 'f' is never used. [unusedFunction]".format(test_file) + ] + + def run_and_assert_cppcheck(stdout_exp): + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0, stdout + assert stdout.splitlines() == stdout_exp + assert stderr.splitlines() == stderr_exp + + test_a1_file_s = str(test_a1_file).replace('\\', '/') + + # no cached results + run_and_assert_cppcheck([ + "no cached result '{}' found".format(test_a1_file_s) + ]) + + # cached results + run_and_assert_cppcheck([ + "skipping analysis - loaded 1 cached finding(s) from '{}'".format(test_a1_file_s) + ]) + + # modified file + with open(test_file, 'a') as f: + f.write('\n#define DEF') + + run_and_assert_cppcheck([ + "discarding cached result - hash mismatch" # TODO: add filename + ]) + + # invalid XML + with open(test_a1_file, 'a') as f: + f.write('.') + + run_and_assert_cppcheck([ + "discarding cached result - failed to load '{}' (XML_ERROR_PARSING_TEXT)".format(test_a1_file_s) + ]) + + # missing root node + with open(test_a1_file, 'w') as f: + f.write('') + + run_and_assert_cppcheck([ + "discarding cached result - no root node found" # TODO: add filename + ]) + + # mismatched root node + with open(test_a1_file, 'w') as f: + f.write('') + + run_and_assert_cppcheck([ + "discarding cached result - unexpected root node" # TODO: add filename + ]) + + # missing 'hash' attribute + with open(test_a1_file, 'w') as f: + f.write('') + + run_and_assert_cppcheck([ + "discarding cached result - no 'hash' attribute found" # TODO: add filename + ]) + + # invalid 'hash' attribute + with open(test_a1_file, 'w') as f: + f.write('') + + run_and_assert_cppcheck([ + "discarding cached result - hash mismatch" # TODO: add filename + ]) + + # TODO: + # - invalid error + # - internalError diff --git a/test/testcmdlineparser.cpp b/test/testcmdlineparser.cpp index 3b343cbc92e..b6690f8d5c7 100644 --- a/test/testcmdlineparser.cpp +++ b/test/testcmdlineparser.cpp @@ -493,6 +493,7 @@ class TestCmdlineParser : public TestFixture { TEST_CASE(safetyOverride); TEST_CASE(noSafety); TEST_CASE(noSafetyOverride); + TEST_CASE(debugAnalyzerinfo); TEST_CASE(ignorepaths1); TEST_CASE(ignorepaths2); @@ -3424,6 +3425,13 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS(false, settings->safety); } + void debugAnalyzerinfo() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--debug-analyzerinfo", "file.cpp"}; + ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parseFromArgs(argv)); + ASSERT_EQUALS(true, settings->debugainfo); + } + void ignorepaths1() { REDIRECT; const char * const argv[] = {"cppcheck", "-isrc", "file.cpp"}; From 4d952190b66a717e7f03ead778a9a95ab6c41dc9 Mon Sep 17 00:00:00 2001 From: firewave Date: Sun, 4 Jan 2026 04:46:35 +0100 Subject: [PATCH 2/3] fixed #13087 - store `unmatchedSuppression` in `AnalyzerInformation` --- cli/cppcheckexecutor.cpp | 57 +++++++++++++++++++++++++++++++++------- lib/analyzerinfo.cpp | 20 ++++++++++++++ lib/analyzerinfo.h | 2 ++ test/cli/other_test.py | 4 --- 4 files changed, 69 insertions(+), 14 deletions(-) diff --git a/cli/cppcheckexecutor.cpp b/cli/cppcheckexecutor.cpp index 4f74bccbd5e..c5f01f16390 100644 --- a/cli/cppcheckexecutor.cpp +++ b/cli/cppcheckexecutor.cpp @@ -299,9 +299,10 @@ int CppCheckExecutor::check_wrapper(const Settings& settings, Suppressions& supp * @param unmatched list of unmatched suppressions (from Settings::Suppressions::getUnmatched(Local|Global)Suppressions) * @return true is returned if errors are reported */ -static bool reportUnmatchedSuppressions(const std::list &unmatched, ErrorLogger &errorLogger, const std::vector& filters) +static std::vector<::ErrorMessage> reportUnmatchedSuppressions(const std::list &unmatched, const std::vector& filters) { - bool err = false; + std::vector<::ErrorMessage> errors; + // Report unmatched suppressions for (const SuppressionList::Suppression &s : unmatched) { // check if this unmatched suppression is suppressed @@ -330,10 +331,11 @@ static bool reportUnmatchedSuppressions(const std::list &files, const std::list& fileSettings, ErrorLogger& errorLogger) { @@ -359,21 +361,55 @@ bool CppCheckExecutor::reportUnmatchedSuppressions(const Settings &settings, con supprlist.addSuppression(std::move(s)); } + const auto reportErrorsFn = [&](const std::string& sourcefile, std::size_t fsFileId, const std::vector<::ErrorMessage>& errors) -> bool { + if (errors.empty()) + return false; + + // TODO: what if sourcefile is empty? + + AnalyzerInformation analyzerInfo; + // FIXME: this is a horrible hack + // we need to "re-open" the file so we can add the unmatchedSuppression findings. + // we cannot keep it open conditionally because the whole program analysis read the XML. + // re-ordering is also not an option because the unmatched suppression reporting needs to be run after all other checks. + analyzerInfo.reopen(settings.buildDir, sourcefile, /*cfgname*/ "", fsFileId); + + for (const auto& errmsg : errors) { + analyzerInfo.reportErr(errmsg); + errorLogger.reportErr(errmsg); + } + return true; + }; + bool err = false; for (auto i = files.cbegin(); i != files.cend(); ++i) { - err |= ::reportUnmatchedSuppressions(supprlist.getUnmatchedLocalSuppressions(*i), errorLogger, settings.unmatchedSuppressionFilters); + const std::vector errors = ::reportUnmatchedSuppressions(supprlist.getUnmatchedLocalSuppressions(*i), settings.unmatchedSuppressionFilters); + err |= reportErrorsFn(i->spath(), 0, errors); } for (auto i = fileSettings.cbegin(); i != fileSettings.cend(); ++i) { - err |= ::reportUnmatchedSuppressions(supprlist.getUnmatchedLocalSuppressions(i->file), errorLogger, settings.unmatchedSuppressionFilters); + const std::vector errors = ::reportUnmatchedSuppressions(supprlist.getUnmatchedLocalSuppressions(i->file), settings.unmatchedSuppressionFilters); + err |= reportErrorsFn(i->file.spath(), i->file.fsFileId(), errors); } if (settings.inlineSuppressions) { - err |= ::reportUnmatchedSuppressions(supprlist.getUnmatchedInlineSuppressions(), errorLogger, settings.unmatchedSuppressionFilters); + const std::vector errors = ::reportUnmatchedSuppressions(supprlist.getUnmatchedInlineSuppressions(), settings.unmatchedSuppressionFilters); + for (const auto& errmsg : errors) { + std::string sourcefile; + if (!errmsg.callStack.empty()) + sourcefile = errmsg.callStack.cbegin()->getfile(); + err |= reportErrorsFn(sourcefile, 0, {errmsg}); + } } - err |= ::reportUnmatchedSuppressions(supprlist.getUnmatchedGlobalSuppressions(), errorLogger, settings.unmatchedSuppressionFilters); + const std::vector errors = ::reportUnmatchedSuppressions(supprlist.getUnmatchedGlobalSuppressions(), settings.unmatchedSuppressionFilters); + for (const auto& errmsg : errors) { + std::string sourcefile; + if (!errmsg.callStack.empty()) + sourcefile = errmsg.callStack.cbegin()->getfile(); + err |= reportErrorsFn(sourcefile, 0, {errmsg}); + } return err; } @@ -426,9 +462,10 @@ int CppCheckExecutor::check_internal(const Settings& settings, Suppressions& sup #endif } + // TODO: is this run again instead of using previously cached results? returnValue |= cppcheck.analyseWholeProgram(settings.buildDir, mFiles, mFileSettings, stdLogger.getCtuInfo()); - if (settings.severity.isEnabled(Severity::information) || settings.checkConfiguration) { + if ((settings.severity.isEnabled(Severity::information) || settings.checkConfiguration) && !supprs.nomsg.getSuppressions().empty()) { const bool err = reportUnmatchedSuppressions(settings, supprs.nomsg, mFiles, mFileSettings, stdLogger); if (err && returnValue == 0) returnValue = settings.exitCode; diff --git a/lib/analyzerinfo.cpp b/lib/analyzerinfo.cpp index d484f7d50a9..09e6fa36cb8 100644 --- a/lib/analyzerinfo.cpp +++ b/lib/analyzerinfo.cpp @@ -302,3 +302,23 @@ std::string AnalyzerInformation::processFilesTxt(const std::string& buildDir, co return ""; } +void AnalyzerInformation::reopen(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, int fileIndex) +{ + if (buildDir.empty() || sourcefile.empty()) + return; + + const std::string analyzerInfoFile = AnalyzerInformation::getAnalyzerInfoFile(buildDir,sourcefile,cfg,fileIndex); + std::ifstream ifs(analyzerInfoFile); + if (!ifs.is_open()) + return; + + std::ostringstream iss; + iss << ifs.rdbuf(); + ifs.close(); + + std::string content = iss.str(); + content = content.substr(0, content.find("")); + + mOutputStream.open(analyzerInfoFile, std::ios::trunc); + mOutputStream << content; +} diff --git a/lib/analyzerinfo.h b/lib/analyzerinfo.h index 5435be62560..08f136548ee 100644 --- a/lib/analyzerinfo.h +++ b/lib/analyzerinfo.h @@ -69,6 +69,8 @@ class CPPCHECKLIB AnalyzerInformation { void setFileInfo(const std::string &check, const std::string &fileInfo); static std::string getAnalyzerInfoFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, std::size_t fsFileId); + void reopen(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, int fileIndex); + static const char sep = ':'; class CPPCHECKLIB Info { diff --git a/test/cli/other_test.py b/test/cli/other_test.py index 7f882268754..63c9eb2c1f3 100644 --- a/test/cli/other_test.py +++ b/test/cli/other_test.py @@ -2315,8 +2315,6 @@ def test_inline_suppr_builddir(tmp_path): __test_inline_suppr(tmp_path, ['--cppcheck-build-dir={}'.format(build_dir), '-j1']) -# TODO: the suppressions are generated outside of the scope which captures the analysis information -@pytest.mark.xfail(strict=True) def test_inline_suppr_builddir_cached(tmp_path): build_dir = tmp_path / 'b1' os.mkdir(build_dir) @@ -2330,8 +2328,6 @@ def test_inline_suppr_builddir_j(tmp_path): __test_inline_suppr(tmp_path, ['--cppcheck-build-dir={}'.format(build_dir), '-j2']) -# TODO: the suppressions are generated outside of the scope which captures the analysis information -@pytest.mark.xfail(strict=True) def test_inline_suppr_builddir_j_cached(tmp_path): build_dir = tmp_path / 'b1' os.mkdir(build_dir) From cc978e9af47a443cf207dcc8673bcbf2cef8bbdb Mon Sep 17 00:00:00 2001 From: firewave Date: Wed, 11 Feb 2026 12:04:31 +0100 Subject: [PATCH 3/3] analyzerinfo.cpp: fixed `uselessCallsSubstr` selfcheck warning --- lib/analyzerinfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/analyzerinfo.cpp b/lib/analyzerinfo.cpp index 09e6fa36cb8..ebb359352c1 100644 --- a/lib/analyzerinfo.cpp +++ b/lib/analyzerinfo.cpp @@ -317,7 +317,7 @@ void AnalyzerInformation::reopen(const std::string &buildDir, const std::string ifs.close(); std::string content = iss.str(); - content = content.substr(0, content.find("")); + content.resize(content.find("")); mOutputStream.open(analyzerInfoFile, std::ios::trunc); mOutputStream << content;