forked from qt-creator/qt-creator
Guard against crashing plugins
If a plugin crashes, ask the user if that plugin should be disabled. For this track which plugin currently is changing its state. Remove that information if the state change was successful. At startup check if there is a plugin for which we wrote, but not removed that information. This is especially interesting if the user installed 3rdparty plugins. Change-Id: I5729aa5c786b653d5bd53304f4fbeaca35ec9e71 Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io> Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
@@ -31,14 +31,18 @@
|
||||
#include "iplugin.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QCryptographicHash>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QEventLoop>
|
||||
#include <QFile>
|
||||
#include <QGuiApplication>
|
||||
#include <QLibrary>
|
||||
#include <QLibraryInfo>
|
||||
#include <QMessageBox>
|
||||
#include <QMetaProperty>
|
||||
#include <QPushButton>
|
||||
#include <QSettings>
|
||||
#include <QSysInfo>
|
||||
#include <QTextStream>
|
||||
@@ -1353,6 +1357,94 @@ bool PluginManagerPrivate::loadQueue(PluginSpec *spec,
|
||||
return true;
|
||||
}
|
||||
|
||||
class LockFile
|
||||
{
|
||||
public:
|
||||
static QString filePath(PluginManagerPrivate *pm)
|
||||
{
|
||||
return QFileInfo(pm->settings->fileName()).absolutePath() + '/'
|
||||
+ QCoreApplication::applicationName() + '.'
|
||||
+ QCryptographicHash::hash(QCoreApplication::applicationDirPath().toUtf8(),
|
||||
QCryptographicHash::Sha1)
|
||||
.left(8)
|
||||
.toHex()
|
||||
+ ".lock";
|
||||
}
|
||||
|
||||
static Utils::optional<QString> lockedPluginName(PluginManagerPrivate *pm)
|
||||
{
|
||||
const QString lockFilePath = LockFile::filePath(pm);
|
||||
if (QFile::exists(lockFilePath)) {
|
||||
QFile f(lockFilePath);
|
||||
f.open(QIODevice::ReadOnly);
|
||||
const auto pluginName = QString::fromUtf8(f.readLine()).trimmed();
|
||||
f.close();
|
||||
return pluginName;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
LockFile(PluginManagerPrivate *pm, PluginSpec *spec)
|
||||
: m_filePath(filePath(pm))
|
||||
{
|
||||
QFile f(m_filePath);
|
||||
f.open(QIODevice::WriteOnly);
|
||||
f.write(spec->name().toUtf8());
|
||||
f.write("\n");
|
||||
f.close();
|
||||
}
|
||||
|
||||
~LockFile() { QFile::remove(m_filePath); }
|
||||
|
||||
private:
|
||||
QString m_filePath;
|
||||
};
|
||||
|
||||
void PluginManagerPrivate::checkForProblematicPlugins()
|
||||
{
|
||||
const Utils::optional<QString> pluginName = LockFile::lockedPluginName(this);
|
||||
if (pluginName) {
|
||||
PluginSpec *spec = pluginByName(*pluginName);
|
||||
if (spec && !spec->isRequired()) {
|
||||
const QSet<PluginSpec *> dependents = PluginManager::pluginsRequiringPlugin(spec);
|
||||
auto dependentsNames = Utils::transform<QStringList>(dependents, &PluginSpec::name);
|
||||
std::sort(dependentsNames.begin(), dependentsNames.end());
|
||||
const QString dependentsList = dependentsNames.join(", ");
|
||||
const QString pluginsMenu = HostOsInfo::isMacHost()
|
||||
? tr("%1 > About Plugins")
|
||||
.arg(QGuiApplication::applicationDisplayName())
|
||||
: tr("Help > About Plugins");
|
||||
const QString otherPluginsText = tr("The following plugins depend on "
|
||||
"%1 and are also disabled: %2.\n\n")
|
||||
.arg(spec->name(), dependentsList);
|
||||
const QString detailsText = (dependents.isEmpty() ? QString() : otherPluginsText)
|
||||
+ tr("Disable plugins permanently in %1.").arg(pluginsMenu);
|
||||
const QString text = tr("It looks like %1 closed because of a problem with the \"%2\" "
|
||||
"plugin. Temporarily disable the plugin?")
|
||||
.arg(QGuiApplication::applicationDisplayName(), spec->name());
|
||||
QMessageBox dialog;
|
||||
dialog.setIcon(QMessageBox::Question);
|
||||
dialog.setText(text);
|
||||
dialog.setDetailedText(detailsText);
|
||||
QPushButton *disableButton = dialog.addButton(tr("Disable Plugin"),
|
||||
QMessageBox::AcceptRole);
|
||||
dialog.addButton(tr("Continue"), QMessageBox::RejectRole);
|
||||
dialog.exec();
|
||||
if (dialog.clickedButton() == disableButton) {
|
||||
spec->d->setForceDisabled(true);
|
||||
for (PluginSpec *other : dependents)
|
||||
other->d->setForceDisabled(true);
|
||||
enableDependenciesIndirectly();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PluginManager::checkForProblematicPlugins()
|
||||
{
|
||||
d->checkForProblematicPlugins();
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
*/
|
||||
@@ -1365,6 +1457,8 @@ void PluginManagerPrivate::loadPlugin(PluginSpec *spec, PluginSpec::State destSt
|
||||
if (!spec->isEffectivelyEnabled() && destState == PluginSpec::Loaded)
|
||||
return;
|
||||
|
||||
LockFile f(this, spec);
|
||||
|
||||
switch (destState) {
|
||||
case PluginSpec::Running:
|
||||
profilingReport(">initializeExtensions", spec);
|
||||
|
||||
Reference in New Issue
Block a user