From 992dec2c6b9e96f7c1ec78b0c4fa21e96f6aa4d8 Mon Sep 17 00:00:00 2001 From: Alexander Golubev Date: Fri, 10 Mar 2023 04:59:45 +0300 Subject: [PATCH] Gui: Prevent UiLoader from loading 3rd-party Qt plugins. Due to a flaw in the QUiLoader, UiLoader were loading all designer plugins it can find in QApplication::libraryPaths(). This in general a bad practice and leads to bugs due to some plugins may perform some unexpected actions upon load which may interfere with FreeCAD's functionality. To avoid such problems reset the libraryPaths before creation of a UiLoader object. Also move setLanguageChangeEnabled(true) into constructor due to it's called every time it's being instanced anyway. See: https://github.com/FreeCAD/FreeCAD/issues/8708 --- src/Gui/PropertyPage.cpp | 7 ++--- src/Gui/TaskView/TaskDialogPython.cpp | 5 ++-- src/Gui/UiLoader.cpp | 41 +++++++++++++++++---------- src/Gui/UiLoader.h | 25 ++++++++++++++-- src/Gui/WidgetFactory.cpp | 5 ++-- 5 files changed, 56 insertions(+), 27 deletions(-) diff --git a/src/Gui/PropertyPage.cpp b/src/Gui/PropertyPage.cpp index b5e92d6ae0..16c09c7711 100644 --- a/src/Gui/PropertyPage.cpp +++ b/src/Gui/PropertyPage.cpp @@ -119,12 +119,11 @@ void PreferencePage::changeEvent(QEvent *e) PreferenceUiForm::PreferenceUiForm(const QString& fn, QWidget* parent) : PreferencePage(parent), form(nullptr) { - UiLoader loader; - loader.setLanguageChangeEnabled(true); - loader.setWorkingDirectory(QFileInfo(fn).absolutePath()); + auto loader = UiLoader::newInstance(); + loader->setWorkingDirectory(QFileInfo(fn).absolutePath()); QFile file(fn); if (file.open(QFile::ReadOnly)) - form = loader.load(&file, this); + form = loader->load(&file, this); file.close(); if (form) { this->setWindowTitle(form->windowTitle()); diff --git a/src/Gui/TaskView/TaskDialogPython.cpp b/src/Gui/TaskView/TaskDialogPython.cpp index 7d05b12411..795b7cf43a 100644 --- a/src/Gui/TaskView/TaskDialogPython.cpp +++ b/src/Gui/TaskView/TaskDialogPython.cpp @@ -544,8 +544,7 @@ TaskDialogPython::~TaskDialogPython() bool TaskDialogPython::tryLoadUiFile() { if (dlg.hasAttr(std::string("ui"))) { - UiLoader loader; - loader.setLanguageChangeEnabled(true); + auto loader = UiLoader::newInstance(); QString fn, icon; Py::String ui(dlg.getAttr(std::string("ui"))); std::string path = static_cast(ui); @@ -554,7 +553,7 @@ bool TaskDialogPython::tryLoadUiFile() QFile file(fn); QWidget* form = nullptr; if (file.open(QFile::ReadOnly)) - form = loader.load(&file, nullptr); + form = loader->load(&file, nullptr); file.close(); if (form) { appendForm(form, QPixmap(icon)); diff --git a/src/Gui/UiLoader.cpp b/src/Gui/UiLoader.cpp index 002f9affbe..20db81ce55 100644 --- a/src/Gui/UiLoader.cpp +++ b/src/Gui/UiLoader.cpp @@ -24,6 +24,7 @@ #ifndef _PreComp_ # include # include +# include # include # include # include @@ -488,10 +489,20 @@ QString QUiLoader::errorString() const UiLoader::UiLoader(QObject* parent) : QUiLoader(parent) { - // do not use the plugins for additional widgets as we don't need them and - // the application may crash under Linux (tested on Ubuntu 7.04 & 7.10). - clearPluginPaths(); this->cw = availableWidgets(); + setLanguageChangeEnabled(true); +} + +std::unique_ptr UiLoader::newInstance(QObject *parent) +{ + QCoreApplication *app=QCoreApplication::instance(); + QStringList libPaths= app->libraryPaths(); + + app->setLibraryPaths(QStringList{}); //< backup library paths, so QUiLoader won't load plugins by default + std::unique_ptr rv{new UiLoader{parent}}; + app->setLibraryPaths(libPaths); + + return rv; } UiLoader::~UiLoader() @@ -544,8 +555,8 @@ void UiLoaderPy::init_type() } UiLoaderPy::UiLoaderPy() + : loader{UiLoader::newInstance()} { - loader.setLanguageChangeEnabled(true); } UiLoaderPy::~UiLoaderPy() @@ -592,7 +603,7 @@ Py::Object UiLoaderPy::load(const Py::Tuple& args) } if (device) { - QWidget* widget = loader.load(device, parent); + QWidget* widget = loader->load(device, parent); if (widget) { wrap.loadGuiModule(); wrap.loadWidgetsModule(); @@ -613,7 +624,7 @@ Py::Object UiLoaderPy::load(const Py::Tuple& args) Py::Object UiLoaderPy::createWidget(const Py::Tuple& args) { - return wrapFromWidgetFactory(args, std::bind(&UiLoader::createWidget, &loader, + return wrapFromWidgetFactory(args, std::bind(&UiLoader::createWidget, loader.get(), std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); @@ -625,7 +636,7 @@ Py::Object UiLoaderPy::addPluginPath(const Py::Tuple& args) if (wrap.loadCoreModule()) { std::string fn; if (wrap.toCString(args[0], fn)) { - loader.addPluginPath(QString::fromStdString(fn)); + loader->addPluginPath(QString::fromStdString(fn)); } } return Py::None(); @@ -633,13 +644,13 @@ Py::Object UiLoaderPy::addPluginPath(const Py::Tuple& args) Py::Object UiLoaderPy::clearPluginPaths(const Py::Tuple& /*args*/) { - loader.clearPluginPaths(); + loader->clearPluginPaths(); return Py::None(); } Py::Object UiLoaderPy::pluginPaths(const Py::Tuple& /*args*/) { - auto list = loader.pluginPaths(); + auto list = loader->pluginPaths(); Py::List py; for (const auto& it : list) { py.append(Py::String(it.toStdString())); @@ -649,7 +660,7 @@ Py::Object UiLoaderPy::pluginPaths(const Py::Tuple& /*args*/) Py::Object UiLoaderPy::availableWidgets(const Py::Tuple& /*args*/) { - auto list = loader.availableWidgets(); + auto list = loader->availableWidgets(); Py::List py; for (const auto& it : list) { py.append(Py::String(it.toStdString())); @@ -665,17 +676,17 @@ Py::Object UiLoaderPy::availableWidgets(const Py::Tuple& /*args*/) Py::Object UiLoaderPy::errorString(const Py::Tuple& /*args*/) { - return Py::String(loader.errorString().toStdString()); + return Py::String(loader->errorString().toStdString()); } Py::Object UiLoaderPy::isLanguageChangeEnabled(const Py::Tuple& /*args*/) { - return Py::Boolean(loader.isLanguageChangeEnabled()); + return Py::Boolean(loader->isLanguageChangeEnabled()); } Py::Object UiLoaderPy::setLanguageChangeEnabled(const Py::Tuple& args) { - loader.setLanguageChangeEnabled(Py::Boolean(args[0])); + loader->setLanguageChangeEnabled(Py::Boolean(args[0])); return Py::None(); } @@ -685,7 +696,7 @@ Py::Object UiLoaderPy::setWorkingDirectory(const Py::Tuple& args) if (wrap.loadCoreModule()) { std::string fn; if (wrap.toCString(args[0], fn)) { - loader.setWorkingDirectory(QString::fromStdString(fn)); + loader->setWorkingDirectory(QString::fromStdString(fn)); } } return Py::None(); @@ -693,7 +704,7 @@ Py::Object UiLoaderPy::setWorkingDirectory(const Py::Tuple& args) Py::Object UiLoaderPy::workingDirectory(const Py::Tuple& /*args*/) { - QDir dir = loader.workingDirectory(); + QDir dir = loader->workingDirectory(); QString path = dir.absolutePath(); return Py::String(path.toStdString()); } diff --git a/src/Gui/UiLoader.h b/src/Gui/UiLoader.h index 112138bd57..94a7ab16dc 100644 --- a/src/Gui/UiLoader.h +++ b/src/Gui/UiLoader.h @@ -106,8 +106,29 @@ private: */ class UiLoader : public QUiLoader { -public: +protected: + /** + * A protected construct for UiLoader. + * To create an instance of UiLoader @see UiLoader::newInstance() + */ explicit UiLoader(QObject* parent=nullptr); + +public: + /** + * Creates a new instance of a UiLoader. + * + * Due to its flaw the QUiLoader upon creation loads every available Qt + * designer plugin it can find in QApplication::libraryPaths(). Some of + * those plugins may perform some unexpected actions upon load which may + * interfere with FreeCAD's functionality. Only way to avoid such behaviour + * is to reset QApplication::libraryPaths, create a QUiLoader and then + * restore the libs paths. Hence need for this function to wrap + * construction. + * + * @see https://github.com/FreeCAD/FreeCAD/issues/8708 + */ + static std::unique_ptr newInstance(QObject *parent=0); + ~UiLoader() override; /** @@ -149,7 +170,7 @@ private: static PyObject *PyMake(struct _typeobject *, PyObject *, PyObject *); private: - UiLoader loader; + std::unique_ptr loader; }; } // namespace Gui diff --git a/src/Gui/WidgetFactory.cpp b/src/Gui/WidgetFactory.cpp index 298ab3b02f..6c2503b904 100644 --- a/src/Gui/WidgetFactory.cpp +++ b/src/Gui/WidgetFactory.cpp @@ -461,11 +461,10 @@ void PyResource::load(const char* name) QWidget* w=nullptr; try { - UiLoader loader; - loader.setLanguageChangeEnabled(true); + auto loader = UiLoader::newInstance(); QFile file(fn); if (file.open(QFile::ReadOnly)) - w = loader.load(&file, QApplication::activeWindow()); + w = loader->load(&file, QApplication::activeWindow()); file.close(); } catch (...) {