Skip to content

Commit bb94cbb

Browse files
authored
Fix #14893 recheck removes unselected files results (#8699)
1 parent 7828b1b commit bb94cbb

12 files changed

Lines changed: 224 additions & 39 deletions

‎gui/checkthread.cpp‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,15 @@ CheckThread::CheckThread(ThreadResult &result, int threadIndex)
113113
void CheckThread::setSettings(const Settings &settings, std::shared_ptr<Suppressions> supprs)
114114
{
115115
mFiles.clear();
116+
mFileSettings.clear();
116117
mSettings = settings; // this is a copy
117118
mSuppressions = std::move(supprs);
118119
}
119120

120-
void CheckThread::analyseWholeProgram(const std::list<FileWithDetails> &files, const std::string& ctuInfo)
121+
void CheckThread::analyseWholeProgram(const std::list<FileWithDetails> &files, const std::list<FileSettings> &fileSettings, const std::string& ctuInfo)
121122
{
122123
mFiles = files;
124+
mFileSettings = fileSettings;
123125
mAnalyseWholeProgram = true;
124126
mCtuInfo = ctuInfo;
125127
start();
@@ -136,8 +138,9 @@ void CheckThread::run()
136138
std::string ctuInfo;
137139
ctuInfo.swap(mCtuInfo);
138140
qDebug() << "Whole program analysis";
139-
cppcheck.analyseWholeProgram(mSettings.buildDir, mFiles, {}, ctuInfo);
141+
cppcheck.analyseWholeProgram(mSettings.buildDir, mFiles, mFileSettings, ctuInfo);
140142
mFiles.clear();
143+
mFileSettings.clear();
141144
emit done();
142145
return;
143146
}

‎gui/checkthread.h‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,11 @@ class CheckThread : public QThread {
6969

7070
/**
7171
* @brief Run whole program analysis
72-
* @param files All files
73-
* @param ctuInfo Ctu info for addons
72+
* @param files All files
73+
* @param fileSettings All files of the project
74+
* @param ctuInfo Ctu info for addons
7475
*/
75-
void analyseWholeProgram(const std::list<FileWithDetails> &files, const std::string& ctuInfo);
76+
void analyseWholeProgram(const std::list<FileWithDetails> &files, const std::list<FileSettings> &fileSettings, const std::string& ctuInfo);
7677

7778
void setAddonsAndTools(const QStringList &addonsAndTools) {
7879
mAddonsAndTools = addonsAndTools;
@@ -153,6 +154,7 @@ class CheckThread : public QThread {
153154
bool isSuppressed(const SuppressionList::ErrorMessage &errorMessage) const;
154155

155156
std::list<FileWithDetails> mFiles;
157+
std::list<FileSettings> mFileSettings;
156158
bool mAnalyseWholeProgram{};
157159
std::string mCtuInfo;
158160
QStringList mAddonsAndTools;

‎gui/mainwindow.cpp‎

Lines changed: 80 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959

6060
#include <algorithm>
6161
#include <cstddef>
62+
#include <fstream>
6263
#include <iterator>
6364
#include <list>
6465
#include <memory>
@@ -96,6 +97,7 @@
9697
#include <QtNetwork/QNetworkRequest>
9798
#include <QPushButton>
9899
#include <QRegularExpression>
100+
#include <QSet>
99101
#include <QSettings>
100102
#include <QSize>
101103
#include <QTimer>
@@ -118,6 +120,18 @@ static QString fromNativePath(const QString& p) {
118120
#endif
119121
}
120122

123+
// The files selected in the results view are not necessarily spelled the same way as the
124+
// project or files.txt spells them, compare them as absolute paths
125+
static QString comparablePath(const QString& path) {
126+
return QDir::cleanPath(QFileInfo(path).absoluteFilePath());
127+
}
128+
129+
static QSet<QString> comparablePaths(const QStringList& paths) {
130+
QSet<QString> ret;
131+
std::transform(paths.cbegin(), paths.cend(), std::inserter(ret, ret.end()), comparablePath);
132+
return ret;
133+
}
134+
121135
template<typename T = std::vector<std::string>>
122136
static T toStdStringList(const QStringList& stringList) {
123137
T ret;
@@ -568,21 +582,31 @@ void MainWindow::saveSettings() const
568582
mUI->mResults->saveSettings(mSettings);
569583
}
570584

571-
void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLib, const bool checkConfig, const QStringList& recheckFiles)
585+
void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLib, const bool checkConfig, const QStringList& selectedFiles, const Recheck recheck)
572586
{
573587
Settings checkSettings;
574588
auto supprs = std::make_shared<Suppressions>();
575589
if (!getCppcheckSettings(checkSettings, *supprs))
576590
return;
577591

578-
// filter requested files
579-
if (!recheckFiles.isEmpty()) {
592+
// Filter the selected files
593+
if (recheck == Recheck::Selected) {
594+
const QSet<QString> selected = comparablePaths(selectedFiles);
580595
p.fileSettings.remove_if([&](const FileSettings& fs) {
581-
return !recheckFiles.contains(QString::fromStdString(fs.filename()));
596+
return !selected.contains(comparablePath(QString::fromStdString(fs.filename())));
582597
});
598+
if (p.fileSettings.empty()) {
599+
QMessageBox msg(QMessageBox::Information,
600+
"Cppcheck",
601+
tr("None of the selected files is part of the project, nothing to analyze."),
602+
QMessageBox::Ok,
603+
this);
604+
msg.exec();
605+
return;
606+
}
583607
}
584608

585-
clearResults();
609+
clearResultsForAnalysis(checkSettings.buildDir, selectedFiles, recheck);
586610

587611
mIsLogfileLoaded = false;
588612
if (mProjectFile) {
@@ -647,7 +671,7 @@ void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLib, const bo
647671
mUI->mResults->setCheckSettings(checkSettings);
648672
}
649673

650-
void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, const bool checkConfig)
674+
void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, const bool checkConfig, const QStringList& selectedFiles, const Recheck recheck)
651675
{
652676
if (files.isEmpty())
653677
return;
@@ -657,7 +681,7 @@ void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, c
657681
if (!getCppcheckSettings(checkSettings, *supprs))
658682
return;
659683

660-
clearResults();
684+
clearResultsForAnalysis(checkSettings.buildDir, selectedFiles, recheck);
661685

662686
mIsLogfileLoaded = false;
663687
FileList pathList;
@@ -669,7 +693,6 @@ void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, c
669693
}
670694
QStringList fileNames = pathList.getFileList();
671695

672-
mUI->mResults->clear(true);
673696
mUI->mResults->setResultsSource(ResultsTree::ResultsSource::Analysis);
674697
mThread->clearFiles();
675698

@@ -1379,11 +1402,6 @@ void MainWindow::reAnalyzeSelected(const QStringList& files)
13791402
return;
13801403

13811404
if (mProjectFile) {
1382-
// Clear details, statistics and progress
1383-
mUI->mResults->clear(false);
1384-
for (int i = 0; i < files.size(); ++i)
1385-
mUI->mResults->clearRecheckFile(files[i]);
1386-
13871405
analyzeProject(mProjectFile, files);
13881406
return;
13891407
}
@@ -1393,10 +1411,7 @@ void MainWindow::reAnalyzeSelected(const QStringList& files)
13931411
if (!getCppcheckSettings(checkSettings, *supprs))
13941412
return;
13951413

1396-
// Clear details, statistics and progress
1397-
mUI->mResults->clear(false);
1398-
for (int i = 0; i < files.size(); ++i)
1399-
mUI->mResults->clearRecheckFile(files[i]);
1414+
clearResultsForAnalysis(checkSettings.buildDir, files, Recheck::Selected);
14001415

14011416
mCurrentDirectory = mUI->mResults->getCheckDirectory();
14021417
FileList pathList;
@@ -1451,7 +1466,7 @@ void MainWindow::reAnalyze(bool all)
14511466
void MainWindow::clearResults()
14521467
{
14531468
if (mProjectFile && !mProjectFile->getBuildDir().isEmpty()) {
1454-
QDir dir(QFileInfo(mProjectFile->getFilename()).absolutePath() + '/' + mProjectFile->getBuildDir());
1469+
QDir dir(QDir(QFileInfo(mProjectFile->getFilename()).absolutePath()).filePath(mProjectFile->getBuildDir()));
14551470
for (const QString& f: dir.entryList(QDir::Files)) {
14561471
if (!f.endsWith("files.txt")) {
14571472
static const QRegularExpression rx("^.*.s[0-9]+$");
@@ -1465,6 +1480,42 @@ void MainWindow::clearResults()
14651480
enableResultsButtons();
14661481
}
14671482

1483+
void MainWindow::clearResultsForAnalysis(const std::string& buildDir, const QStringList& selectedFiles, const Recheck recheck)
1484+
{
1485+
switch (recheck) {
1486+
case Recheck::None:
1487+
clearResults();
1488+
break;
1489+
case Recheck::Selected:
1490+
// Only the selected files are analyzed, the results of the other files are kept
1491+
mUI->mResults->clear(false);
1492+
for (const QString& f : selectedFiles)
1493+
mUI->mResults->clearRecheckFile(f);
1494+
enableResultsButtons();
1495+
break;
1496+
case Recheck::Cached: {
1497+
// Every file reports its results again, either analyzed or replayed from the analyzer
1498+
// information. Remove the analyzer information of the selected files so that they really
1499+
// are analyzed again, also by the clang tools that keep their results next to it.
1500+
const QSet<QString> selected = comparablePaths(selectedFiles);
1501+
QDir dir(QString::fromStdString(buildDir));
1502+
std::ifstream filesTxt(buildDir + "/files.txt");
1503+
std::string line;
1504+
while (std::getline(filesTxt, line)) {
1505+
AnalyzerInformation::Info info;
1506+
if (!info.parse(line) || !selected.contains(comparablePath(QString::fromStdString(info.sourceFile))))
1507+
continue;
1508+
const QString afile = QString::fromStdString(info.afile);
1509+
for (const QString& f : dir.entryList({afile, afile + ".*"}, QDir::Files))
1510+
dir.remove(f);
1511+
}
1512+
mUI->mResults->clear(true);
1513+
enableResultsButtons();
1514+
break;
1515+
}
1516+
}
1517+
}
1518+
14681519
void MainWindow::openResults()
14691520
{
14701521
if (mUI->mResults->hasResults()) {
@@ -1863,10 +1914,17 @@ bool MainWindow::loadLastResults()
18631914
return true;
18641915
}
18651916

1866-
void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringList& recheckFiles, const bool checkLib, const bool checkConfig)
1917+
void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringList& selectedFiles, const bool checkLib, const bool checkConfig)
18671918
{
18681919
Settings::terminate(false);
18691920

1921+
// With a build directory the whole project is analyzed: the analyzer information cache skips
1922+
// the unchanged files, and files.txt stays complete so whole program analysis sees every file.
1923+
// Without a build directory there is no cache, so only the selected files are analyzed.
1924+
Recheck recheck = Recheck::None;
1925+
if (!selectedFiles.isEmpty())
1926+
recheck = projectFile->getBuildDir().isEmpty() ? Recheck::Selected : Recheck::Cached;
1927+
18701928
QFileInfo inf(projectFile->getFilename());
18711929
const QString& rootpath = projectFile->getRootPath();
18721930

@@ -1968,11 +2026,11 @@ void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringLis
19682026
msg.exec();
19692027
return;
19702028
}
1971-
doAnalyzeProject(p, checkLib, checkConfig, recheckFiles); // TODO: avoid copy
2029+
doAnalyzeProject(p, checkLib, checkConfig, selectedFiles, recheck); // TODO: avoid copy
19722030
return;
19732031
}
19742032

1975-
QStringList paths = recheckFiles.isEmpty() ? projectFile->getCheckPaths() : recheckFiles;
2033+
QStringList paths = recheck == Recheck::Selected ? selectedFiles : projectFile->getCheckPaths();
19762034

19772035
// If paths not given then check the root path (which may be the project
19782036
// file's location, see above). This is to keep the compatibility with
@@ -1981,7 +2039,7 @@ void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringLis
19812039
if (paths.isEmpty()) {
19822040
paths << mCurrentDirectory;
19832041
}
1984-
doAnalyzeFiles(paths, checkLib, checkConfig);
2042+
doAnalyzeFiles(paths, checkLib, checkConfig, selectedFiles, recheck);
19852043
}
19862044

19872045
void MainWindow::newProjectFile()

‎gui/mainwindow.h‎

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
#include <cstdint>
2626
#include <list>
27+
#include <string>
2728

2829
#include <QFileDialog>
2930
#include <QMainWindow>
@@ -249,6 +250,19 @@ private slots:
249250
void changeReportType();
250251
private:
251252

253+
/** @brief How an analysis treats the files selected for a recheck */
254+
enum class Recheck : std::uint8_t {
255+
/** Not a recheck, everything is analyzed from scratch */
256+
None,
257+
/** Only the selected files are analyzed, the results of the other files are kept */
258+
Selected,
259+
/**
260+
* Everything is analyzed. The analyzer information in the build directory is kept,
261+
* except for the selected files, so the unchanged files are not analyzed again.
262+
*/
263+
Cached
264+
};
265+
252266
bool isCppcheckPremium() const;
253267

254268
/** Get filename for last results */
@@ -266,11 +280,11 @@ private slots:
266280
/**
267281
* @brief Analyze the project.
268282
* @param projectFile Pointer to the project to analyze.
269-
* @param recheckFiles files to recheck, empty => check all files
283+
* @param selectedFiles files to recheck, empty => check all files
270284
* @param checkLib Flag to indicate if the library should be checked.
271285
* @param checkConfig Flag to indicate if the configuration should be checked.
272286
*/
273-
void analyzeProject(const ProjectFile *projectFile, const QStringList& recheckFiles, bool checkLib = false, bool checkConfig = false);
287+
void analyzeProject(const ProjectFile *projectFile, const QStringList& selectedFiles, bool checkLib = false, bool checkConfig = false);
274288

275289
/**
276290
* @brief Set current language
@@ -308,17 +322,29 @@ private slots:
308322
* @param p imported project
309323
* @param checkLib Flag to indicate if library should be checked
310324
* @param checkConfig Flag to indicate if the configuration should be checked.
325+
* @param selectedFiles Files selected for a recheck
326+
* @param recheck How the selected files are rechecked
311327
*/
312-
void doAnalyzeProject(ImportProject p, bool checkLib = false, bool checkConfig = false, const QStringList& recheckFiles = QStringList());
328+
void doAnalyzeProject(ImportProject p, bool checkLib = false, bool checkConfig = false, const QStringList& selectedFiles = QStringList(), Recheck recheck = Recheck::None);
313329

314330
/**
315331
* @brief Analyze all files specified in parameter files
316332
*
317333
* @param files List of files and/or directories to analyze
318334
* @param checkLib Flag to indicate if library should be checked
319335
* @param checkConfig Flag to indicate if the configuration should be checked.
336+
* @param selectedFiles Files selected for a recheck
337+
* @param recheck How the selected files are rechecked
338+
*/
339+
void doAnalyzeFiles(const QStringList &files, bool checkLib = false, bool checkConfig = false, const QStringList& selectedFiles = QStringList(), Recheck recheck = Recheck::None);
340+
341+
/**
342+
* @brief Clear the results that a new analysis replaces
343+
* @param buildDir Build directory of the analysis
344+
* @param selectedFiles Files selected for a recheck
345+
* @param recheck How the selected files are rechecked
320346
*/
321-
void doAnalyzeFiles(const QStringList &files, bool checkLib = false, bool checkConfig = false);
347+
void clearResultsForAnalysis(const std::string& buildDir, const QStringList& selectedFiles, Recheck recheck);
322348

323349
/**
324350
* @brief Get our default cppcheck settings and read project file.

‎gui/resultstree.cpp‎

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ bool ResultsTree::addErrorItem(const ErrorItem& errorItem)
246246
const QString s = errorItem.toString();
247247
if (mErrorList.contains(s))
248248
return false;
249-
mErrorList.append(s);
249+
mErrorList.insert(s);
250250

251251
QSharedPointer<ErrorItem> errorItemPtr{new ErrorItem(errorItem)};
252252

@@ -427,7 +427,7 @@ void ResultsTree::clear(const QString &filename)
427427

428428
if (stripped == fileItem->text() ||
429429
filename == fileItem->errorItem->file0) {
430-
mErrorList.removeAll(fileItem->errorItem->toString());
430+
removeFileErrorsFromErrorList(fileItem);
431431
mModel->removeRow(i);
432432
break;
433433
}
@@ -445,7 +445,7 @@ void ResultsTree::clearRecheckFile(const QString &filename)
445445
QString storedfile = fileItem->getErrorPathItem().file;
446446
storedfile = ((!mCheckPath.isEmpty() && storedfile.startsWith(mCheckPath)) ? storedfile.mid(mCheckPath.length() + 1) : storedfile);
447447
if (actualfile == storedfile) {
448-
mErrorList.removeAll(fileItem->errorItem->toString());
448+
removeFileErrorsFromErrorList(fileItem);
449449
mModel->removeRow(i);
450450
break;
451451
}
@@ -1132,6 +1132,15 @@ void ResultsTree::saveErrors(Report *report, const ResultItem *fileItem) const
11321132
}
11331133
}
11341134

1135+
void ResultsTree::removeFileErrorsFromErrorList(const ResultItem *fileItem)
1136+
{
1137+
for (int i{0}; i < fileItem->rowCount(); ++i) {
1138+
const auto *errorItem = dynamic_cast<const ResultItem*>(fileItem->child(i, COLUMN_FILE));
1139+
if (errorItem && errorItem->errorItem)
1140+
mErrorList.remove(errorItem->errorItem->toString());
1141+
}
1142+
}
1143+
11351144
void ResultsTree::updateFromOldReport(const QString &filename)
11361145
{
11371146
showColumn(COLUMN_SINCE_DATE);
@@ -1194,7 +1203,15 @@ void ResultsTree::updateSettings(bool showFullPath,
11941203

11951204
void ResultsTree::setCheckDirectory(const QString &dir)
11961205
{
1206+
if (mCheckPath == dir)
1207+
return;
1208+
11971209
mCheckPath = dir;
1210+
1211+
// A partial recheck keeps results whose displayed paths are relative to the check path, so
1212+
// they are recalculated. Not without a check path, QDir("") would turn absolute paths into "../..".
1213+
if (!mCheckPath.isEmpty())
1214+
refreshFilePaths();
11981215
}
11991216

12001217
const QString& ResultsTree::getCheckDirectory() const

0 commit comments

Comments
 (0)