From da7b999d0bae68614666cae499febaf3c5f79811 Mon Sep 17 00:00:00 2001 From: William Jakobsson <50847546+wjakobsson@users.noreply.github.com> Date: Thu, 2 Jul 2026 14:54:00 +0200 Subject: [PATCH 01/12] Fix recheck --- gui/mainwindow.cpp | 23 ++++++++++++++--------- gui/mainwindow.h | 12 ++++++++---- gui/resultstree.cpp | 15 +++++++++++++-- gui/resultstree.h | 6 ++++++ lib/analyzerinfo.cpp | 42 +++++++++++++++++++++++++++++++++++++----- lib/analyzerinfo.h | 5 +++-- 6 files changed, 81 insertions(+), 22 deletions(-) diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 3ed37eb7b25..e5305531b94 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -171,7 +171,7 @@ MainWindow::MainWindow(TranslationHandler* th, QSettings* settings) : connect(mUI->mActionAnalyzeFiles, &QAction::triggered, this, &MainWindow::analyzeFiles); connect(mUI->mActionAnalyzeDirectory, &QAction::triggered, this, &MainWindow::analyzeDirectory); connect(mUI->mActionSettings, &QAction::triggered, this, &MainWindow::programSettings); - connect(mUI->mActionClearResults, &QAction::triggered, this, &MainWindow::clearResults); + connect(mUI->mActionClearResults, &QAction::triggered, this, [this]() { clearResults(); }); connect(mUI->mActionOpenXML, &QAction::triggered, this, &MainWindow::openResults); connect(mUI->mActionShowStyle, &QAction::toggled, this, &MainWindow::showStyle); @@ -582,7 +582,7 @@ void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLib, const bo }); } - clearResults(); + clearResults(recheckFiles); mIsLogfileLoaded = false; if (mProjectFile) { @@ -628,7 +628,7 @@ void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLib, const bo if (!checkSettings.buildDir.empty()) { checkSettings.loadSummaries(); std::list sourcefiles; - AnalyzerInformation::writeFilesTxt(checkSettings.buildDir, sourcefiles, p.fileSettings); + AnalyzerInformation::writeFilesTxt(checkSettings.buildDir, sourcefiles, p.fileSettings, !recheckFiles.isEmpty()); } //mThread->SetanalyzeProject(true); @@ -647,7 +647,7 @@ void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLib, const bo mUI->mResults->setCheckSettings(checkSettings); } -void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, const bool checkConfig) +void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, const bool checkConfig, const bool partialRecheck) { if (files.isEmpty()) return; @@ -657,7 +657,7 @@ void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, c if (!getCppcheckSettings(checkSettings, *supprs)) return; - clearResults(); + clearResults(partialRecheck ? files : QStringList()); mIsLogfileLoaded = false; FileList pathList; @@ -669,7 +669,6 @@ void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, c } QStringList fileNames = pathList.getFileList(); - mUI->mResults->clear(true); mUI->mResults->setResultsSource(ResultsTree::ResultsSource::Analysis); mThread->clearFiles(); @@ -707,7 +706,7 @@ void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, c if (!checkSettings.buildDir.empty()) { checkSettings.loadSummaries(); const auto& sourcefiles = toStdStringList>(fileNames); - AnalyzerInformation::writeFilesTxt(checkSettings.buildDir, sourcefiles, {}); + AnalyzerInformation::writeFilesTxt(checkSettings.buildDir, sourcefiles, {}, partialRecheck); } mThread->setCheckFiles(true); @@ -1448,8 +1447,14 @@ void MainWindow::reAnalyze(bool all) mUI->mResults->setCheckSettings(checkSettings); } -void MainWindow::clearResults() +void MainWindow::clearResults(const QStringList& selectedFiles) { + if (!selectedFiles.isEmpty()) { + mUI->mResults->clear(false); + for (QString f : selectedFiles) + mUI->mResults->clearRecheckFile(f); + return; + } if (mProjectFile && !mProjectFile->getBuildDir().isEmpty()) { QDir dir(QFileInfo(mProjectFile->getFilename()).absolutePath() + '/' + mProjectFile->getBuildDir()); for (const QString& f: dir.entryList(QDir::Files)) { @@ -1981,7 +1986,7 @@ void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringLis if (paths.isEmpty()) { paths << mCurrentDirectory; } - doAnalyzeFiles(paths, checkLib, checkConfig); + doAnalyzeFiles(paths, checkLib, checkConfig, !recheckFiles.isEmpty()); } void MainWindow::newProjectFile() diff --git a/gui/mainwindow.h b/gui/mainwindow.h index c654a2c94f0..e3c727ae297 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -107,8 +107,11 @@ public slots: /** @brief Slot to reanalyze modified files */ void reAnalyzeModified(); - /** @brief Slot to clear all search results */ - void clearResults(); + /** + * @brief Slot to clear all search results or selected + * @param selectedFiles list to clear, leave empty for all + */ + void clearResults(const QStringList& selectedFiles = QStringList()); /** @brief Slot to open XML report file */ void openResults(); @@ -309,7 +312,7 @@ private slots: * @param checkLib Flag to indicate if library should be checked * @param checkConfig Flag to indicate if the configuration should be checked. */ - void doAnalyzeProject(ImportProject p, bool checkLib = false, bool checkConfig = false, const QStringList& recheckFiles = QStringList()); + void doAnalyzeProject(ImportProject p, bool checkLib = false, bool checkConfig = false, const QStringList &recheckFiles = QStringList()); /** * @brief Analyze all files specified in parameter files @@ -317,8 +320,9 @@ private slots: * @param files List of files and/or directories to analyze * @param checkLib Flag to indicate if library should be checked * @param checkConfig Flag to indicate if the configuration should be checked. + * @param partialRecheck Flag to indicate a partial recheck. */ - void doAnalyzeFiles(const QStringList &files, bool checkLib = false, bool checkConfig = false); + void doAnalyzeFiles(const QStringList &files, bool checkLib = false, bool checkConfig = false, const bool partialRecheck = false); /** * @brief Get our default cppcheck settings and read project file. diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp index ed8b3c7412e..77c1e37f4b1 100644 --- a/gui/resultstree.cpp +++ b/gui/resultstree.cpp @@ -427,7 +427,7 @@ void ResultsTree::clear(const QString &filename) if (stripped == fileItem->text() || filename == fileItem->errorItem->file0) { - mErrorList.removeAll(fileItem->errorItem->toString()); + removeFileErrorsFromErrorList(fileItem); mModel->removeRow(i); break; } @@ -445,7 +445,7 @@ void ResultsTree::clearRecheckFile(const QString &filename) QString storedfile = fileItem->getErrorPathItem().file; storedfile = ((!mCheckPath.isEmpty() && storedfile.startsWith(mCheckPath)) ? storedfile.mid(mCheckPath.length() + 1) : storedfile); if (actualfile == storedfile) { - mErrorList.removeAll(fileItem->errorItem->toString()); + removeFileErrorsFromErrorList(fileItem); mModel->removeRow(i); break; } @@ -1132,6 +1132,16 @@ void ResultsTree::saveErrors(Report *report, const ResultItem *fileItem) const } } +void ResultsTree::removeFileErrorsFromErrorList(const ResultItem *fileItem) +{ + for (int i{0}; i < fileItem->rowCount(); ++i) { + const auto *errorItem = dynamic_cast(fileItem->child(i, COLUMN_FILE)); + if (errorItem && errorItem->errorItem) + mErrorList.removeAll(errorItem->errorItem->toString()); + } + mErrorList.removeAll(fileItem->errorItem->toString()); +} + void ResultsTree::updateFromOldReport(const QString &filename) { showColumn(COLUMN_SINCE_DATE); @@ -1195,6 +1205,7 @@ void ResultsTree::updateSettings(bool showFullPath, void ResultsTree::setCheckDirectory(const QString &dir) { mCheckPath = dir; + refreshFilePaths(); } const QString& ResultsTree::getCheckDirectory() const diff --git a/gui/resultstree.h b/gui/resultstree.h index 23fe5567ed9..b0634e96396 100644 --- a/gui/resultstree.h +++ b/gui/resultstree.h @@ -356,6 +356,12 @@ protected slots: */ void saveErrors(Report *report, const ResultItem *fileItem) const; + /** + * @brief Remove all errors of a file item from the duplicate-check list + * @param fileItem Item whose errors to remove + */ + void removeFileErrorsFromErrorList(const ResultItem *fileItem); + /** * @brief Convert a severity string to a icon filename * diff --git a/lib/analyzerinfo.cpp b/lib/analyzerinfo.cpp index aa8d82c27fa..324f57b22ee 100644 --- a/lib/analyzerinfo.cpp +++ b/lib/analyzerinfo.cpp @@ -24,10 +24,12 @@ #include "utils.h" #include +#include #include #include #include #include +#include #include #include #include @@ -51,11 +53,43 @@ static std::string getFilename(const std::string &fullpath) return fullpath.substr(pos1,pos2); } -void AnalyzerInformation::writeFilesTxt(const std::string &buildDir, const std::list &sourcefiles, const std::list &fileSettings) +void AnalyzerInformation::writeFilesTxt(const std::string &buildDir, const std::list &sourcefiles, const std::list &fileSettings, bool merge) { const std::string filesTxt(buildDir + "/files.txt"); + + std::string keptLines; + std::map fileCount; + + if (merge) { + std::set current; + for (const std::string &f : sourcefiles) + current.insert(Path::simplifyPath(f)); + for (const FileSettings &fs : fileSettings) + current.insert(Path::simplifyPath(fs.filename())); + + std::ifstream fin(filesTxt); + std::string line; + while (std::getline(fin, line)) { + Info info; + if (!info.parse(line)) + continue; + if (current.count(info.sourceFile)) + continue; + keptLines += line + '\n'; + + const std::string::size_type dotA = info.afile.rfind(".a"); + if (dotA != std::string::npos) { + const std::string base = info.afile.substr(0, dotA); + const unsigned int n = std::strtoul(info.afile.c_str() + dotA + 2, nullptr, 10); + unsigned int &count = fileCount[base]; + count = std::max(count, n); + } + } + } + std::ofstream fout(filesTxt); - fout << getFilesTxt(sourcefiles, fileSettings); + fout << keptLines; + fout << getFilesTxt(sourcefiles, fileSettings, fileCount); } void AnalyzerInformation::writeIncludes(const std::set &files) @@ -120,11 +154,9 @@ void AnalyzerInformation::writeHash(std::size_t hash) } } -std::string AnalyzerInformation::getFilesTxt(const std::list &sourcefiles, const std::list &fileSettings) { +std::string AnalyzerInformation::getFilesTxt(const std::list &sourcefiles, const std::list &fileSettings, std::map fileCount) { std::ostringstream ret; - std::map fileCount; - for (const std::string &f : sourcefiles) { const std::string afile = getFilename(f); ret << afile << ".a" << (++fileCount[afile]) << sep << sep << sep << Path::simplifyPath(f) << '\n'; diff --git a/lib/analyzerinfo.h b/lib/analyzerinfo.h index 75ff519db84..cf99c575ccf 100644 --- a/lib/analyzerinfo.h +++ b/lib/analyzerinfo.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -58,7 +59,7 @@ class CPPCHECKLIB AnalyzerInformation { public: ~AnalyzerInformation(); - static void writeFilesTxt(const std::string &buildDir, const std::list &sourcefiles, const std::list &fileSettings); + static void writeFilesTxt(const std::string &buildDir, const std::list &sourcefiles, const std::list &fileSettings, bool merge = false); /** Close current TU.analyzerinfo file */ void close(); @@ -89,7 +90,7 @@ class CPPCHECKLIB AnalyzerInformation { 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 getFilesTxt(const std::list &sourcefiles, const std::list &fileSettings, std::map fileCount = {}); static std::string getAnalyzerInfoFileFromFilesTxt(std::istream& filesTxt, const std::string &sourcefile, const std::string &cfg, size_t fsFileId); From f5c2109383de8e44cf6fa641d9dfb756e5ad47c7 Mon Sep 17 00:00:00 2001 From: William Jakobsson <50847546+wjakobsson@users.noreply.github.com> Date: Mon, 6 Jul 2026 13:31:45 +0200 Subject: [PATCH 02/12] nits --- gui/mainwindow.cpp | 6 +++--- gui/mainwindow.h | 2 +- lib/analyzerinfo.cpp | 42 +++++------------------------------------- lib/analyzerinfo.h | 5 ++--- 4 files changed, 11 insertions(+), 44 deletions(-) diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index e5305531b94..0908587bee3 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -628,7 +628,7 @@ void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLib, const bo if (!checkSettings.buildDir.empty()) { checkSettings.loadSummaries(); std::list sourcefiles; - AnalyzerInformation::writeFilesTxt(checkSettings.buildDir, sourcefiles, p.fileSettings, !recheckFiles.isEmpty()); + AnalyzerInformation::writeFilesTxt(checkSettings.buildDir, sourcefiles, p.fileSettings); } //mThread->SetanalyzeProject(true); @@ -706,7 +706,7 @@ void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, c if (!checkSettings.buildDir.empty()) { checkSettings.loadSummaries(); const auto& sourcefiles = toStdStringList>(fileNames); - AnalyzerInformation::writeFilesTxt(checkSettings.buildDir, sourcefiles, {}, partialRecheck); + AnalyzerInformation::writeFilesTxt(checkSettings.buildDir, sourcefiles, {}); } mThread->setCheckFiles(true); @@ -1451,7 +1451,7 @@ void MainWindow::clearResults(const QStringList& selectedFiles) { if (!selectedFiles.isEmpty()) { mUI->mResults->clear(false); - for (QString f : selectedFiles) + for (const QString& f : selectedFiles) mUI->mResults->clearRecheckFile(f); return; } diff --git a/gui/mainwindow.h b/gui/mainwindow.h index e3c727ae297..f632598cccb 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -322,7 +322,7 @@ private slots: * @param checkConfig Flag to indicate if the configuration should be checked. * @param partialRecheck Flag to indicate a partial recheck. */ - void doAnalyzeFiles(const QStringList &files, bool checkLib = false, bool checkConfig = false, const bool partialRecheck = false); + void doAnalyzeFiles(const QStringList &files, bool checkLib = false, bool checkConfig = false, bool partialRecheck = false); /** * @brief Get our default cppcheck settings and read project file. diff --git a/lib/analyzerinfo.cpp b/lib/analyzerinfo.cpp index 324f57b22ee..aa8d82c27fa 100644 --- a/lib/analyzerinfo.cpp +++ b/lib/analyzerinfo.cpp @@ -24,12 +24,10 @@ #include "utils.h" #include -#include #include #include #include #include -#include #include #include #include @@ -53,43 +51,11 @@ static std::string getFilename(const std::string &fullpath) return fullpath.substr(pos1,pos2); } -void AnalyzerInformation::writeFilesTxt(const std::string &buildDir, const std::list &sourcefiles, const std::list &fileSettings, bool merge) +void AnalyzerInformation::writeFilesTxt(const std::string &buildDir, const std::list &sourcefiles, const std::list &fileSettings) { const std::string filesTxt(buildDir + "/files.txt"); - - std::string keptLines; - std::map fileCount; - - if (merge) { - std::set current; - for (const std::string &f : sourcefiles) - current.insert(Path::simplifyPath(f)); - for (const FileSettings &fs : fileSettings) - current.insert(Path::simplifyPath(fs.filename())); - - std::ifstream fin(filesTxt); - std::string line; - while (std::getline(fin, line)) { - Info info; - if (!info.parse(line)) - continue; - if (current.count(info.sourceFile)) - continue; - keptLines += line + '\n'; - - const std::string::size_type dotA = info.afile.rfind(".a"); - if (dotA != std::string::npos) { - const std::string base = info.afile.substr(0, dotA); - const unsigned int n = std::strtoul(info.afile.c_str() + dotA + 2, nullptr, 10); - unsigned int &count = fileCount[base]; - count = std::max(count, n); - } - } - } - std::ofstream fout(filesTxt); - fout << keptLines; - fout << getFilesTxt(sourcefiles, fileSettings, fileCount); + fout << getFilesTxt(sourcefiles, fileSettings); } void AnalyzerInformation::writeIncludes(const std::set &files) @@ -154,9 +120,11 @@ void AnalyzerInformation::writeHash(std::size_t hash) } } -std::string AnalyzerInformation::getFilesTxt(const std::list &sourcefiles, const std::list &fileSettings, std::map fileCount) { +std::string AnalyzerInformation::getFilesTxt(const std::list &sourcefiles, const std::list &fileSettings) { std::ostringstream ret; + std::map fileCount; + for (const std::string &f : sourcefiles) { const std::string afile = getFilename(f); ret << afile << ".a" << (++fileCount[afile]) << sep << sep << sep << Path::simplifyPath(f) << '\n'; diff --git a/lib/analyzerinfo.h b/lib/analyzerinfo.h index cf99c575ccf..75ff519db84 100644 --- a/lib/analyzerinfo.h +++ b/lib/analyzerinfo.h @@ -27,7 +27,6 @@ #include #include #include -#include #include #include @@ -59,7 +58,7 @@ class CPPCHECKLIB AnalyzerInformation { public: ~AnalyzerInformation(); - static void writeFilesTxt(const std::string &buildDir, const std::list &sourcefiles, const std::list &fileSettings, bool merge = false); + static void writeFilesTxt(const std::string &buildDir, const std::list &sourcefiles, const std::list &fileSettings); /** Close current TU.analyzerinfo file */ void close(); @@ -90,7 +89,7 @@ class CPPCHECKLIB AnalyzerInformation { 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, std::map fileCount = {}); + 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, size_t fsFileId); From c0a79a683319aa6ee84f6c432df1e0238a0e39ee Mon Sep 17 00:00:00 2001 From: William Jakobsson <50847546+wjakobsson@users.noreply.github.com> Date: Mon, 6 Jul 2026 14:29:37 +0200 Subject: [PATCH 03/12] nits --- gui/mainwindow.cpp | 4 +++- gui/mainwindow.h | 2 +- gui/resultstree.cpp | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 0908587bee3..167d2f3c36b 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -171,7 +171,9 @@ MainWindow::MainWindow(TranslationHandler* th, QSettings* settings) : connect(mUI->mActionAnalyzeFiles, &QAction::triggered, this, &MainWindow::analyzeFiles); connect(mUI->mActionAnalyzeDirectory, &QAction::triggered, this, &MainWindow::analyzeDirectory); connect(mUI->mActionSettings, &QAction::triggered, this, &MainWindow::programSettings); - connect(mUI->mActionClearResults, &QAction::triggered, this, [this]() { clearResults(); }); + connect(mUI->mActionClearResults, &QAction::triggered, this, [this]() { + clearResults(); + }); connect(mUI->mActionOpenXML, &QAction::triggered, this, &MainWindow::openResults); connect(mUI->mActionShowStyle, &QAction::toggled, this, &MainWindow::showStyle); diff --git a/gui/mainwindow.h b/gui/mainwindow.h index f632598cccb..72b11c49ec4 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -110,7 +110,7 @@ public slots: /** * @brief Slot to clear all search results or selected * @param selectedFiles list to clear, leave empty for all - */ + */ void clearResults(const QStringList& selectedFiles = QStringList()); /** @brief Slot to open XML report file */ diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp index 77c1e37f4b1..408866fc2ef 100644 --- a/gui/resultstree.cpp +++ b/gui/resultstree.cpp @@ -1139,7 +1139,6 @@ void ResultsTree::removeFileErrorsFromErrorList(const ResultItem *fileItem) if (errorItem && errorItem->errorItem) mErrorList.removeAll(errorItem->errorItem->toString()); } - mErrorList.removeAll(fileItem->errorItem->toString()); } void ResultsTree::updateFromOldReport(const QString &filename) From 082d3f03fb82328b7607c586d3a3ac8af4978e81 Mon Sep 17 00:00:00 2001 From: William Jakobsson <50847546+wjakobsson@users.noreply.github.com> Date: Mon, 6 Jul 2026 15:32:10 +0200 Subject: [PATCH 04/12] small fix --- gui/mainwindow.cpp | 4 ++-- lib/analyzerinfo.cpp | 42 +++++++++++++++++++++++++++++++++++++----- lib/analyzerinfo.h | 5 +++-- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 167d2f3c36b..8777644d9ee 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -630,7 +630,7 @@ void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLib, const bo if (!checkSettings.buildDir.empty()) { checkSettings.loadSummaries(); std::list sourcefiles; - AnalyzerInformation::writeFilesTxt(checkSettings.buildDir, sourcefiles, p.fileSettings); + AnalyzerInformation::writeFilesTxt(checkSettings.buildDir, sourcefiles, p.fileSettings, !recheckFiles.isEmpty()); } //mThread->SetanalyzeProject(true); @@ -708,7 +708,7 @@ void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, c if (!checkSettings.buildDir.empty()) { checkSettings.loadSummaries(); const auto& sourcefiles = toStdStringList>(fileNames); - AnalyzerInformation::writeFilesTxt(checkSettings.buildDir, sourcefiles, {}); + AnalyzerInformation::writeFilesTxt(checkSettings.buildDir, sourcefiles, {}, partialRecheck); } mThread->setCheckFiles(true); diff --git a/lib/analyzerinfo.cpp b/lib/analyzerinfo.cpp index aa8d82c27fa..324f57b22ee 100644 --- a/lib/analyzerinfo.cpp +++ b/lib/analyzerinfo.cpp @@ -24,10 +24,12 @@ #include "utils.h" #include +#include #include #include #include #include +#include #include #include #include @@ -51,11 +53,43 @@ static std::string getFilename(const std::string &fullpath) return fullpath.substr(pos1,pos2); } -void AnalyzerInformation::writeFilesTxt(const std::string &buildDir, const std::list &sourcefiles, const std::list &fileSettings) +void AnalyzerInformation::writeFilesTxt(const std::string &buildDir, const std::list &sourcefiles, const std::list &fileSettings, bool merge) { const std::string filesTxt(buildDir + "/files.txt"); + + std::string keptLines; + std::map fileCount; + + if (merge) { + std::set current; + for (const std::string &f : sourcefiles) + current.insert(Path::simplifyPath(f)); + for (const FileSettings &fs : fileSettings) + current.insert(Path::simplifyPath(fs.filename())); + + std::ifstream fin(filesTxt); + std::string line; + while (std::getline(fin, line)) { + Info info; + if (!info.parse(line)) + continue; + if (current.count(info.sourceFile)) + continue; + keptLines += line + '\n'; + + const std::string::size_type dotA = info.afile.rfind(".a"); + if (dotA != std::string::npos) { + const std::string base = info.afile.substr(0, dotA); + const unsigned int n = std::strtoul(info.afile.c_str() + dotA + 2, nullptr, 10); + unsigned int &count = fileCount[base]; + count = std::max(count, n); + } + } + } + std::ofstream fout(filesTxt); - fout << getFilesTxt(sourcefiles, fileSettings); + fout << keptLines; + fout << getFilesTxt(sourcefiles, fileSettings, fileCount); } void AnalyzerInformation::writeIncludes(const std::set &files) @@ -120,11 +154,9 @@ void AnalyzerInformation::writeHash(std::size_t hash) } } -std::string AnalyzerInformation::getFilesTxt(const std::list &sourcefiles, const std::list &fileSettings) { +std::string AnalyzerInformation::getFilesTxt(const std::list &sourcefiles, const std::list &fileSettings, std::map fileCount) { std::ostringstream ret; - std::map fileCount; - for (const std::string &f : sourcefiles) { const std::string afile = getFilename(f); ret << afile << ".a" << (++fileCount[afile]) << sep << sep << sep << Path::simplifyPath(f) << '\n'; diff --git a/lib/analyzerinfo.h b/lib/analyzerinfo.h index 75ff519db84..cf99c575ccf 100644 --- a/lib/analyzerinfo.h +++ b/lib/analyzerinfo.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -58,7 +59,7 @@ class CPPCHECKLIB AnalyzerInformation { public: ~AnalyzerInformation(); - static void writeFilesTxt(const std::string &buildDir, const std::list &sourcefiles, const std::list &fileSettings); + static void writeFilesTxt(const std::string &buildDir, const std::list &sourcefiles, const std::list &fileSettings, bool merge = false); /** Close current TU.analyzerinfo file */ void close(); @@ -89,7 +90,7 @@ class CPPCHECKLIB AnalyzerInformation { 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 getFilesTxt(const std::list &sourcefiles, const std::list &fileSettings, std::map fileCount = {}); static std::string getAnalyzerInfoFileFromFilesTxt(std::istream& filesTxt, const std::string &sourcefile, const std::string &cfg, size_t fsFileId); From 16fadf53a7f44895612ff6d629625968e5e85a82 Mon Sep 17 00:00:00 2001 From: William Jakobsson <50847546+wjakobsson@users.noreply.github.com> Date: Mon, 6 Jul 2026 15:55:03 +0200 Subject: [PATCH 05/12] fix github warning --- lib/analyzerinfo.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/analyzerinfo.cpp b/lib/analyzerinfo.cpp index 324f57b22ee..0f62b4e18af 100644 --- a/lib/analyzerinfo.cpp +++ b/lib/analyzerinfo.cpp @@ -24,7 +24,6 @@ #include "utils.h" #include -#include #include #include #include @@ -80,9 +79,11 @@ void AnalyzerInformation::writeFilesTxt(const std::string &buildDir, const std:: const std::string::size_type dotA = info.afile.rfind(".a"); if (dotA != std::string::npos) { const std::string base = info.afile.substr(0, dotA); - const unsigned int n = std::strtoul(info.afile.c_str() + dotA + 2, nullptr, 10); - unsigned int &count = fileCount[base]; - count = std::max(count, n); + unsigned int n = 0; + if (strToInt(info.afile.substr(dotA + 2), n)) { + unsigned int &count = fileCount[base]; + count = std::max(count, n); + } } } } From da4e4026877523c3f996ef0ca5d248df72fcba5f Mon Sep 17 00:00:00 2001 From: William Jakobsson <50847546+wjakobsson@users.noreply.github.com> Date: Thu, 16 Jul 2026 14:32:27 +0200 Subject: [PATCH 06/12] make addons use cache --- lib/cppcheck.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index 8e32e3e3152..4852da9c188 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -1681,7 +1681,7 @@ void CppCheck::executeAddons(const std::vector& files, const std::s } } -void CppCheck::executeAddonsWholeProgram(const std::list &files, const std::list& fileSettings, const std::string& ctuInfo) +void CppCheck::executeAddonsWholeProgram(const std::list& /*files*/, const std::list& /*fileSettings*/, const std::string& ctuInfo) { if (mSettings.addons.empty()) return; @@ -1698,13 +1698,19 @@ void CppCheck::executeAddonsWholeProgram(const std::list &files } std::vector ctuInfoFiles; - for (const auto &f: files) { - const std::string &dumpFileName = getDumpFileName(mSettings, f); - ctuInfoFiles.push_back(getCtuInfoFileName(dumpFileName)); - } - - for (const auto &fs: fileSettings) { - const std::string &dumpFileName = getDumpFileName(mSettings, fs.file); + std::set> seen; + std::ifstream filesTxt(mSettings.buildDir + "/files.txt"); + std::string line; + while (std::getline(filesTxt, line)) { + AnalyzerInformation::Info info; + if (!info.parse(line) || info.sourceFile.empty()) + continue; + if (!seen.emplace(info.sourceFile, info.fsFileId).second) + continue; + std::string dumpFileName = AnalyzerInformation::getAnalyzerInfoFile(mSettings.buildDir, info.sourceFile, "", info.fsFileId); + if (info.fsFileId > 0) + dumpFileName += '.' + std::to_string(info.fsFileId); + dumpFileName += ".dump"; ctuInfoFiles.push_back(getCtuInfoFileName(dumpFileName)); } From fcb396be280775d5f1eba5b6203f407aff55a81b Mon Sep 17 00:00:00 2001 From: William Jakobsson <50847546+wjakobsson@users.noreply.github.com> Date: Tue, 4 Aug 2026 13:45:16 +0200 Subject: [PATCH 07/12] WIP cache --- lib/errorlogger.cpp | 54 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp index 30097a4f21c..13eb6e7721e 100644 --- a/lib/errorlogger.cpp +++ b/lib/errorlogger.cpp @@ -36,11 +36,13 @@ #include #include #include -#include +#include +#include #include #include #include #include +#include #include "xml.h" @@ -639,13 +641,57 @@ std::string ErrorMessage::toXML() const return printer.CStr(); } +// Byte offset of the start of each line, indexed by 1-based line number. +// Building this once per file keeps readCode() cheap. Without it every call +// rescans the file from the start, which is quadratic for a file that produces +// many findings - vendor/generated headers routinely produce tens of thousands. +static const std::vector* getLineOffsets(const std::string &file) +{ + // thread_local: the thread executor calls this concurrently + static thread_local std::list>> cache; + + for (auto it = cache.begin(); it != cache.end(); ++it) { + if (it->first == file) { + // most recently used goes first + cache.splice(cache.begin(), cache, it); + return &cache.front().second; + } + } + + // Binary mode so the offsets match what the seek in readCode() expects. + // A trailing '\r' is stripped by the caller. + std::ifstream fin(file, std::ios::binary); + if (!fin.is_open()) + return nullptr; + + std::vector offsets{0, 0}; // index 0 is unused, line 1 starts at offset 0 + std::array buf; + std::streamoff pos = 0; + while (fin.read(buf.data(), buf.size()) || fin.gcount() > 0) { + const std::streamsize n = fin.gcount(); + for (std::streamsize i = 0; i < n; ++i) { + if (buf[i] == '\n') + offsets.push_back(pos + i + 1); + } + pos += n; + } + + constexpr std::size_t maxCachedFiles = 4; + if (cache.size() >= maxCachedFiles) + cache.pop_back(); + cache.emplace_front(file, std::move(offsets)); + return &cache.front().second; +} + // 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::vector* const offsets = getLineOffsets(file); + if (offsets && linenr > 0 && linenr < static_cast(offsets->size())) { + std::ifstream fin(file, std::ios::binary); + fin.seekg((*offsets)[linenr]); + std::getline(fin, line); } const std::string::size_type endPos = line.find_last_not_of("\r\n\t "); if (endPos + 1 < line.size()) From dbc1c3c698e98b89531c224081ba64f3413ecf7e Mon Sep 17 00:00:00 2001 From: William Jakobsson <50847546+wjakobsson@users.noreply.github.com> Date: Mon, 17 Aug 2026 11:06:31 +0200 Subject: [PATCH 08/12] Include numeric --- lib/errorlogger.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp index 13eb6e7721e..0fd42e52ddd 100644 --- a/lib/errorlogger.cpp +++ b/lib/errorlogger.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include "xml.h" From 4c99c085288623c305d773b1c9ae8bb1d1096551 Mon Sep 17 00:00:00 2001 From: William Jakobsson <50847546+wjakobsson@users.noreply.github.com> Date: Fri, 23 Oct 2026 08:15:02 +0200 Subject: [PATCH 09/12] add fix --- gui/checkthread.cpp | 7 ++- gui/checkthread.h | 8 +-- gui/mainwindow.cpp | 117 ++++++++++++++++++++++++++++++------------ gui/mainwindow.h | 42 +++++++++++---- gui/resultstree.cpp | 9 +++- gui/threadhandler.cpp | 6 ++- gui/threadhandler.h | 7 +++ lib/analyzerinfo.cpp | 55 ++++++-------------- lib/analyzerinfo.h | 5 +- lib/cppcheck.cpp | 22 +++----- lib/errorlogger.cpp | 55 ++------------------ 11 files changed, 175 insertions(+), 158 deletions(-) diff --git a/gui/checkthread.cpp b/gui/checkthread.cpp index 07ca4e77ed2..2b4c9ba654f 100644 --- a/gui/checkthread.cpp +++ b/gui/checkthread.cpp @@ -113,13 +113,15 @@ CheckThread::CheckThread(ThreadResult &result, int threadIndex) void CheckThread::setSettings(const Settings &settings, std::shared_ptr supprs) { mFiles.clear(); + mFileSettings.clear(); mSettings = settings; // this is a copy mSuppressions = std::move(supprs); } -void CheckThread::analyseWholeProgram(const std::list &files, const std::string& ctuInfo) +void CheckThread::analyseWholeProgram(const std::list &files, const std::list &fileSettings, const std::string& ctuInfo) { mFiles = files; + mFileSettings = fileSettings; mAnalyseWholeProgram = true; mCtuInfo = ctuInfo; start(); @@ -136,8 +138,9 @@ void CheckThread::run() std::string ctuInfo; ctuInfo.swap(mCtuInfo); qDebug() << "Whole program analysis"; - cppcheck.analyseWholeProgram(mSettings.buildDir, mFiles, {}, ctuInfo); + cppcheck.analyseWholeProgram(mSettings.buildDir, mFiles, mFileSettings, ctuInfo); mFiles.clear(); + mFileSettings.clear(); emit done(); return; } diff --git a/gui/checkthread.h b/gui/checkthread.h index a630ef7fc13..74eb292e6e2 100644 --- a/gui/checkthread.h +++ b/gui/checkthread.h @@ -69,10 +69,11 @@ class CheckThread : public QThread { /** * @brief Run whole program analysis - * @param files All files - * @param ctuInfo Ctu info for addons + * @param files All files + * @param fileSettings All files of the project + * @param ctuInfo Ctu info for addons */ - void analyseWholeProgram(const std::list &files, const std::string& ctuInfo); + void analyseWholeProgram(const std::list &files, const std::list &fileSettings, const std::string& ctuInfo); void setAddonsAndTools(const QStringList &addonsAndTools) { mAddonsAndTools = addonsAndTools; @@ -153,6 +154,7 @@ class CheckThread : public QThread { bool isSuppressed(const SuppressionList::ErrorMessage &errorMessage) const; std::list mFiles; + std::list mFileSettings; bool mAnalyseWholeProgram{}; std::string mCtuInfo; QStringList mAddonsAndTools; diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 8777644d9ee..5ddb790b77b 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -59,6 +59,7 @@ #include #include +#include #include #include #include @@ -96,6 +97,7 @@ #include #include #include +#include #include #include #include @@ -118,6 +120,18 @@ static QString fromNativePath(const QString& p) { #endif } +// The files selected in the results view are not necessarily spelled the same way as the +// project or files.txt spells them, compare them as absolute paths +static QString comparablePath(const QString& path) { + return QDir::cleanPath(QFileInfo(path).absoluteFilePath()); +} + +static QSet comparablePaths(const QStringList& paths) { + QSet ret; + std::transform(paths.cbegin(), paths.cend(), std::inserter(ret, ret.end()), comparablePath); + return ret; +} + template> static T toStdStringList(const QStringList& stringList) { T ret; @@ -171,9 +185,7 @@ MainWindow::MainWindow(TranslationHandler* th, QSettings* settings) : connect(mUI->mActionAnalyzeFiles, &QAction::triggered, this, &MainWindow::analyzeFiles); connect(mUI->mActionAnalyzeDirectory, &QAction::triggered, this, &MainWindow::analyzeDirectory); connect(mUI->mActionSettings, &QAction::triggered, this, &MainWindow::programSettings); - connect(mUI->mActionClearResults, &QAction::triggered, this, [this]() { - clearResults(); - }); + connect(mUI->mActionClearResults, &QAction::triggered, this, &MainWindow::clearResults); connect(mUI->mActionOpenXML, &QAction::triggered, this, &MainWindow::openResults); connect(mUI->mActionShowStyle, &QAction::toggled, this, &MainWindow::showStyle); @@ -570,21 +582,31 @@ void MainWindow::saveSettings() const mUI->mResults->saveSettings(mSettings); } -void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLib, const bool checkConfig, const QStringList& recheckFiles) +void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLib, const bool checkConfig, const QStringList& selectedFiles, const Recheck recheck) { Settings checkSettings; auto supprs = std::make_shared(); if (!getCppcheckSettings(checkSettings, *supprs)) return; - // filter requested files - if (!recheckFiles.isEmpty()) { + // Filter the selected files + if (recheck == Recheck::Selected) { + const QSet selected = comparablePaths(selectedFiles); p.fileSettings.remove_if([&](const FileSettings& fs) { - return !recheckFiles.contains(QString::fromStdString(fs.filename())); + return !selected.contains(comparablePath(QString::fromStdString(fs.filename()))); }); + if (p.fileSettings.empty()) { + QMessageBox msg(QMessageBox::Information, + "Cppcheck", + tr("None of the selected files is part of the project, nothing to analyze."), + QMessageBox::Ok, + this); + msg.exec(); + return; + } } - clearResults(recheckFiles); + clearResultsForAnalysis(checkSettings.buildDir, selectedFiles, recheck); mIsLogfileLoaded = false; if (mProjectFile) { @@ -630,7 +652,7 @@ void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLib, const bo if (!checkSettings.buildDir.empty()) { checkSettings.loadSummaries(); std::list sourcefiles; - AnalyzerInformation::writeFilesTxt(checkSettings.buildDir, sourcefiles, p.fileSettings, !recheckFiles.isEmpty()); + AnalyzerInformation::writeFilesTxt(checkSettings.buildDir, sourcefiles, p.fileSettings); } //mThread->SetanalyzeProject(true); @@ -649,7 +671,7 @@ void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLib, const bo mUI->mResults->setCheckSettings(checkSettings); } -void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, const bool checkConfig, const bool partialRecheck) +void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, const bool checkConfig, const QStringList& selectedFiles, const Recheck recheck) { if (files.isEmpty()) return; @@ -659,7 +681,7 @@ void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, c if (!getCppcheckSettings(checkSettings, *supprs)) return; - clearResults(partialRecheck ? files : QStringList()); + clearResultsForAnalysis(checkSettings.buildDir, selectedFiles, recheck); mIsLogfileLoaded = false; FileList pathList; @@ -708,7 +730,7 @@ void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, c if (!checkSettings.buildDir.empty()) { checkSettings.loadSummaries(); const auto& sourcefiles = toStdStringList>(fileNames); - AnalyzerInformation::writeFilesTxt(checkSettings.buildDir, sourcefiles, {}, partialRecheck); + AnalyzerInformation::writeFilesTxt(checkSettings.buildDir, sourcefiles, {}); } mThread->setCheckFiles(true); @@ -1380,11 +1402,6 @@ void MainWindow::reAnalyzeSelected(const QStringList& files) return; if (mProjectFile) { - // Clear details, statistics and progress - mUI->mResults->clear(false); - for (int i = 0; i < files.size(); ++i) - mUI->mResults->clearRecheckFile(files[i]); - analyzeProject(mProjectFile, files); return; } @@ -1394,10 +1411,7 @@ void MainWindow::reAnalyzeSelected(const QStringList& files) if (!getCppcheckSettings(checkSettings, *supprs)) return; - // Clear details, statistics and progress - mUI->mResults->clear(false); - for (int i = 0; i < files.size(); ++i) - mUI->mResults->clearRecheckFile(files[i]); + clearResultsForAnalysis(checkSettings.buildDir, files, Recheck::Selected); mCurrentDirectory = mUI->mResults->getCheckDirectory(); FileList pathList; @@ -1449,16 +1463,10 @@ void MainWindow::reAnalyze(bool all) mUI->mResults->setCheckSettings(checkSettings); } -void MainWindow::clearResults(const QStringList& selectedFiles) +void MainWindow::clearResults() { - if (!selectedFiles.isEmpty()) { - mUI->mResults->clear(false); - for (const QString& f : selectedFiles) - mUI->mResults->clearRecheckFile(f); - return; - } if (mProjectFile && !mProjectFile->getBuildDir().isEmpty()) { - QDir dir(QFileInfo(mProjectFile->getFilename()).absolutePath() + '/' + mProjectFile->getBuildDir()); + QDir dir(QDir(QFileInfo(mProjectFile->getFilename()).absolutePath()).filePath(mProjectFile->getBuildDir())); for (const QString& f: dir.entryList(QDir::Files)) { if (!f.endsWith("files.txt")) { static const QRegularExpression rx("^.*.s[0-9]+$"); @@ -1472,6 +1480,42 @@ void MainWindow::clearResults(const QStringList& selectedFiles) enableResultsButtons(); } +void MainWindow::clearResultsForAnalysis(const std::string& buildDir, const QStringList& selectedFiles, const Recheck recheck) +{ + switch (recheck) { + case Recheck::None: + clearResults(); + break; + case Recheck::Selected: + // Only the selected files are analyzed, the results of the other files are kept + mUI->mResults->clear(false); + for (const QString& f : selectedFiles) + mUI->mResults->clearRecheckFile(f); + enableResultsButtons(); + break; + case Recheck::Cached: { + // Every file reports its results again, either analyzed or replayed from the analyzer + // information. Remove the analyzer information of the selected files so that they really + // are analyzed again, also by the clang tools that keep their results next to it. + const QSet selected = comparablePaths(selectedFiles); + QDir dir(QString::fromStdString(buildDir)); + std::ifstream filesTxt(buildDir + "/files.txt"); + std::string line; + while (std::getline(filesTxt, line)) { + AnalyzerInformation::Info info; + if (!info.parse(line) || !selected.contains(comparablePath(QString::fromStdString(info.sourceFile)))) + continue; + const QString afile = QString::fromStdString(info.afile); + for (const QString& f : dir.entryList({afile, afile + ".*"}, QDir::Files)) + dir.remove(f); + } + mUI->mResults->clear(true); + enableResultsButtons(); + break; + } + } +} + void MainWindow::openResults() { if (mUI->mResults->hasResults()) { @@ -1870,10 +1914,17 @@ bool MainWindow::loadLastResults() return true; } -void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringList& recheckFiles, const bool checkLib, const bool checkConfig) +void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringList& selectedFiles, const bool checkLib, const bool checkConfig) { Settings::terminate(false); + // With a build directory the whole project is analyzed: the analyzer information cache skips + // the unchanged files, and files.txt stays complete so whole program analysis sees every file. + // Without a build directory there is no cache, so only the selected files are analyzed. + Recheck recheck = Recheck::None; + if (!selectedFiles.isEmpty()) + recheck = projectFile->getBuildDir().isEmpty() ? Recheck::Selected : Recheck::Cached; + QFileInfo inf(projectFile->getFilename()); const QString& rootpath = projectFile->getRootPath(); @@ -1975,11 +2026,11 @@ void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringLis msg.exec(); return; } - doAnalyzeProject(p, checkLib, checkConfig, recheckFiles); // TODO: avoid copy + doAnalyzeProject(p, checkLib, checkConfig, selectedFiles, recheck); // TODO: avoid copy return; } - QStringList paths = recheckFiles.isEmpty() ? projectFile->getCheckPaths() : recheckFiles; + QStringList paths = recheck == Recheck::Selected ? selectedFiles : projectFile->getCheckPaths(); // If paths not given then check the root path (which may be the project // file's location, see above). This is to keep the compatibility with @@ -1988,7 +2039,7 @@ void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringLis if (paths.isEmpty()) { paths << mCurrentDirectory; } - doAnalyzeFiles(paths, checkLib, checkConfig, !recheckFiles.isEmpty()); + doAnalyzeFiles(paths, checkLib, checkConfig, selectedFiles, recheck); } void MainWindow::newProjectFile() diff --git a/gui/mainwindow.h b/gui/mainwindow.h index 72b11c49ec4..a92476a8c20 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -107,11 +108,8 @@ public slots: /** @brief Slot to reanalyze modified files */ void reAnalyzeModified(); - /** - * @brief Slot to clear all search results or selected - * @param selectedFiles list to clear, leave empty for all - */ - void clearResults(const QStringList& selectedFiles = QStringList()); + /** @brief Slot to clear all search results */ + void clearResults(); /** @brief Slot to open XML report file */ void openResults(); @@ -252,6 +250,19 @@ private slots: void changeReportType(); private: + /** @brief How an analysis treats the files selected for a recheck */ + enum class Recheck : std::uint8_t { + /** Not a recheck, everything is analyzed from scratch */ + None, + /** Only the selected files are analyzed, the results of the other files are kept */ + Selected, + /** + * Everything is analyzed. The analyzer information in the build directory is kept, + * except for the selected files, so the unchanged files are not analyzed again. + */ + Cached + }; + bool isCppcheckPremium() const; /** Get filename for last results */ @@ -269,11 +280,11 @@ private slots: /** * @brief Analyze the project. * @param projectFile Pointer to the project to analyze. - * @param recheckFiles files to recheck, empty => check all files + * @param selectedFiles files to recheck, empty => check all files * @param checkLib Flag to indicate if the library should be checked. * @param checkConfig Flag to indicate if the configuration should be checked. */ - void analyzeProject(const ProjectFile *projectFile, const QStringList& recheckFiles, bool checkLib = false, bool checkConfig = false); + void analyzeProject(const ProjectFile *projectFile, const QStringList& selectedFiles, bool checkLib = false, bool checkConfig = false); /** * @brief Set current language @@ -311,8 +322,10 @@ private slots: * @param p imported project * @param checkLib Flag to indicate if library should be checked * @param checkConfig Flag to indicate if the configuration should be checked. + * @param selectedFiles Files selected for a recheck + * @param recheck How the selected files are rechecked */ - void doAnalyzeProject(ImportProject p, bool checkLib = false, bool checkConfig = false, const QStringList &recheckFiles = QStringList()); + void doAnalyzeProject(ImportProject p, bool checkLib = false, bool checkConfig = false, const QStringList& selectedFiles = QStringList(), Recheck recheck = Recheck::None); /** * @brief Analyze all files specified in parameter files @@ -320,9 +333,18 @@ private slots: * @param files List of files and/or directories to analyze * @param checkLib Flag to indicate if library should be checked * @param checkConfig Flag to indicate if the configuration should be checked. - * @param partialRecheck Flag to indicate a partial recheck. + * @param selectedFiles Files selected for a recheck + * @param recheck How the selected files are rechecked + */ + void doAnalyzeFiles(const QStringList &files, bool checkLib = false, bool checkConfig = false, const QStringList& selectedFiles = QStringList(), Recheck recheck = Recheck::None); + + /** + * @brief Clear the results that a new analysis replaces + * @param buildDir Build directory of the analysis + * @param selectedFiles Files selected for a recheck + * @param recheck How the selected files are rechecked */ - void doAnalyzeFiles(const QStringList &files, bool checkLib = false, bool checkConfig = false, bool partialRecheck = false); + void clearResultsForAnalysis(const std::string& buildDir, const QStringList& selectedFiles, Recheck recheck); /** * @brief Get our default cppcheck settings and read project file. diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp index 408866fc2ef..d70b597198d 100644 --- a/gui/resultstree.cpp +++ b/gui/resultstree.cpp @@ -1203,8 +1203,15 @@ void ResultsTree::updateSettings(bool showFullPath, void ResultsTree::setCheckDirectory(const QString &dir) { + if (mCheckPath == dir) + return; + mCheckPath = dir; - refreshFilePaths(); + + // A partial recheck keeps results whose displayed paths are relative to the check path, so + // they are recalculated. Not without a check path, QDir("") would turn absolute paths into "../..". + if (!mCheckPath.isEmpty()) + refreshFilePaths(); } const QString& ResultsTree::getCheckDirectory() const diff --git a/gui/threadhandler.cpp b/gui/threadhandler.cpp index 3f89d58b558..577dcdc3375 100644 --- a/gui/threadhandler.cpp +++ b/gui/threadhandler.cpp @@ -21,6 +21,7 @@ #include "checkthread.h" #include "common.h" #include "filesettings.h" +#include "importproject.h" #include "resultsview.h" #include "settings.h" #include "utils.h" @@ -53,6 +54,7 @@ ThreadHandler::~ThreadHandler() void ThreadHandler::clearFiles() { mLastFiles.clear(); + mLastFileSettings.clear(); mResults.clearFiles(); mAnalyseWholeProgram = false; mCtuInfo.clear(); @@ -63,6 +65,7 @@ void ThreadHandler::clearFiles() void ThreadHandler::setFiles(std::list files) { mLastFiles = files; + mLastFileSettings.clear(); mResults.setFiles(std::move(files)); } @@ -70,6 +73,7 @@ void ThreadHandler::setProject(const ImportProject &prj) { mResults.setProject(prj); mLastFiles.clear(); + mLastFileSettings = prj.fileSettings; } void ThreadHandler::setCheckFiles(bool all) @@ -190,7 +194,7 @@ void ThreadHandler::threadDone() createThreads(1); mRunningThreadCount = 1; setupCheckThread(*mThreads[0]); - mThreads[0]->analyseWholeProgram(mLastFiles, mCtuInfo); + mThreads[0]->analyseWholeProgram(mLastFiles, mLastFileSettings, mCtuInfo); mAnalyseWholeProgram = false; mCtuInfo.clear(); return; diff --git a/gui/threadhandler.h b/gui/threadhandler.h index 17f011c32ac..879da0db8da 100644 --- a/gui/threadhandler.h +++ b/gui/threadhandler.h @@ -20,6 +20,7 @@ #ifndef THREADHANDLER_H #define THREADHANDLER_H +#include "filesettings.h" #include "settings.h" #include "suppressions.h" #include "threadresult.h" @@ -238,6 +239,12 @@ protected slots: */ std::list mLastFiles; + /** + * @brief Files of the project checked last time (used by the whole program analysis) + * + */ + std::list mLastFileSettings; + /** @brief date and time when current checking started */ QDateTime mCheckStartTime; diff --git a/lib/analyzerinfo.cpp b/lib/analyzerinfo.cpp index 0f62b4e18af..5a320e386e3 100644 --- a/lib/analyzerinfo.cpp +++ b/lib/analyzerinfo.cpp @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include @@ -52,45 +51,11 @@ static std::string getFilename(const std::string &fullpath) return fullpath.substr(pos1,pos2); } -void AnalyzerInformation::writeFilesTxt(const std::string &buildDir, const std::list &sourcefiles, const std::list &fileSettings, bool merge) +void AnalyzerInformation::writeFilesTxt(const std::string &buildDir, const std::list &sourcefiles, const std::list &fileSettings) { const std::string filesTxt(buildDir + "/files.txt"); - - std::string keptLines; - std::map fileCount; - - if (merge) { - std::set current; - for (const std::string &f : sourcefiles) - current.insert(Path::simplifyPath(f)); - for (const FileSettings &fs : fileSettings) - current.insert(Path::simplifyPath(fs.filename())); - - std::ifstream fin(filesTxt); - std::string line; - while (std::getline(fin, line)) { - Info info; - if (!info.parse(line)) - continue; - if (current.count(info.sourceFile)) - continue; - keptLines += line + '\n'; - - const std::string::size_type dotA = info.afile.rfind(".a"); - if (dotA != std::string::npos) { - const std::string base = info.afile.substr(0, dotA); - unsigned int n = 0; - if (strToInt(info.afile.substr(dotA + 2), n)) { - unsigned int &count = fileCount[base]; - count = std::max(count, n); - } - } - } - } - std::ofstream fout(filesTxt); - fout << keptLines; - fout << getFilesTxt(sourcefiles, fileSettings, fileCount); + fout << getFilesTxt(sourcefiles, fileSettings); } void AnalyzerInformation::writeIncludes(const std::set &files) @@ -155,9 +120,11 @@ void AnalyzerInformation::writeHash(std::size_t hash) } } -std::string AnalyzerInformation::getFilesTxt(const std::list &sourcefiles, const std::list &fileSettings, std::map fileCount) { +std::string AnalyzerInformation::getFilesTxt(const std::list &sourcefiles, const std::list &fileSettings) { std::ostringstream ret; + std::map fileCount; + for (const std::string &f : sourcefiles) { const std::string afile = getFilename(f); ret << afile << ".a" << (++fileCount[afile]) << sep << sep << sep << Path::simplifyPath(f) << '\n'; @@ -230,15 +197,23 @@ std::string AnalyzerInformation::skipAnalysis(const tinyxml2::XMLDocument &analy std::string AnalyzerInformation::getAnalyzerInfoFileFromFilesTxt(std::istream& filesTxt, const std::string &sourcefile, const std::string &cfg, size_t fsFileId) { + // An exact match wins, the suffix match is only a fallback for paths spelled differently than in + // files.txt. Otherwise "processexecutor.cpp" matches the "executor.cpp" line and both share one file. + const std::string simplifiedSourcefile = Path::simplifyPath(sourcefile); + std::string suffixMatch; std::string line; while (std::getline(filesTxt,line)) { AnalyzerInformation::Info filesTxtInfo; if (!filesTxtInfo.parse(line)) continue; // TODO: report error? - if (endsWith(sourcefile, filesTxtInfo.sourceFile) && filesTxtInfo.cfg == cfg && filesTxtInfo.fsFileId == fsFileId) + if (filesTxtInfo.cfg != cfg || filesTxtInfo.fsFileId != fsFileId) + continue; + if (simplifiedSourcefile == filesTxtInfo.sourceFile) return filesTxtInfo.afile; + if (suffixMatch.empty() && endsWith(simplifiedSourcefile, filesTxtInfo.sourceFile)) + suffixMatch = filesTxtInfo.afile; } - return ""; + return suffixMatch; } std::string AnalyzerInformation::getAnalyzerInfoFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, std::size_t fsFileId) diff --git a/lib/analyzerinfo.h b/lib/analyzerinfo.h index cf99c575ccf..75ff519db84 100644 --- a/lib/analyzerinfo.h +++ b/lib/analyzerinfo.h @@ -27,7 +27,6 @@ #include #include #include -#include #include #include @@ -59,7 +58,7 @@ class CPPCHECKLIB AnalyzerInformation { public: ~AnalyzerInformation(); - static void writeFilesTxt(const std::string &buildDir, const std::list &sourcefiles, const std::list &fileSettings, bool merge = false); + static void writeFilesTxt(const std::string &buildDir, const std::list &sourcefiles, const std::list &fileSettings); /** Close current TU.analyzerinfo file */ void close(); @@ -90,7 +89,7 @@ class CPPCHECKLIB AnalyzerInformation { 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, std::map fileCount = {}); + 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, size_t fsFileId); diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index 4852da9c188..8e32e3e3152 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -1681,7 +1681,7 @@ void CppCheck::executeAddons(const std::vector& files, const std::s } } -void CppCheck::executeAddonsWholeProgram(const std::list& /*files*/, const std::list& /*fileSettings*/, const std::string& ctuInfo) +void CppCheck::executeAddonsWholeProgram(const std::list &files, const std::list& fileSettings, const std::string& ctuInfo) { if (mSettings.addons.empty()) return; @@ -1698,19 +1698,13 @@ void CppCheck::executeAddonsWholeProgram(const std::list& /*fil } std::vector ctuInfoFiles; - std::set> seen; - std::ifstream filesTxt(mSettings.buildDir + "/files.txt"); - std::string line; - while (std::getline(filesTxt, line)) { - AnalyzerInformation::Info info; - if (!info.parse(line) || info.sourceFile.empty()) - continue; - if (!seen.emplace(info.sourceFile, info.fsFileId).second) - continue; - std::string dumpFileName = AnalyzerInformation::getAnalyzerInfoFile(mSettings.buildDir, info.sourceFile, "", info.fsFileId); - if (info.fsFileId > 0) - dumpFileName += '.' + std::to_string(info.fsFileId); - dumpFileName += ".dump"; + for (const auto &f: files) { + const std::string &dumpFileName = getDumpFileName(mSettings, f); + ctuInfoFiles.push_back(getCtuInfoFileName(dumpFileName)); + } + + for (const auto &fs: fileSettings) { + const std::string &dumpFileName = getDumpFileName(mSettings, fs.file); ctuInfoFiles.push_back(getCtuInfoFileName(dumpFileName)); } diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp index 0fd42e52ddd..30097a4f21c 100644 --- a/lib/errorlogger.cpp +++ b/lib/errorlogger.cpp @@ -36,14 +36,11 @@ #include #include #include -#include -#include +#include #include #include #include #include -#include -#include #include "xml.h" @@ -642,57 +639,13 @@ std::string ErrorMessage::toXML() const return printer.CStr(); } -// Byte offset of the start of each line, indexed by 1-based line number. -// Building this once per file keeps readCode() cheap. Without it every call -// rescans the file from the start, which is quadratic for a file that produces -// many findings - vendor/generated headers routinely produce tens of thousands. -static const std::vector* getLineOffsets(const std::string &file) -{ - // thread_local: the thread executor calls this concurrently - static thread_local std::list>> cache; - - for (auto it = cache.begin(); it != cache.end(); ++it) { - if (it->first == file) { - // most recently used goes first - cache.splice(cache.begin(), cache, it); - return &cache.front().second; - } - } - - // Binary mode so the offsets match what the seek in readCode() expects. - // A trailing '\r' is stripped by the caller. - std::ifstream fin(file, std::ios::binary); - if (!fin.is_open()) - return nullptr; - - std::vector offsets{0, 0}; // index 0 is unused, line 1 starts at offset 0 - std::array buf; - std::streamoff pos = 0; - while (fin.read(buf.data(), buf.size()) || fin.gcount() > 0) { - const std::streamsize n = fin.gcount(); - for (std::streamsize i = 0; i < n; ++i) { - if (buf[i] == '\n') - offsets.push_back(pos + i + 1); - } - pos += n; - } - - constexpr std::size_t maxCachedFiles = 4; - if (cache.size() >= maxCachedFiles) - cache.pop_back(); - cache.emplace_front(file, std::move(offsets)); - return &cache.front().second; -} - // 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; - const std::vector* const offsets = getLineOffsets(file); - if (offsets && linenr > 0 && linenr < static_cast(offsets->size())) { - std::ifstream fin(file, std::ios::binary); - fin.seekg((*offsets)[linenr]); - std::getline(fin, 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()) From fba0ced635934e314ac2df91327d7a3f5661db12 Mon Sep 17 00:00:00 2001 From: William Jakobsson <50847546+wjakobsson@users.noreply.github.com> Date: Fri, 23 Oct 2026 08:15:14 +0200 Subject: [PATCH 10/12] add tests --- gui/test/resultstree/testresultstree.cpp | 25 +++++++++++++++++ gui/test/resultstree/testresultstree.h | 1 + test/testanalyzerinformation.cpp | 35 ++++++++++++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/gui/test/resultstree/testresultstree.cpp b/gui/test/resultstree/testresultstree.cpp index 31520f3e6d9..e2e40517f69 100644 --- a/gui/test/resultstree/testresultstree.cpp +++ b/gui/test/resultstree/testresultstree.cpp @@ -265,6 +265,31 @@ void TestResultsTree::resultsInSameFile() const } } +void TestResultsTree::clearRecheckFile() const +{ + // #14893 - rechecking a file removes its results, the results of the other files are kept + ResultsTree tree(nullptr); + QVERIFY(tree.addErrorItem(createErrorItem("file1.c", 10, Severity::style, "test", "bugId"))); + QVERIFY(tree.addErrorItem(createErrorItem("file1.c", 20, Severity::style, "test", "bugId"))); + QVERIFY(tree.addErrorItem(createErrorItem("file2.c", 10, Severity::style, "test", "bugId"))); + + const auto* model = dynamic_cast(tree.model()); + QVERIFY(model != nullptr); + QCOMPARE(model->rowCount(), 2); + + tree.clearRecheckFile("file1.c"); + QCOMPARE(model->rowCount(), 1); + QCOMPARE(model->item(0,0)->text(), "file2.c"); + + // the recheck reports the results of the rechecked file again, they must not be treated as duplicates + QVERIFY(tree.addErrorItem(createErrorItem("file1.c", 10, Severity::style, "test", "bugId"))); + QVERIFY(tree.addErrorItem(createErrorItem("file1.c", 20, Severity::style, "test", "bugId"))); + QCOMPARE(model->rowCount(), 2); + + // the results of the other files are still known + QVERIFY(!tree.addErrorItem(createErrorItem("file2.c", 10, Severity::style, "test", "bugId"))); +} + void TestResultsTree::testReportType() const { TestReport report("{id},{classification},{guideline}"); diff --git a/gui/test/resultstree/testresultstree.h b/gui/test/resultstree/testresultstree.h index add0390c456..dcce568b4ba 100644 --- a/gui/test/resultstree/testresultstree.h +++ b/gui/test/resultstree/testresultstree.h @@ -26,6 +26,7 @@ private slots: void duplicateResults() const; void multiLineResult() const; void resultsInSameFile() const; + void clearRecheckFile() const; void testReportType() const; void testReportTypeIcon() const; void testGetGuidelineError() const; diff --git a/test/testanalyzerinformation.cpp b/test/testanalyzerinformation.cpp index 2174f7d4676..0e27e59e5b7 100644 --- a/test/testanalyzerinformation.cpp +++ b/test/testanalyzerinformation.cpp @@ -53,6 +53,41 @@ class TestAnalyzerInformation : public TestFixture { ASSERT_EQUALS("file1.a4", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f, "file1.c", "", 0)); f.str(filesTxt); ASSERT_EQUALS("file1.a4", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f, "./file1.c", "", 0)); + + // an exact match wins over a suffix match - "executor.cpp" is a suffix of + // "processexecutor.cpp" so both used to resolve to the same analyzer info file + { + constexpr char suffixTxt[] = "executor.a1:::executor.cpp\n" + "processexecutor.a2:::processexecutor.cpp\n"; + std::istringstream f1(suffixTxt); + ASSERT_EQUALS("executor.a1", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f1, "executor.cpp", "", 0)); + std::istringstream f2(suffixTxt); + ASSERT_EQUALS("processexecutor.a2", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f2, "processexecutor.cpp", "", 0)); + } + + // the same, with the exact match on the first line + { + constexpr char suffixTxt[] = "processexecutor.a1:::processexecutor.cpp\n" + "executor.a2:::executor.cpp\n"; + std::istringstream f1(suffixTxt); + ASSERT_EQUALS("processexecutor.a1", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f1, "processexecutor.cpp", "", 0)); + std::istringstream f2(suffixTxt); + ASSERT_EQUALS("executor.a2", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f2, "executor.cpp", "", 0)); + } + + // a suffix match is still used when there is no exact match + { + constexpr char relTxt[] = "a.a1:::src/a.c\n"; + std::istringstream f1(relTxt); + ASSERT_EQUALS("a.a1", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f1, "/home/proj/src/a.c", "", 0)); + } + + // no match at all + { + constexpr char noneTxt[] = "a.a1:::a.c\n"; + std::istringstream f1(noneTxt); + ASSERT_EQUALS("", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f1, "b.c", "", 0)); + } } void getAnalyzerInfoFile() const { From 8ac514a7cbe0d41fcabb69edaa980f51312700f5 Mon Sep 17 00:00:00 2001 From: William Jakobsson <50847546+wjakobsson@users.noreply.github.com> Date: Fri, 23 Oct 2026 12:43:32 +0200 Subject: [PATCH 11/12] change matching --- lib/analyzerinfo.cpp | 12 ++---------- test/testanalyzerinformation.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/lib/analyzerinfo.cpp b/lib/analyzerinfo.cpp index 5a320e386e3..3bdc6875e63 100644 --- a/lib/analyzerinfo.cpp +++ b/lib/analyzerinfo.cpp @@ -21,7 +21,6 @@ #include "errorlogger.h" #include "filesettings.h" #include "path.h" -#include "utils.h" #include #include @@ -197,23 +196,16 @@ std::string AnalyzerInformation::skipAnalysis(const tinyxml2::XMLDocument &analy std::string AnalyzerInformation::getAnalyzerInfoFileFromFilesTxt(std::istream& filesTxt, const std::string &sourcefile, const std::string &cfg, size_t fsFileId) { - // An exact match wins, the suffix match is only a fallback for paths spelled differently than in - // files.txt. Otherwise "processexecutor.cpp" matches the "executor.cpp" line and both share one file. const std::string simplifiedSourcefile = Path::simplifyPath(sourcefile); - std::string suffixMatch; std::string line; while (std::getline(filesTxt,line)) { AnalyzerInformation::Info filesTxtInfo; if (!filesTxtInfo.parse(line)) continue; // TODO: report error? - if (filesTxtInfo.cfg != cfg || filesTxtInfo.fsFileId != fsFileId) - continue; - if (simplifiedSourcefile == filesTxtInfo.sourceFile) + if (filesTxtInfo.sourceFile == simplifiedSourcefile && filesTxtInfo.cfg == cfg && filesTxtInfo.fsFileId == fsFileId) return filesTxtInfo.afile; - if (suffixMatch.empty() && endsWith(simplifiedSourcefile, filesTxtInfo.sourceFile)) - suffixMatch = filesTxtInfo.afile; } - return suffixMatch; + return ""; } std::string AnalyzerInformation::getAnalyzerInfoFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, std::size_t fsFileId) diff --git a/test/testanalyzerinformation.cpp b/test/testanalyzerinformation.cpp index 0e27e59e5b7..0356630686c 100644 --- a/test/testanalyzerinformation.cpp +++ b/test/testanalyzerinformation.cpp @@ -54,8 +54,8 @@ class TestAnalyzerInformation : public TestFixture { f.str(filesTxt); ASSERT_EQUALS("file1.a4", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f, "./file1.c", "", 0)); - // an exact match wins over a suffix match - "executor.cpp" is a suffix of - // "processexecutor.cpp" so both used to resolve to the same analyzer info file + // only an exact match is used - "executor.cpp" is a suffix of "processexecutor.cpp" + // so both used to resolve to the same analyzer info file { constexpr char suffixTxt[] = "executor.a1:::executor.cpp\n" "processexecutor.a2:::processexecutor.cpp\n"; @@ -75,11 +75,11 @@ class TestAnalyzerInformation : public TestFixture { ASSERT_EQUALS("executor.a2", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f2, "executor.cpp", "", 0)); } - // a suffix match is still used when there is no exact match + // a path that only ends with the path in files.txt is not a match { constexpr char relTxt[] = "a.a1:::src/a.c\n"; std::istringstream f1(relTxt); - ASSERT_EQUALS("a.a1", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f1, "/home/proj/src/a.c", "", 0)); + ASSERT_EQUALS("", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f1, "/home/proj/src/a.c", "", 0)); } // no match at all From cafcfeec5f8f6004e9521358a85f71dcdf20532b Mon Sep 17 00:00:00 2001 From: William Jakobsson <50847546+wjakobsson@users.noreply.github.com> Date: Fri, 23 Oct 2026 13:18:01 +0200 Subject: [PATCH 12/12] use set for duplicate check in results tree --- gui/resultstree.cpp | 4 ++-- gui/resultstree.h | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp index d70b597198d..2c99ecf3ed1 100644 --- a/gui/resultstree.cpp +++ b/gui/resultstree.cpp @@ -246,7 +246,7 @@ bool ResultsTree::addErrorItem(const ErrorItem& errorItem) const QString s = errorItem.toString(); if (mErrorList.contains(s)) return false; - mErrorList.append(s); + mErrorList.insert(s); QSharedPointer errorItemPtr{new ErrorItem(errorItem)}; @@ -1137,7 +1137,7 @@ void ResultsTree::removeFileErrorsFromErrorList(const ResultItem *fileItem) for (int i{0}; i < fileItem->rowCount(); ++i) { const auto *errorItem = dynamic_cast(fileItem->child(i, COLUMN_FILE)); if (errorItem && errorItem->errorItem) - mErrorList.removeAll(errorItem->errorItem->toString()); + mErrorList.remove(errorItem->errorItem->toString()); } } diff --git a/gui/resultstree.h b/gui/resultstree.h index b0634e96396..c829c21fc6a 100644 --- a/gui/resultstree.h +++ b/gui/resultstree.h @@ -29,6 +29,7 @@ #include #include +#include #include #include #include @@ -572,8 +573,8 @@ protected slots: QStringList mHiddenMessageId; - // List of existing errors so we can avoid duplicates - QStringList mErrorList; + // Set of existing errors so we can avoid duplicates + QSet mErrorList; QItemSelectionModel* mSelectionModel{}; ThreadHandler *mThread{};