1 /*
2  * Cppcheck - A tool for static C/C++ code analysis
3  * Copyright (C) 2007-2021 Cppcheck team.
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "threadhandler.h"
20 
21 #include <QFileInfo>
22 #include <QDebug>
23 #include <QSettings>
24 #include "common.h"
25 #include "settings.h"
26 #include "checkthread.h"
27 #include "resultsview.h"
28 
ThreadHandler(QObject * parent)29 ThreadHandler::ThreadHandler(QObject *parent) :
30     QObject(parent),
31     mScanDuration(0),
32     mRunningThreadCount(0),
33     mAnalyseWholeProgram(false)
34 
35 {
36     setThreadCount(1);
37 }
38 
~ThreadHandler()39 ThreadHandler::~ThreadHandler()
40 {
41     removeThreads();
42 }
43 
clearFiles()44 void ThreadHandler::clearFiles()
45 {
46     mLastFiles.clear();
47     mResults.clearFiles();
48     mAnalyseWholeProgram = false;
49     mAddonsAndTools.clear();
50     mSuppressions.clear();
51 }
52 
setFiles(const QStringList & files)53 void ThreadHandler::setFiles(const QStringList &files)
54 {
55     mResults.setFiles(files);
56     mLastFiles = files;
57 }
58 
setProject(const ImportProject & prj)59 void ThreadHandler::setProject(const ImportProject &prj)
60 {
61     mResults.setProject(prj);
62     mLastFiles.clear();
63 }
64 
setCheckFiles(bool all)65 void ThreadHandler::setCheckFiles(bool all)
66 {
67     if (mRunningThreadCount == 0) {
68         mResults.setFiles(getReCheckFiles(all));
69     }
70 }
71 
setCheckFiles(const QStringList & files)72 void ThreadHandler::setCheckFiles(const QStringList& files)
73 {
74     if (mRunningThreadCount == 0) {
75         mResults.setFiles(files);
76     }
77 }
78 
check(const Settings & settings)79 void ThreadHandler::check(const Settings &settings)
80 {
81     if (mResults.getFileCount() == 0 || mRunningThreadCount > 0 || settings.jobs == 0) {
82         qDebug() << "Can't start checking if there's no files to check or if check is in progress.";
83         emit done();
84         return;
85     }
86 
87     setThreadCount(settings.jobs);
88 
89     mRunningThreadCount = mThreads.size();
90 
91     if (mResults.getFileCount() < mRunningThreadCount) {
92         mRunningThreadCount = mResults.getFileCount();
93     }
94 
95     QStringList addonsAndTools = mAddonsAndTools;
96     for (const std::string& addon: settings.addons) {
97         QString s = QString::fromStdString(addon);
98         if (!addonsAndTools.contains(s))
99             addonsAndTools << s;
100     }
101 
102     for (int i = 0; i < mRunningThreadCount; i++) {
103         mThreads[i]->setAddonsAndTools(addonsAndTools);
104         mThreads[i]->setSuppressions(mSuppressions);
105         mThreads[i]->setClangIncludePaths(mClangIncludePaths);
106         mThreads[i]->setDataDir(mDataDir);
107         mThreads[i]->check(settings);
108     }
109 
110     // Date and time when checking starts..
111     mCheckStartTime = QDateTime::currentDateTime();
112 
113     mAnalyseWholeProgram = true;
114 
115     mTime.start();
116 }
117 
isChecking() const118 bool ThreadHandler::isChecking() const
119 {
120     return mRunningThreadCount > 0;
121 }
122 
setThreadCount(const int count)123 void ThreadHandler::setThreadCount(const int count)
124 {
125     if (mRunningThreadCount > 0 ||
126         count == mThreads.size() ||
127         count <= 0) {
128         return;
129     }
130 
131     //Remove unused old threads
132     removeThreads();
133     //Create new threads
134     for (int i = mThreads.size(); i < count; i++) {
135         mThreads << new CheckThread(mResults);
136         connect(mThreads.last(), &CheckThread::done,
137                 this, &ThreadHandler::threadDone);
138         connect(mThreads.last(), &CheckThread::fileChecked,
139                 &mResults, &ThreadResult::fileChecked);
140     }
141 }
142 
143 
removeThreads()144 void ThreadHandler::removeThreads()
145 {
146     for (CheckThread* thread : mThreads) {
147         thread->terminate();
148         disconnect(thread, &CheckThread::done,
149                    this, &ThreadHandler::threadDone);
150         disconnect(thread, &CheckThread::fileChecked,
151                    &mResults, &ThreadResult::fileChecked);
152         delete thread;
153     }
154 
155     mThreads.clear();
156     mAnalyseWholeProgram = false;
157 }
158 
threadDone()159 void ThreadHandler::threadDone()
160 {
161     if (mRunningThreadCount == 1 && mAnalyseWholeProgram) {
162         mThreads[0]->analyseWholeProgram(mLastFiles);
163         mAnalyseWholeProgram = false;
164         return;
165     }
166 
167     mRunningThreadCount--;
168     if (mRunningThreadCount == 0) {
169         emit done();
170 
171         mScanDuration = mTime.elapsed();
172 
173         // Set date/time used by the recheck
174         if (!mCheckStartTime.isNull()) {
175             mLastCheckTime = mCheckStartTime;
176             mCheckStartTime = QDateTime();
177         }
178     }
179 }
180 
stop()181 void ThreadHandler::stop()
182 {
183     mCheckStartTime = QDateTime();
184     mAnalyseWholeProgram = false;
185     for (CheckThread* thread : mThreads) {
186         thread->stop();
187     }
188 }
189 
initialize(ResultsView * view)190 void ThreadHandler::initialize(ResultsView *view)
191 {
192     connect(&mResults, &ThreadResult::progress,
193             view, &ResultsView::progress);
194 
195     connect(&mResults, &ThreadResult::error,
196             view, &ResultsView::error);
197 
198     connect(&mResults, &ThreadResult::log,
199             this, &ThreadHandler::log);
200 
201     connect(&mResults, &ThreadResult::debugError,
202             this, &ThreadHandler::debugError);
203 
204     connect(&mResults, &ThreadResult::bughuntingReportLine,
205             this, &ThreadHandler::bughuntingReportLine);
206 }
207 
loadSettings(const QSettings & settings)208 void ThreadHandler::loadSettings(const QSettings &settings)
209 {
210     setThreadCount(settings.value(SETTINGS_CHECK_THREADS, 1).toInt());
211 }
212 
saveSettings(QSettings & settings) const213 void ThreadHandler::saveSettings(QSettings &settings) const
214 {
215     settings.setValue(SETTINGS_CHECK_THREADS, mThreads.size());
216 }
217 
hasPreviousFiles() const218 bool ThreadHandler::hasPreviousFiles() const
219 {
220     return !mLastFiles.isEmpty();
221 }
222 
getPreviousFilesCount() const223 int ThreadHandler::getPreviousFilesCount() const
224 {
225     return mLastFiles.size();
226 }
227 
getPreviousScanDuration() const228 int ThreadHandler::getPreviousScanDuration() const
229 {
230     return mScanDuration;
231 }
232 
getReCheckFiles(bool all) const233 QStringList ThreadHandler::getReCheckFiles(bool all) const
234 {
235     if (mLastCheckTime.isNull() || all)
236         return mLastFiles;
237 
238     std::set<QString> modified;
239     std::set<QString> unmodified;
240 
241     QStringList files;
242     for (int i = 0; i < mLastFiles.size(); ++i) {
243         if (needsReCheck(mLastFiles[i], modified, unmodified))
244             files.push_back(mLastFiles[i]);
245     }
246     return files;
247 }
248 
needsReCheck(const QString & filename,std::set<QString> & modified,std::set<QString> & unmodified) const249 bool ThreadHandler::needsReCheck(const QString &filename, std::set<QString> &modified, std::set<QString> &unmodified) const
250 {
251     if (modified.find(filename) != modified.end())
252         return true;
253 
254     if (unmodified.find(filename) != unmodified.end())
255         return false;
256 
257     if (QFileInfo(filename).lastModified() > mLastCheckTime) {
258         return true;
259     }
260 
261     // Parse included files recursively
262     QFile f(filename);
263     if (!f.open(QIODevice::ReadOnly | QIODevice::Text))
264         return false;
265 
266     // prevent recursion..
267     unmodified.insert(filename);
268 
269     QTextStream in(&f);
270     while (!in.atEnd()) {
271         QString line = in.readLine();
272         if (line.startsWith("#include \"")) {
273             line.remove(0,10);
274             int i = line.indexOf("\"");
275             if (i > 0) {
276                 line.remove(i,line.length());
277                 line = QFileInfo(filename).absolutePath() + "/" + line;
278                 if (needsReCheck(line, modified, unmodified)) {
279                     modified.insert(line);
280                     return true;
281                 }
282             }
283         }
284     }
285 
286     return false;
287 }
288 
getCheckStartTime() const289 QDateTime ThreadHandler::getCheckStartTime() const
290 {
291     return mCheckStartTime;
292 }
293 
setCheckStartTime(QDateTime checkStartTime)294 void ThreadHandler::setCheckStartTime(QDateTime checkStartTime)
295 {
296     mCheckStartTime = std::move(checkStartTime);
297 }
298