From 5d527e74bc548de1f9dfe0d5100ed46f1333427b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Tue, 8 Sep 2026 15:24:38 +0200 Subject: [PATCH 01/13] fix --- cli/cppcheckexecutor.cpp | 7 +-- lib/cppcheck.cpp | 7 ++- lib/errorlogger.cpp | 133 +++++++++++++++++++++++++++++++++------ lib/errorlogger.h | 35 ++++++++++- test/testerrorlogger.cpp | 2 +- 5 files changed, 154 insertions(+), 30 deletions(-) diff --git a/cli/cppcheckexecutor.cpp b/cli/cppcheckexecutor.cpp index 82d4d70c0a0..a5772fbf75f 100644 --- a/cli/cppcheckexecutor.cpp +++ b/cli/cppcheckexecutor.cpp @@ -661,13 +661,12 @@ void StdLogger::reportErr(const ErrorMessage &msg) msgCopy.classification = getClassification(msgCopy.guideline, mSettings.reportType); // TODO: there should be no need for verbose and default messages here - // Don't perform redundant reads for these formats, the code is not needed - // for deduplication - const bool noCode = mSettings.outputFormat == Settings::OutputFormat::xml || + const bool noContext = mSettings.outputFormat == Settings::OutputFormat::xml || mSettings.outputFormat == Settings::OutputFormat::sarif; + const ErrorMessage::SourceLineCallback callback = noContext ? nullptr : getSourceLineCallback(); const std::string msgStr = msgCopy.toString(mSettings.verbose, mSettings.templateFormat, - mSettings.templateLocation, noCode); + mSettings.templateLocation, callback); // Alert only about unique errors if (!mSettings.emitDuplicates && !mShownErrors.insert(msgStr).second) diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index 8e32e3e3152..5d1b46f8965 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -210,9 +210,10 @@ class CppCheck::CppCheckLogger : public ErrorLogger } // TODO: there should be no need for the verbose and default messages here - // Code is not needed for deduplication - const bool noCode = true; - std::string errmsg = msg.toString(mSettings.verbose, mSettings.templateFormat, mSettings.templateLocation, noCode); + std::string errmsg = msg.toString(mSettings.verbose, + mSettings.templateFormat, + mSettings.templateLocation, + nullptr); if (errmsg.empty()) return; diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp index 1199223c822..3338f240ae8 100644 --- a/lib/errorlogger.cpp +++ b/lib/errorlogger.cpp @@ -638,23 +638,6 @@ std::string ErrorMessage::toXML() const return printer.CStr(); } -// TODO: read info from some shared resource instead? -static std::string readCode(const std::string &file, int linenr, int column, const char endl[]) -{ - std::ifstream fin(file); - std::string line; - while (linenr > 0 && std::getline(fin,line)) { - linenr--; - } - const std::string::size_type endPos = line.find_last_not_of("\r\n\t "); - if (endPos + 1 < line.size()) - line.erase(endPos + 1); - std::string::size_type pos = 0; - while ((pos = line.find('\t', pos)) != std::string::npos) - line[pos] = ' '; - return line + endl + std::string((column>0 ? column-1 : 0), ' ') + '^'; -} - static void replaceSpecialChars(std::string& source) { // Support a few special characters to allow to specific formatting, see http://sourceforge.net/apps/phpbb/cppcheck/viewtopic.php?f=4&t=494&sid=21715d362c0dbafd3791da4d9522f814 @@ -729,7 +712,38 @@ static void replaceColors(std::string& source, bool erase) { replace(source, substitutionMapErase); } -std::string ErrorMessage::toString(bool verbose, const std::string &templateFormat, const std::string &templateLocation, bool noCode) const +static std::string formatLine(std::string line, int column, const char endl[]) +{ + const std::string::size_type endPos = line.find_last_not_of("\r\n\t "); + if (endPos + 1 < line.size()) + line.erase(endPos + 1); + + std::string::size_type pos = 0; + while ((pos = line.find('\t', pos)) != std::string::npos) + line[pos] = ' '; + + return line + endl + std::string((column>0 ? column-1 : 0), ' ') + '^'; +} + +std::string ErrorMessage::directSourceLineCallback(const std::string &file, + int linenr, + int column, + const char endl[], + int cachePrio) +{ + std::ifstream fin(file); + std::string line; + + while (linenr > 0 && std::getline(fin, line)) + --linenr; + + return formatLine(line, column, endl); +} + +std::string ErrorMessage::toString(bool verbose, + const std::string &templateFormat, + const std::string &templateLocation, + SourceLineCallback sourceLineCallback) const { assert(!templateFormat.empty()); @@ -770,7 +784,12 @@ std::string ErrorMessage::toString(bool verbose, const std::string &templateForm endl = "\r\n"; else endl = "\r"; - const std::string code = noCode ? "" : readCode(callStack.back().getOrigFile(), callStack.back().line, callStack.back().column, endl); + const std::string code = sourceLineCallback == nullptr ? + "" : sourceLineCallback(callStack.back().getOrigFile(), + callStack.back().line, + callStack.back().column, + endl, + 0); findAndReplace(result, "{code}", code); } } else { @@ -785,6 +804,7 @@ std::string ErrorMessage::toString(bool verbose, const std::string &templateForm replace(result, callStackSubstitutionMap); } + int cachePrio = -1; if (!templateLocation.empty() && callStack.size() >= 2U) { for (const FileLocation &fileLocation : callStack) { std::string text = templateLocation; @@ -802,7 +822,12 @@ std::string ErrorMessage::toString(bool verbose, const std::string &templateForm endl = "\r\n"; else endl = "\r"; - const std::string code = noCode ? "" : readCode(fileLocation.getOrigFile(), fileLocation.line, fileLocation.column, endl); + const std::string code = sourceLineCallback == nullptr ? + "" : sourceLineCallback(fileLocation.getOrigFile(), + fileLocation.line, + fileLocation.column, + endl, + cachePrio--); findAndReplace(text, "{code}", code); } result += '\n' + text; @@ -1261,3 +1286,71 @@ std::map createGuidelineMapping(ReportType reportType) return guidelineMapping; } + +ErrorLogger::SourceCacheEntry::SourceCacheEntry(const std::string &file, int prio) + : prio(prio) + , file(file) + , stream(std::ifstream(file)) +{ +} + +std::string ErrorLogger::sourceLineCallback(const std::string &file, + int linenr, + int column, + const char endl[], + int cachePrio) +{ + // For sorting cache entries by priority + const auto heapCompare = [](const std::shared_ptr &lhs, const std::shared_ptr &rhs) { + return lhs->prio > rhs->prio; + }; + + std::shared_ptr entry = nullptr; + + const auto existing = std::find_if( + mSourceCache.begin(), + mSourceCache.end(), + [&] (const std::shared_ptr &e) { return e->file == file; } + ); + + if (existing == mSourceCache.end()) { + if (mSourceCache.size() == mSourceCacheSize) { + // Evict the cache entry with lowest priority + std::pop_heap(mSourceCache.begin(), mSourceCache.end(), heapCompare); + mSourceCache.pop_back(); + } + + // Insert new entry + entry = std::make_shared(file, cachePrio); + mSourceCache.push_back(entry); + std::push_heap(mSourceCache.begin(), mSourceCache.end(), heapCompare); + } else { + entry = *existing; + if (entry->prio < cachePrio) { + // Update priority and sort cache + entry->prio = cachePrio; + std::make_heap(mSourceCache.begin(), mSourceCache.end(), heapCompare); + } + } + + entry->stream.clear(); + entry->stream.seekg(0); + + std::string line; + while (linenr > 0 && std::getline(entry->stream, line)) + linenr--; + + return formatLine(line, column, endl); +} + +ErrorMessage::SourceLineCallback ErrorLogger::getSourceLineCallback() +{ + return [this](const std::string &file, + int linenr, + int column, + const char endl[], + int cachePrio) + { + return sourceLineCallback(file, linenr, column, endl, cachePrio); + }; +} diff --git a/lib/errorlogger.h b/lib/errorlogger.h index 4b62d1b3218..3e1cc32c55b 100644 --- a/lib/errorlogger.h +++ b/lib/errorlogger.h @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -33,6 +34,8 @@ #include #include #include +#include +#include class Token; class TokenList; @@ -102,6 +105,19 @@ class CPPCHECKLIB ErrorMessage { std::string mInfo; }; + using SourceLineCallback = std::function; + + static std::string directSourceLineCallback(const std::string &file, + int linenr, + int column, + const char endl[], + int cachePrio); + ErrorMessage(std::list callStack, std::string file1, Severity severity, @@ -152,13 +168,13 @@ class CPPCHECKLIB ErrorMessage { * or template to be used. E.g. "{file}:{line},{severity},{id},{message}" * @param templateLocation Format Empty string to use default output format * or template to be used. E.g. "{file}:{line},{info}" - * @param noCode Always replace {code} with an empty string + * @param sourceLineCallback Function used for fetching a line of source code for the error context * @return formatted string */ std::string toString(bool verbose, const std::string &templateFormat, const std::string &templateLocation, - bool noCode = false) const; + SourceLineCallback sourceLineCallback = directSourceLineCallback) const; std::string serialize() const; /** @@ -302,8 +318,23 @@ class CPPCHECKLIB ErrorLogger { return mCriticalErrorIds.count(id) != 0; } + ErrorMessage::SourceLineCallback getSourceLineCallback(); + private: static const std::set mCriticalErrorIds; + static const std::size_t mSourceCacheSize = 4; + + struct SourceCacheEntry { + explicit SourceCacheEntry(const std::string &file, int prio); + + int prio; + std::string file; + std::ifstream stream; + }; + + std::vector> mSourceCache; + + std::string sourceLineCallback(const std::string &file, int linenr, int column, const char endl[], int cachePrio); }; /// RAII class for reporting progress messages diff --git a/test/testerrorlogger.cpp b/test/testerrorlogger.cpp index 68d473420b9..f32f3eb4de2 100644 --- a/test/testerrorlogger.cpp +++ b/test/testerrorlogger.cpp @@ -472,7 +472,7 @@ class TestErrorLogger : public TestFixture { ASSERT_EQUALS(1, msg.callStack.size()); const bool noCode = true; ASSERT_EQUALS("code.cpp:3:5: error: Programming error. [errorId]\n", - msg.toString(false, "{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\n{code}", "", noCode)); + msg.toString(false, "{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\n{code}", "", nullptr)); } void CustomFormat() const { From 0089cbaa294da755fdea5a2efef808fa9af4e8fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Tue, 8 Sep 2026 15:43:48 +0200 Subject: [PATCH 02/13] update strace test --- test/cli/other_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cli/other_test.py b/test/cli/other_test.py index ace82b9ac32..dbda01b2e3f 100644 --- a/test/cli/other_test.py +++ b/test/cli/other_test.py @@ -4864,7 +4864,7 @@ def test_ipc_inline_suppressions(tmp_path): assert stderr.splitlines() == [] test_redundant_file_reads_params = [ - ([], 3), + ([], 2), (['--suppress=zerodiv'], 1), (['--template=cppcheck1'], 1), (['--xml'], 1), From 45e8797bd57c75c07aa687ea9bf9723297abf2f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Tue, 8 Sep 2026 19:45:33 +0200 Subject: [PATCH 03/13] fix selfcheck warning --- cli/cppcheckexecutor.cpp | 2 +- lib/errorlogger.cpp | 33 +++++++++++++++-------------- lib/errorlogger.h | 14 ++++++------- test/scripts/strace_tests.sh | 40 ++++++++++++++++++++++++++++++++++++ test/testerrorlogger.cpp | 1 - 5 files changed, 66 insertions(+), 24 deletions(-) create mode 100644 test/scripts/strace_tests.sh diff --git a/cli/cppcheckexecutor.cpp b/cli/cppcheckexecutor.cpp index a5772fbf75f..f9feb2e3444 100644 --- a/cli/cppcheckexecutor.cpp +++ b/cli/cppcheckexecutor.cpp @@ -662,7 +662,7 @@ void StdLogger::reportErr(const ErrorMessage &msg) // TODO: there should be no need for verbose and default messages here const bool noContext = mSettings.outputFormat == Settings::OutputFormat::xml || - mSettings.outputFormat == Settings::OutputFormat::sarif; + mSettings.outputFormat == Settings::OutputFormat::sarif; const ErrorMessage::SourceLineCallback callback = noContext ? nullptr : getSourceLineCallback(); const std::string msgStr = msgCopy.toString(mSettings.verbose, mSettings.templateFormat, diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp index 3338f240ae8..bf936b11399 100644 --- a/lib/errorlogger.cpp +++ b/lib/errorlogger.cpp @@ -731,6 +731,8 @@ std::string ErrorMessage::directSourceLineCallback(const std::string &file, const char endl[], int cachePrio) { + (void) cachePrio; + std::ifstream fin(file); std::string line; @@ -785,11 +787,11 @@ std::string ErrorMessage::toString(bool verbose, else endl = "\r"; const std::string code = sourceLineCallback == nullptr ? - "" : sourceLineCallback(callStack.back().getOrigFile(), - callStack.back().line, - callStack.back().column, - endl, - 0); + "" : sourceLineCallback(callStack.back().getOrigFile(), + callStack.back().line, + callStack.back().column, + endl, + 0); findAndReplace(result, "{code}", code); } } else { @@ -804,8 +806,8 @@ std::string ErrorMessage::toString(bool verbose, replace(result, callStackSubstitutionMap); } - int cachePrio = -1; if (!templateLocation.empty() && callStack.size() >= 2U) { + int cachePrio = -1; for (const FileLocation &fileLocation : callStack) { std::string text = templateLocation; @@ -823,11 +825,11 @@ std::string ErrorMessage::toString(bool verbose, else endl = "\r"; const std::string code = sourceLineCallback == nullptr ? - "" : sourceLineCallback(fileLocation.getOrigFile(), - fileLocation.line, - fileLocation.column, - endl, - cachePrio--); + "" : sourceLineCallback(fileLocation.getOrigFile(), + fileLocation.line, + fileLocation.column, + endl, + cachePrio--); findAndReplace(text, "{code}", code); } result += '\n' + text; @@ -1291,8 +1293,7 @@ ErrorLogger::SourceCacheEntry::SourceCacheEntry(const std::string &file, int pri : prio(prio) , file(file) , stream(std::ifstream(file)) -{ -} +{} std::string ErrorLogger::sourceLineCallback(const std::string &file, int linenr, @@ -1310,8 +1311,10 @@ std::string ErrorLogger::sourceLineCallback(const std::string &file, const auto existing = std::find_if( mSourceCache.begin(), mSourceCache.end(), - [&] (const std::shared_ptr &e) { return e->file == file; } - ); + [&] (const std::shared_ptr &e) { + return e->file == file; + } + ); if (existing == mSourceCache.end()) { if (mSourceCache.size() == mSourceCacheSize) { diff --git a/lib/errorlogger.h b/lib/errorlogger.h index 3e1cc32c55b..04b40203a94 100644 --- a/lib/errorlogger.h +++ b/lib/errorlogger.h @@ -106,11 +106,11 @@ class CPPCHECKLIB ErrorMessage { }; using SourceLineCallback = std::function; + const std::string &file, + int linenr, + int column, + const char endl[], + int cachePrio)>; static std::string directSourceLineCallback(const std::string &file, int linenr, @@ -327,8 +327,8 @@ class CPPCHECKLIB ErrorLogger { struct SourceCacheEntry { explicit SourceCacheEntry(const std::string &file, int prio); - int prio; - std::string file; + int prio; + std::string file; std::ifstream stream; }; diff --git a/test/scripts/strace_tests.sh b/test/scripts/strace_tests.sh new file mode 100644 index 00000000000..3576938c28a --- /dev/null +++ b/test/scripts/strace_tests.sh @@ -0,0 +1,40 @@ +#! /usr/bin/env bash + +[ -z "$CPPCHECK" ] && CPPCHECK="$(dirname "${BASH_SORCE[0]}")"/cppcheck + +failed=no +temp_c_1="$(mktemp XXX.c)" + +strace_test() { + local command="$1" + local pattern="$2" + local expected="$3" + local actual="$(strace --follow-forks $command 2>&1 | grep "$pattern" | wc --lines)" + + if [ ! "$expected" = "$actual" ]; then + >&2 echo "Command '$command' performed $actual syscalls matching pattern '$pattern', but $expected was expected" + failed=yes + fi +} + +cat <<-EOF >"$temp_c_1" +void f(int x) { + int a = x / 0; +} +EOF + +strace_test "$CPPCHECK $temp_c_1" "openat.*$temp_c_1" 2 +strace_test "$CPPCHECK $temp_c_1 --suppress=zerodiv" "openat.*$temp_c_1" 1 +strace_test "$CPPCHECK $temp_c_1 --format=xml" "openat.*$temp_c_1" 1 +strace_test "$CPPCHECK $temp_c_1 --format=sarif" "openat.*$temp_c_1" 1 + +[ "$failed" = yes ] && exit 1 + +exit 0 + +# strace +# --summary-only +# --summary-columns=count +# --trace=openat +# --trace-path=tickets/15010-redundant-reads.c +# ./cppcheck tickets/15010-redundant-reads.c 2>&1 | tail -1 | cut -f1 -d'\t' diff --git a/test/testerrorlogger.cpp b/test/testerrorlogger.cpp index f32f3eb4de2..91e75cd3baa 100644 --- a/test/testerrorlogger.cpp +++ b/test/testerrorlogger.cpp @@ -470,7 +470,6 @@ class TestErrorLogger : public TestFixture { std::list locs = { code }; ErrorMessage msg(std::move(locs), "", Severity::error, "Programming error.\nVerbose error", "errorId", Certainty::normal); ASSERT_EQUALS(1, msg.callStack.size()); - const bool noCode = true; ASSERT_EQUALS("code.cpp:3:5: error: Programming error. [errorId]\n", msg.toString(false, "{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\n{code}", "", nullptr)); } From cebe53183e874740948e4a4617992d69cebc76a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Wed, 9 Sep 2026 07:42:20 +0200 Subject: [PATCH 04/13] cleanup --- test/scripts/strace_tests.sh | 40 ------------------------------------ 1 file changed, 40 deletions(-) delete mode 100644 test/scripts/strace_tests.sh diff --git a/test/scripts/strace_tests.sh b/test/scripts/strace_tests.sh deleted file mode 100644 index 3576938c28a..00000000000 --- a/test/scripts/strace_tests.sh +++ /dev/null @@ -1,40 +0,0 @@ -#! /usr/bin/env bash - -[ -z "$CPPCHECK" ] && CPPCHECK="$(dirname "${BASH_SORCE[0]}")"/cppcheck - -failed=no -temp_c_1="$(mktemp XXX.c)" - -strace_test() { - local command="$1" - local pattern="$2" - local expected="$3" - local actual="$(strace --follow-forks $command 2>&1 | grep "$pattern" | wc --lines)" - - if [ ! "$expected" = "$actual" ]; then - >&2 echo "Command '$command' performed $actual syscalls matching pattern '$pattern', but $expected was expected" - failed=yes - fi -} - -cat <<-EOF >"$temp_c_1" -void f(int x) { - int a = x / 0; -} -EOF - -strace_test "$CPPCHECK $temp_c_1" "openat.*$temp_c_1" 2 -strace_test "$CPPCHECK $temp_c_1 --suppress=zerodiv" "openat.*$temp_c_1" 1 -strace_test "$CPPCHECK $temp_c_1 --format=xml" "openat.*$temp_c_1" 1 -strace_test "$CPPCHECK $temp_c_1 --format=sarif" "openat.*$temp_c_1" 1 - -[ "$failed" = yes ] && exit 1 - -exit 0 - -# strace -# --summary-only -# --summary-columns=count -# --trace=openat -# --trace-path=tickets/15010-redundant-reads.c -# ./cppcheck tickets/15010-redundant-reads.c 2>&1 | tail -1 | cut -f1 -d'\t' From f626e2608e52b89e81606b55af8f8728b35a2904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Wed, 9 Sep 2026 07:44:49 +0200 Subject: [PATCH 05/13] fix clang-tidy warning --- lib/errorlogger.cpp | 2 +- lib/errorlogger.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp index bf936b11399..3f08e384d5c 100644 --- a/lib/errorlogger.cpp +++ b/lib/errorlogger.cpp @@ -745,7 +745,7 @@ std::string ErrorMessage::directSourceLineCallback(const std::string &file, std::string ErrorMessage::toString(bool verbose, const std::string &templateFormat, const std::string &templateLocation, - SourceLineCallback sourceLineCallback) const + const SourceLineCallback &sourceLineCallback) const { assert(!templateFormat.empty()); diff --git a/lib/errorlogger.h b/lib/errorlogger.h index 04b40203a94..0ea2c37c286 100644 --- a/lib/errorlogger.h +++ b/lib/errorlogger.h @@ -174,7 +174,7 @@ class CPPCHECKLIB ErrorMessage { std::string toString(bool verbose, const std::string &templateFormat, const std::string &templateLocation, - SourceLineCallback sourceLineCallback = directSourceLineCallback) const; + const SourceLineCallback &sourceLineCallback = directSourceLineCallback) const; std::string serialize() const; /** From bbef78c49072f0d1b6022ac8189261560868d7d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Wed, 9 Sep 2026 10:17:00 +0200 Subject: [PATCH 06/13] fix computation of cache priority --- lib/errorlogger.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp index 3f08e384d5c..2b21dec886c 100644 --- a/lib/errorlogger.cpp +++ b/lib/errorlogger.cpp @@ -807,7 +807,7 @@ std::string ErrorMessage::toString(bool verbose, } if (!templateLocation.empty() && callStack.size() >= 2U) { - int cachePrio = -1; + int cachePrio = 1 - static_cast(callStack.size()); for (const FileLocation &fileLocation : callStack) { std::string text = templateLocation; @@ -829,7 +829,7 @@ std::string ErrorMessage::toString(bool verbose, fileLocation.line, fileLocation.column, endl, - cachePrio--); + cachePrio++); findAndReplace(text, "{code}", code); } result += '\n' + text; From 06c6327f2428ff00bdd744810b7e4a882ac02673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Wed, 9 Sep 2026 11:07:59 +0200 Subject: [PATCH 07/13] decrement priority for existing entries --- lib/errorlogger.cpp | 13 +++++++++++-- lib/errorlogger.h | 2 ++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp index 2b21dec886c..8d73ecc35fe 100644 --- a/lib/errorlogger.cpp +++ b/lib/errorlogger.cpp @@ -1295,17 +1295,26 @@ ErrorLogger::SourceCacheEntry::SourceCacheEntry(const std::string &file, int pri , stream(std::ifstream(file)) {} +bool ErrorLogger::SourceCacheEntry::operator<(const ErrorLogger::SourceCacheEntry &rhs) const +{ + return (prio > rhs.prio) || + (prio == rhs.prio && file < rhs.file); +} + std::string ErrorLogger::sourceLineCallback(const std::string &file, int linenr, int column, const char endl[], int cachePrio) { - // For sorting cache entries by priority const auto heapCompare = [](const std::shared_ptr &lhs, const std::shared_ptr &rhs) { - return lhs->prio > rhs->prio; + return *lhs < *rhs; }; + // Decrease priority for all cache entries + for (auto &entry : mSourceCache) + --entry->prio; + std::shared_ptr entry = nullptr; const auto existing = std::find_if( diff --git a/lib/errorlogger.h b/lib/errorlogger.h index 0ea2c37c286..15701c0cb5d 100644 --- a/lib/errorlogger.h +++ b/lib/errorlogger.h @@ -330,6 +330,8 @@ class CPPCHECKLIB ErrorLogger { int prio; std::string file; std::ifstream stream; + + bool operator<(const SourceCacheEntry &rhs) const; }; std::vector> mSourceCache; From c1e5636588f390d1e1acdeda88e501fa1bb72db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Wed, 9 Sep 2026 10:39:57 +0200 Subject: [PATCH 08/13] add source cache tests --- lib/errorlogger.cpp | 2 +- lib/errorlogger.h | 4 +- test/testerrorlogger.cpp | 197 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 2 deletions(-) diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp index 8d73ecc35fe..248bee1bfe5 100644 --- a/lib/errorlogger.cpp +++ b/lib/errorlogger.cpp @@ -1326,7 +1326,7 @@ std::string ErrorLogger::sourceLineCallback(const std::string &file, ); if (existing == mSourceCache.end()) { - if (mSourceCache.size() == mSourceCacheSize) { + if (mSourceCache.size() == getSourceCacheSize()) { // Evict the cache entry with lowest priority std::pop_heap(mSourceCache.begin(), mSourceCache.end(), heapCompare); mSourceCache.pop_back(); diff --git a/lib/errorlogger.h b/lib/errorlogger.h index 15701c0cb5d..179b62763ac 100644 --- a/lib/errorlogger.h +++ b/lib/errorlogger.h @@ -322,7 +322,9 @@ class CPPCHECKLIB ErrorLogger { private: static const std::set mCriticalErrorIds; - static const std::size_t mSourceCacheSize = 4; + +protected: + virtual std::size_t getSourceCacheSize() const { return 4; }; struct SourceCacheEntry { explicit SourceCacheEntry(const std::string &file, int prio); diff --git a/test/testerrorlogger.cpp b/test/testerrorlogger.cpp index 91e75cd3baa..9c5c5a81916 100644 --- a/test/testerrorlogger.cpp +++ b/test/testerrorlogger.cpp @@ -83,6 +83,8 @@ class TestErrorLogger : public TestFixture { TEST_CASE(isCriticalErrorId); TEST_CASE(TestReportType); + + TEST_CASE(ErrorLoggerSourceCache); } void TestPatternSearchReplace(const std::string& idPlaceholder, const std::string& id) const { @@ -865,6 +867,201 @@ class TestErrorLogger : public TestFixture { // It does not abort all the analysis of the file. Like "missingInclude" there can be false negatives. ASSERT_EQUALS(false, ErrorLogger::isCriticalErrorId("misra-config")); } + + class TestSourceCacheLogger : public ErrorLogger { + public: + friend class TestErrorLogger; + + virtual void reportOut(const std::string &outmsg, Color c) + { + (void) outmsg; + (void) c; + } + + virtual void reportErr(const ErrorMessage &msg) + { + (void) msg.toString(false, "{code}", "{code}", getSourceLineCallback()); + } + + virtual void reportMetric(const std::string &metric) + { + (void) metric; + } + + void clearCache() { mSourceCache.clear(); } + + struct Match { + std::string file; + int prio; + }; + + private: + // Override this in case it's changed in the main implementation + virtual std::size_t getSourceCacheSize() const override { return 4; } + }; + + TestSourceCacheLogger testSourceCacheLogger; + + #define testCacheContent(...) testCacheContent_(__FILE__,__LINE__,__VA_ARGS__) + void testCacheContent_(const char *testfile, + int testline, + const std::string &file, + std::vector &&callstackFiles, + std::vector &&content) + { + const auto heapCompare = [](const std::shared_ptr &lhs, + const std::shared_ptr &rhs) + { + return *lhs < *rhs; + }; + + std::list callstack; + for (const auto &file : callstackFiles) + callstack.emplace_back(file, 1, 1); + + const ErrorMessage msg(callstack, + file, + Severity::warning, + "Made up message", + "made-up-id", + Certainty::normal); + testSourceCacheLogger.reportErr(msg); + + auto copy = testSourceCacheLogger.mSourceCache; + ASSERT_EQUALS(content.size(), copy.size()); + + std::make_heap(copy.begin(), copy.end(), heapCompare); + for (const auto match : content) { + std::pop_heap(copy.begin(), copy.end(), heapCompare); + ASSERT_EQUALS(match.file, copy.back()->file); + ASSERT_EQUALS(match.prio, copy.back()->prio); + copy.pop_back(); + } + } + + void ErrorLoggerSourceCache() { + const char *content = "first line\n" + "second line\n" + "third line\n"; + + const ScopedFile files[] = { + { "1.txt", content }, + { "2.txt", content }, + { "3.txt", content }, + { "4.txt", content }, + { "5.txt", content }, + }; + + testCacheContent( + "1.txt", + { + "1.txt", + }, + { + { "1.txt", 0 }, + } + ); + + testCacheContent( + "2.txt", + { + "2.txt", + }, + { + { "1.txt", -1 }, + { "2.txt", 0 }, + } + ); + + testCacheContent( + "1.txt", + { + "1.txt", + }, + { + { "2.txt", -1 }, + { "1.txt", 0 }, + } + ); + + testCacheContent( + "3.txt", + { + "3.txt", + }, + { + { "2.txt", -2 }, + { "1.txt", -1 }, + { "3.txt", 0 }, + } + ); + + testSourceCacheLogger.clearCache(); + + testCacheContent( + "1.txt", + { + "4.txt", + "3.txt", + "2.txt", + "1.txt", + }, + { + { "4.txt", -6 }, + { "3.txt", -4 }, + { "2.txt", -2 }, + { "1.txt", 0 }, + } + ); + + testCacheContent( + "1.txt", + { + "5.txt", + "3.txt", + "2.txt", + "1.txt", + }, + { + { "5.txt", -6 }, + { "3.txt", -4 }, + { "2.txt", -2 }, + { "1.txt", 0 }, + } + ); + + testCacheContent( + "2.txt", + { + "5.txt", + "4.txt", + "3.txt", + "2.txt", + }, + { + { "1.txt", -5 }, + { "4.txt", -4 }, + { "3.txt", -2 }, + { "2.txt", 0 }, + } + ); + + testCacheContent( + "2.txt", + { + "5.txt", + "4.txt", + "3.txt", + "2.txt", + }, + { + { "5.txt", -6 }, + { "4.txt", -4 }, + { "3.txt", -2 }, + { "2.txt", 0 }, + } + ); + } }; REGISTER_TEST(TestErrorLogger) From 61086d720c5bf0aec232cf7e66901c5b9b33ef49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Wed, 9 Sep 2026 13:40:32 +0200 Subject: [PATCH 09/13] don't reset stream if moving forward --- lib/errorlogger.cpp | 26 +++++++++++++++++--------- lib/errorlogger.h | 11 +++++++++-- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp index 248bee1bfe5..8ac95302697 100644 --- a/lib/errorlogger.cpp +++ b/lib/errorlogger.cpp @@ -1292,7 +1292,8 @@ std::map createGuidelineMapping(ReportType reportType) ErrorLogger::SourceCacheEntry::SourceCacheEntry(const std::string &file, int prio) : prio(prio) , file(file) - , stream(std::ifstream(file)) + , mStream(std::ifstream(file)) + , mLinenr(0) {} bool ErrorLogger::SourceCacheEntry::operator<(const ErrorLogger::SourceCacheEntry &rhs) const @@ -1301,6 +1302,20 @@ bool ErrorLogger::SourceCacheEntry::operator<(const ErrorLogger::SourceCacheEntr (prio == rhs.prio && file < rhs.file); } +std::string ErrorLogger::SourceCacheEntry::getLine(int linenr) +{ + if (linenr < mLinenr) { + mLinenr = 0; + mStream.clear(); + mStream.seekg(0); + } + + while (mLinenr < linenr && std::getline(mStream, mLine)) + mLinenr++; + + return mLine; +} + std::string ErrorLogger::sourceLineCallback(const std::string &file, int linenr, int column, @@ -1345,14 +1360,7 @@ std::string ErrorLogger::sourceLineCallback(const std::string &file, } } - entry->stream.clear(); - entry->stream.seekg(0); - - std::string line; - while (linenr > 0 && std::getline(entry->stream, line)) - linenr--; - - return formatLine(line, column, endl); + return formatLine(entry->getLine(linenr), column, endl); } ErrorMessage::SourceLineCallback ErrorLogger::getSourceLineCallback() diff --git a/lib/errorlogger.h b/lib/errorlogger.h index 179b62763ac..6d4827c89f4 100644 --- a/lib/errorlogger.h +++ b/lib/errorlogger.h @@ -326,14 +326,21 @@ class CPPCHECKLIB ErrorLogger { protected: virtual std::size_t getSourceCacheSize() const { return 4; }; - struct SourceCacheEntry { + class SourceCacheEntry { + public: explicit SourceCacheEntry(const std::string &file, int prio); int prio; std::string file; - std::ifstream stream; bool operator<(const SourceCacheEntry &rhs) const; + + std::string getLine(int linenr); + + private: + std::ifstream mStream; + std::string mLine; + int mLinenr; }; std::vector> mSourceCache; From a9f6c11ed639270039cc064190b2dcdc0fe53cb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Wed, 9 Sep 2026 16:06:08 +0200 Subject: [PATCH 10/13] add missing nullptr in cli/executor.cpp --- cli/executor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/executor.cpp b/cli/executor.cpp index d0a05eda5f7..80a8fa64f68 100644 --- a/cli/executor.cpp +++ b/cli/executor.cpp @@ -46,7 +46,7 @@ bool Executor::hasToLog(const ErrorMessage &msg) if (!mSuppressions.nomsg.isSuppressed(msg, {})) { // TODO: there should be no need for verbose and default messages here - std::string errmsg = msg.toString(mSettings.verbose, mSettings.templateFormat, mSettings.templateLocation); + std::string errmsg = msg.toString(mSettings.verbose, mSettings.templateFormat, mSettings.templateLocation, nullptr); if (errmsg.empty()) return false; From 62b1610a768481c36e4b59b733724340c241e2b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Thu, 10 Sep 2026 08:18:03 +0200 Subject: [PATCH 11/13] format --- lib/errorlogger.h | 4 +- test/testerrorlogger.cpp | 176 ++++++++++++++++++++------------------- 2 files changed, 93 insertions(+), 87 deletions(-) diff --git a/lib/errorlogger.h b/lib/errorlogger.h index 6d4827c89f4..db4cefe09b0 100644 --- a/lib/errorlogger.h +++ b/lib/errorlogger.h @@ -324,7 +324,9 @@ class CPPCHECKLIB ErrorLogger { static const std::set mCriticalErrorIds; protected: - virtual std::size_t getSourceCacheSize() const { return 4; }; + virtual std::size_t getSourceCacheSize() const { + return 4; + }; class SourceCacheEntry { public: diff --git a/test/testerrorlogger.cpp b/test/testerrorlogger.cpp index 9c5c5a81916..2492b5a709c 100644 --- a/test/testerrorlogger.cpp +++ b/test/testerrorlogger.cpp @@ -888,7 +888,9 @@ class TestErrorLogger : public TestFixture { (void) metric; } - void clearCache() { mSourceCache.clear(); } + void clearCache() { + mSourceCache.clear(); + } struct Match { std::string file; @@ -897,7 +899,9 @@ class TestErrorLogger : public TestFixture { private: // Override this in case it's changed in the main implementation - virtual std::size_t getSourceCacheSize() const override { return 4; } + virtual std::size_t getSourceCacheSize() const override { + return 4; + } }; TestSourceCacheLogger testSourceCacheLogger; @@ -954,113 +958,113 @@ class TestErrorLogger : public TestFixture { testCacheContent( "1.txt", - { - "1.txt", - }, - { - { "1.txt", 0 }, - } - ); + { + "1.txt", + }, + { + { "1.txt", 0 }, + } + ); testCacheContent( "2.txt", - { - "2.txt", - }, - { - { "1.txt", -1 }, - { "2.txt", 0 }, - } - ); + { + "2.txt", + }, + { + { "1.txt", -1 }, + { "2.txt", 0 }, + } + ); testCacheContent( "1.txt", - { - "1.txt", - }, - { - { "2.txt", -1 }, - { "1.txt", 0 }, - } - ); + { + "1.txt", + }, + { + { "2.txt", -1 }, + { "1.txt", 0 }, + } + ); testCacheContent( "3.txt", - { - "3.txt", - }, - { - { "2.txt", -2 }, - { "1.txt", -1 }, - { "3.txt", 0 }, - } - ); + { + "3.txt", + }, + { + { "2.txt", -2 }, + { "1.txt", -1 }, + { "3.txt", 0 }, + } + ); testSourceCacheLogger.clearCache(); testCacheContent( "1.txt", - { - "4.txt", - "3.txt", - "2.txt", - "1.txt", - }, - { - { "4.txt", -6 }, - { "3.txt", -4 }, - { "2.txt", -2 }, - { "1.txt", 0 }, - } - ); + { + "4.txt", + "3.txt", + "2.txt", + "1.txt", + }, + { + { "4.txt", -6 }, + { "3.txt", -4 }, + { "2.txt", -2 }, + { "1.txt", 0 }, + } + ); testCacheContent( "1.txt", - { - "5.txt", - "3.txt", - "2.txt", - "1.txt", - }, - { - { "5.txt", -6 }, - { "3.txt", -4 }, - { "2.txt", -2 }, - { "1.txt", 0 }, - } - ); + { + "5.txt", + "3.txt", + "2.txt", + "1.txt", + }, + { + { "5.txt", -6 }, + { "3.txt", -4 }, + { "2.txt", -2 }, + { "1.txt", 0 }, + } + ); testCacheContent( "2.txt", - { - "5.txt", - "4.txt", - "3.txt", - "2.txt", - }, - { - { "1.txt", -5 }, - { "4.txt", -4 }, - { "3.txt", -2 }, - { "2.txt", 0 }, - } - ); + { + "5.txt", + "4.txt", + "3.txt", + "2.txt", + }, + { + { "1.txt", -5 }, + { "4.txt", -4 }, + { "3.txt", -2 }, + { "2.txt", 0 }, + } + ); testCacheContent( "2.txt", - { - "5.txt", - "4.txt", - "3.txt", - "2.txt", - }, - { - { "5.txt", -6 }, - { "4.txt", -4 }, - { "3.txt", -2 }, - { "2.txt", 0 }, - } - ); + { + "5.txt", + "4.txt", + "3.txt", + "2.txt", + }, + { + { "5.txt", -6 }, + { "4.txt", -4 }, + { "3.txt", -2 }, + { "2.txt", 0 }, + } + ); } }; From f67d7ba50563ca5ead3779aff576035935768914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Thu, 10 Sep 2026 08:24:26 +0200 Subject: [PATCH 12/13] ci fixes --- lib/errorlogger.cpp | 2 +- lib/errorlogger.h | 6 +++--- test/testerrorlogger.cpp | 38 ++++++++++++++++++++------------------ 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp index 8ac95302697..388f8113565 100644 --- a/lib/errorlogger.cpp +++ b/lib/errorlogger.cpp @@ -1293,7 +1293,6 @@ ErrorLogger::SourceCacheEntry::SourceCacheEntry(const std::string &file, int pri : prio(prio) , file(file) , mStream(std::ifstream(file)) - , mLinenr(0) {} bool ErrorLogger::SourceCacheEntry::operator<(const ErrorLogger::SourceCacheEntry &rhs) const @@ -1306,6 +1305,7 @@ std::string ErrorLogger::SourceCacheEntry::getLine(int linenr) { if (linenr < mLinenr) { mLinenr = 0; + mLine.clear(); mStream.clear(); mStream.seekg(0); } diff --git a/lib/errorlogger.h b/lib/errorlogger.h index db4cefe09b0..3bc46a5e54d 100644 --- a/lib/errorlogger.h +++ b/lib/errorlogger.h @@ -326,9 +326,9 @@ class CPPCHECKLIB ErrorLogger { protected: virtual std::size_t getSourceCacheSize() const { return 4; - }; + } - class SourceCacheEntry { + class CPPCHECKLIB SourceCacheEntry { public: explicit SourceCacheEntry(const std::string &file, int prio); @@ -342,7 +342,7 @@ class CPPCHECKLIB ErrorLogger { private: std::ifstream mStream; std::string mLine; - int mLinenr; + int mLinenr{0}; }; std::vector> mSourceCache; diff --git a/test/testerrorlogger.cpp b/test/testerrorlogger.cpp index 2492b5a709c..e9b15cb9688 100644 --- a/test/testerrorlogger.cpp +++ b/test/testerrorlogger.cpp @@ -872,18 +872,18 @@ class TestErrorLogger : public TestFixture { public: friend class TestErrorLogger; - virtual void reportOut(const std::string &outmsg, Color c) + void reportOut(const std::string &outmsg, Color c) override { (void) outmsg; (void) c; } - virtual void reportErr(const ErrorMessage &msg) + void reportErr(const ErrorMessage &msg) override { (void) msg.toString(false, "{code}", "{code}", getSourceLineCallback()); } - virtual void reportMetric(const std::string &metric) + void reportMetric(const std::string &metric) override { (void) metric; } @@ -899,7 +899,7 @@ class TestErrorLogger : public TestFixture { private: // Override this in case it's changed in the main implementation - virtual std::size_t getSourceCacheSize() const override { + std::size_t getSourceCacheSize() const override { return 4; } }; @@ -910,8 +910,8 @@ class TestErrorLogger : public TestFixture { void testCacheContent_(const char *testfile, int testline, const std::string &file, - std::vector &&callstackFiles, - std::vector &&content) + const std::vector &callstackFiles, + const std::vector &content) { const auto heapCompare = [](const std::shared_ptr &lhs, const std::shared_ptr &rhs) @@ -920,8 +920,12 @@ class TestErrorLogger : public TestFixture { }; std::list callstack; - for (const auto &file : callstackFiles) - callstack.emplace_back(file, 1, 1); + std::transform(callstackFiles.cbegin(), + callstackFiles.cend(), + std::back_inserter(callstack), + [](const std::string &filename) { + return ErrorMessage::FileLocation(filename, 1, 1); + }); const ErrorMessage msg(callstack, file, @@ -935,10 +939,10 @@ class TestErrorLogger : public TestFixture { ASSERT_EQUALS(content.size(), copy.size()); std::make_heap(copy.begin(), copy.end(), heapCompare); - for (const auto match : content) { + for (const auto &match : content) { std::pop_heap(copy.begin(), copy.end(), heapCompare); - ASSERT_EQUALS(match.file, copy.back()->file); - ASSERT_EQUALS(match.prio, copy.back()->prio); + ASSERT_EQUALS_LOC(match.file, copy.back()->file, testfile, testline); + ASSERT_EQUALS_LOC(match.prio, copy.back()->prio, testfile, testline); copy.pop_back(); } } @@ -948,13 +952,11 @@ class TestErrorLogger : public TestFixture { "second line\n" "third line\n"; - const ScopedFile files[] = { - { "1.txt", content }, - { "2.txt", content }, - { "3.txt", content }, - { "4.txt", content }, - { "5.txt", content }, - }; + ScopedFile file1("1.txt", content); + ScopedFile file2("2.txt", content); + ScopedFile file3("3.txt", content); + ScopedFile file4("4.txt", content); + ScopedFile file5("5.txt", content); testCacheContent( "1.txt", From 7598d900aedef1016855684d01adae275623cac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Thu, 10 Sep 2026 15:59:40 +0200 Subject: [PATCH 13/13] add cli test with multiple files --- test/cli/other_test.py | 130 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/test/cli/other_test.py b/test/cli/other_test.py index dbda01b2e3f..2f4124c3feb 100644 --- a/test/cli/other_test.py +++ b/test/cli/other_test.py @@ -4863,6 +4863,8 @@ def test_ipc_inline_suppressions(tmp_path): assert stdout_lines == stdout_exp assert stderr.splitlines() == [] +__strace_decorator = pytest.mark.skipif(sys.platform != 'linux' or 'ASAN_OPTIONS' in os.environ, reason="uses strace") + test_redundant_file_reads_params = [ ([], 2), (['--suppress=zerodiv'], 1), @@ -4870,7 +4872,7 @@ def test_ipc_inline_suppressions(tmp_path): (['--xml'], 1), ] -@pytest.mark.skipif(sys.platform != 'linux' or 'ASAN_OPTIONS' in os.environ, reason="uses strace") +@__strace_decorator @pytest.mark.parametrize('flags,expected', test_redundant_file_reads_params) def test_redundant_file_reads(tmpdir, flags, expected): source_pathname = os.path.join(tmpdir, 'test.c') @@ -4904,3 +4906,129 @@ def test_redundant_file_reads(tmpdir, flags, expected): assert proc.returncode == 0 assert stderr.splitlines()[-1].strip() == f'{expected} total'.encode('utf-8') + +@__strace_decorator +def test_errorlogger_sourcecache(tmpdir): + header_pathname = os.path.join(tmpdir, 'header.h') + file_1_pathname = os.path.join(tmpdir, 'file_1.c') + file_2_pathname = os.path.join(tmpdir, 'file_2.c') + file_3_pathname = os.path.join(tmpdir, 'file_3.c') + output_pathname = os.path.join(tmpdir, 'out.txt') + + # Project setup that results in error paths + # spanning multiple files (ctuuninitvar) + + header_content = """ +int func_1(int *ptr); +int func_2(int *ptr); +""" + + file_1_content = f""" +#include "{header_pathname}" + +int func_1(int *ptr) +{{ + return *ptr; +}} +""" + + file_2_content = f""" +#include "{header_pathname}" + +int func_2(int *ptr) +{{ + return *ptr; +}} +""" + + file_3_content = f""" +#include "{header_pathname}" + +int func_3(void) +{{ + int x, y; + return func_1(&x) + func_2(&y); +}} +""" + + with open(header_pathname, 'wt') as f: + f.write(header_content) + + with open(file_1_pathname, 'wt') as f: + f.write(file_1_content) + + with open(file_2_pathname, 'wt') as f: + f.write(file_2_content) + + with open(file_3_pathname, 'wt') as f: + f.write(file_3_content) + + cppcheck_path = __lookup_cppcheck_exe() + + args = [ + 'strace', + '--summary-only', + '--summary-columns=count', + '--trace=openat', + '--follow-forks', + f'--trace-path={file_1_pathname}', + f'--trace-path={file_2_pathname}', + f'--trace-path={file_3_pathname}', + cppcheck_path, + '-q', + '--enable=all', + f'--output-file={output_pathname}', + file_1_pathname, + file_2_pathname, + file_3_pathname, + ] + + proc = subprocess.Popen(args, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE) + _, strace_output = proc.communicate() + + expected_cppcheck_output = f"""{file_1_pathname}:4:17: style: Parameter 'ptr' can be declared as pointer to const [constParameterPointer] +int func_1(int *ptr) + ^ +{file_2_pathname}:4:17: style: Parameter 'ptr' can be declared as pointer to const [constParameterPointer] +int func_2(int *ptr) + ^ +{file_1_pathname}:6:13: error: Using argument ptr that points at uninitialized variable x [ctuuninitvar] + return *ptr; + ^ +{file_3_pathname}:7:18: note: Calling function func_1, 1st argument is uninitialized + return func_1(&x) + func_2(&y); + ^ +{file_1_pathname}:6:13: note: Using argument ptr + return *ptr; + ^ +{file_2_pathname}:6:13: error: Using argument ptr that points at uninitialized variable y [ctuuninitvar] + return *ptr; + ^ +{file_3_pathname}:7:31: note: Calling function func_2, 1st argument is uninitialized + return func_1(&x) + func_2(&y); + ^ +{file_2_pathname}:6:13: note: Using argument ptr + return *ptr; + ^ +{file_3_pathname}:4:5: style: The function 'func_3' is never used. [unusedFunction] +int func_3(void) + ^ +nofile:0:0: information: Active checkers: 114/188 (use --checkers-report= to see details) [checkersReport] + +""" + + # Each source file is opened exactly twice: once for analysis, + # once for error reporting. If ErrorLogger::mSourceCacheSize + # is ever changed, this may have to be updated. + expected_strace_output = """ calls syscall +--------- ---------------- + 6 openat +--------- ---------------- + 6 total +""" + + with open(output_pathname, 'r') as f: + output_content = f.read() + + assert output_content == expected_cppcheck_output + assert strace_output.decode('utf-8') == expected_strace_output