ClassView: Fix a possible crash on session switch

Don't call SessionManager methods in non-main thread.
It's not safe to call SessionManger::projects() or
any method of Project class in a non-main thread,
as in meantime the Project object may get deleted
or the Project object may change in main thread in
a not thread-safe way.

Instead, prepare the data needed for the parser's
thread before, when scheduling a call in a main thread,
and pass this data in a safe way.

This fixes possible crash in class view, e.g. on session
switch.

Task-number: QTCREATORBUG-25317
Fixes: QTCREATORBUG-25312
Change-Id: I114aae788aec649d1de3b3d3afdd049ed1e9b2c6
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
Jarek Kobus
2021-03-01 15:01:35 +01:00
parent ced7a2e51f
commit 8fad758f60
4 changed files with 124 additions and 150 deletions

View File

@@ -44,6 +44,7 @@
#include <QTimer>
using namespace Core;
using namespace ProjectExplorer;
using namespace Utils;
namespace ClassView {
@@ -94,7 +95,7 @@ public:
ParserTreeItem::ConstPtr m_root;
QTimer m_timer;
QHash<QString, CPlusPlus::Document::Ptr> m_awaitingDocuments;
QSet<FilePath> m_awaitingDocuments;
//! Internal manager state. \sa Manager::state
bool state = false;
@@ -116,7 +117,15 @@ void ManagerPrivate::cancelScheduledUpdate()
void ManagerPrivate::resetParser()
{
cancelScheduledUpdate();
QMetaObject::invokeMethod(m_parser, &Parser::resetDataToCurrentState, Qt::QueuedConnection);
QHash<FilePath, QPair<QString, FilePaths>> projectData;
for (const Project *project : SessionManager::projects()) {
projectData.insert(project->projectFilePath(),
qMakePair(project->displayName(), project->files(Project::SourceFiles)));
}
QMetaObject::invokeMethod(m_parser, [this, projectData]() {
m_parser->resetData(projectData);
}, Qt::QueuedConnection);
}
/*!
@@ -218,15 +227,26 @@ bool Manager::hasChildren(QStandardItem *item) const
void Manager::initialize()
{
using ProjectExplorer::SessionManager;
d->m_timer.setSingleShot(true);
// connections to enable/disable navi widget factory
SessionManager *sessionManager = SessionManager::instance();
connect(sessionManager, &SessionManager::projectAdded,
this, &Manager::onProjectListChanged);
this, [this](Project *project) {
const FilePath projectPath = project->projectFilePath();
const QString projectName = project->displayName();
const FilePaths projectFiles = project->files(Project::SourceFiles);
QMetaObject::invokeMethod(d->m_parser, [this, projectPath, projectName, projectFiles]() {
d->m_parser->addProject(projectPath, projectName, projectFiles);
}, Qt::QueuedConnection);
});
connect(sessionManager, &SessionManager::projectRemoved,
this, &Manager::onProjectListChanged);
this, [this](Project *project) {
const FilePath projectPath = project->projectFilePath();
QMetaObject::invokeMethod(d->m_parser, [this, projectPath]() {
d->m_parser->removeProject(projectPath);
}, Qt::QueuedConnection);
});
// connect to the progress manager for signals about Parsing tasks
connect(ProgressManager::instance(), &ProgressManager::taskStarted,
@@ -283,12 +303,12 @@ void Manager::initialize()
if (doc.data() == nullptr)
return;
d->m_awaitingDocuments.insert(doc->fileName(), doc);
d->m_awaitingDocuments.insert(FilePath::fromString(doc->fileName()));
d->m_timer.start(400); // Accumulate multiple requests into one, restarts the timer
});
connect(&d->m_timer, &QTimer::timeout, this, [this]() {
const QList<CPlusPlus::Document::Ptr> docsToBeUpdated = d->m_awaitingDocuments.values();
const QSet<FilePath> docsToBeUpdated = d->m_awaitingDocuments;
d->cancelScheduledUpdate();
if (!state() || d->disableCodeParser) // enabling any of them will trigger the total update
return;
@@ -346,20 +366,7 @@ void Manager::onWidgetVisibilityIsChanged(bool visibility)
if (!visibility)
return;
setState(true);
onProjectListChanged();
}
/*!
Reacts to the project list being changed by updating the navigation pane
visibility if necessary.
*/
void Manager::onProjectListChanged()
{
// do nothing if Manager is disabled
if (!state())
return;
// TODO: this one may change into getter (when a new class view widget is being shown)
QMetaObject::invokeMethod(d->m_parser, &Parser::requestCurrentState, Qt::QueuedConnection);
}