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:
Eike Ziller
2020-01-13 16:12:28 +01:00
parent 0334b6e491
commit 392b063fe8
4 changed files with 97 additions and 0 deletions

View File

@@ -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);