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
This commit is contained in:
Alexander Golubev
2023-03-10 04:59:45 +03:00
committed by wwmayer
parent 64eb85b1ea
commit 992dec2c6b
5 changed files with 56 additions and 27 deletions

View File

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

View File

@@ -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<std::string>(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));

View File

@@ -24,6 +24,7 @@
#ifndef _PreComp_
# include <QAction>
# include <QActionGroup>
# include <QCoreApplication>
# include <QDir>
# include <QFile>
# include <QLayout>
@@ -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> 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<UiLoader> 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());
}

View File

@@ -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<UiLoader> newInstance(QObject *parent=0);
~UiLoader() override;
/**
@@ -149,7 +170,7 @@ private:
static PyObject *PyMake(struct _typeobject *, PyObject *, PyObject *);
private:
UiLoader loader;
std::unique_ptr<UiLoader> loader;
};
} // namespace Gui

View File

@@ -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 (...) {