Lua: Add connection guard

Improves safety by limiting connection lifetime to
lifetime of lua vm.

Change-Id: I508b0814fdda169f5e30d7c3282132e3c622a0a5
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Marcus Tillmanns
2024-05-28 12:35:25 +02:00
parent 26cfa86969
commit b9f2808384
7 changed files with 78 additions and 71 deletions

View File

@@ -39,6 +39,8 @@ void addFetchModule()
{
LuaEngine::registerProvider(
"Fetch", [](sol::state_view lua) -> sol::object {
const ScriptPluginSpec *pluginSpec = lua.get<ScriptPluginSpec *>("PluginSpec");
sol::table async = lua.script("return require('async')", "_fetch_").get<sol::table>();
sol::function wrap = async["wrap"];
@@ -58,9 +60,10 @@ void addFetchModule()
.arg(r->error());
});
fetch["fetch_cb"] = [](const sol::table &options,
const sol::function &callback,
const sol::this_state &thisState) {
fetch["fetch_cb"] = [guard = pluginSpec->connectionGuard.get()](
const sol::table &options,
const sol::function &callback,
const sol::this_state &thisState) {
auto url = options.get<QString>("url");
auto method = (options.get_or<QString>("method", "GET")).toLower();
@@ -85,10 +88,7 @@ void addFetchModule()
if (convertToTable) {
QObject::connect(
reply,
&QNetworkReply::finished,
&LuaEngine::instance(),
[reply, thisState, callback]() {
reply, &QNetworkReply::finished, guard, [reply, thisState, callback]() {
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
@@ -120,7 +120,7 @@ void addFetchModule()
} else {
QObject::connect(
reply, &QNetworkReply::finished, &LuaEngine::instance(), [reply, callback]() {
reply, &QNetworkReply::finished, guard, [reply, callback]() {
// We don't want the network reply to be deleted by the manager, but
// by the Lua GC
reply->setParent(nullptr);

View File

@@ -253,12 +253,12 @@ void addInstallModule()
sol::function wrap = async["wrap"];
sol::table install = lua.create_table();
const ScriptPluginSpec pluginSpec = lua.get<ScriptPluginSpec>("PluginSpec");
const ScriptPluginSpec *pluginSpec = lua.get<ScriptPluginSpec *>("PluginSpec");
install["packageInfo"] =
[pluginSpec](const QString &name, sol::this_state l) -> sol::optional<sol::table> {
expected_str<QJsonObject> obj
= getInstalledPackageInfo(pluginSpec.appDataPath, name);
= getInstalledPackageInfo(pluginSpec->appDataPath, name);
if (!obj)
throw sol::error(obj.error().toStdString());
@@ -302,7 +302,7 @@ void addInstallModule()
}
const Utils::Id infoBarId = Utils::Id::fromString(
"Install" + pluginSpec.name + QString::number(qHash(installOptionsList)));
"Install" + pluginSpec->name + QString::number(qHash(installOptionsList)));
InfoBarEntry entry(infoBarId, msg, InfoBarEntry::GlobalSuppression::Enabled);
@@ -315,7 +315,7 @@ void addInstallModule()
progress->setDisplayName(Tr::tr("Installing package(s) %1").arg("..."));
tree->setRecipe(
installRecipe(pluginSpec.appDataPath, installOptionsList, callback));
installRecipe(pluginSpec->appDataPath, installOptionsList, callback));
tree->start();
Core::ICore::infoBar()->removeInfo(infoBarId);
@@ -327,7 +327,7 @@ void addInstallModule()
const QString markdown
= Tr::tr("The plugin \"**%1**\" would like to install the following "
"package(s):\n\n")
.arg(pluginSpec.name)
.arg(pluginSpec->name)
+ Utils::transform(installOptionsList, [](const InstallOptions &options) {
return QString("* %1 - %2 (from: [%3](%3))")
.arg(options.name, options.version, options.url.toString());

View File

@@ -89,7 +89,7 @@ HAS_MEM_FUNC(setTitle, hasSetTitle);
HAS_MEM_FUNC(setValue, hasSetValue);
template<class T>
void setProperties(std::unique_ptr<T> &item, const sol::table &children)
void setProperties(std::unique_ptr<T> &item, const sol::table &children, QObject *guard)
{
if constexpr (hasOnTextChanged<T, void (T::*)(const QString &)>::value) {
sol::optional<sol::protected_function> onTextChanged
@@ -100,7 +100,7 @@ void setProperties(std::unique_ptr<T> &item, const sol::table &children)
auto res = LuaEngine::void_safe_call(f, text);
QTC_CHECK_EXPECTED(res);
},
&LuaEngine::instance());
guard);
}
}
if constexpr (hasOnClicked<T, void (T::*)(const std::function<void()> &, QObject *guard)>::value) {
@@ -112,7 +112,7 @@ void setProperties(std::unique_ptr<T> &item, const sol::table &children)
auto res = LuaEngine::void_safe_call(f);
QTC_CHECK_EXPECTED(res);
},
&LuaEngine::instance());
guard);
}
}
if constexpr (hasSetText<T, void (T::*)(const QString &)>::value) {
@@ -129,11 +129,11 @@ void setProperties(std::unique_ptr<T> &item, const sol::table &children)
}
template<class T>
std::unique_ptr<T> constructWidgetType(const sol::table &children)
std::unique_ptr<T> constructWidgetType(const sol::table &children, QObject *guard)
{
std::unique_ptr<T> item(new T({}));
constructWidget(item, children);
setProperties(item, children);
setProperties(item, children, guard);
return item;
}
@@ -143,10 +143,10 @@ std::unique_ptr<Tab> constructTab(const QString &tabName, const Layout &layout)
return item;
}
std::unique_ptr<TabWidget> constructTabWidget(const sol::table &children)
std::unique_ptr<TabWidget> constructTabWidget(const sol::table &children, QObject *guard)
{
std::unique_ptr<TabWidget> item(new TabWidget({}));
setProperties(item, children);
setProperties(item, children, guard);
for (size_t i = 1; i < children.size(); ++i) {
const auto &child = children[i];
if (child.is<Tab *>())
@@ -177,6 +177,9 @@ std::unique_ptr<Splitter> constructSplitter(const sol::table &children)
void addLayoutModule()
{
LuaEngine::registerProvider("Layout", [](sol::state_view l) -> sol::object {
const ScriptPluginSpec *pluginSpec = l.get<ScriptPluginSpec *>("PluginSpec");
QObject *guard = pluginSpec->connectionGuard.get();
sol::table layout = l.create_table();
layout.new_usertype<Span>(
@@ -225,14 +228,18 @@ void addLayoutModule()
layout.new_usertype<PushButton>(
"PushButton",
sol::call_constructor,
sol::factories(&constructWidgetType<PushButton>),
sol::factories([guard](const sol::table &children) {
return constructWidgetType<PushButton>(children, guard);
}),
sol::base_classes,
sol::bases<Widget, Object, Thing>());
layout.new_usertype<Widget>(
"Widget",
sol::call_constructor,
sol::factories(&constructWidgetType<Widget>),
sol::factories([guard](const sol::table &children) {
return constructWidgetType<Widget>(children, guard);
}),
"show",
&Widget::show,
"resize",
@@ -243,7 +250,9 @@ void addLayoutModule()
layout.new_usertype<Stack>(
"Stack",
sol::call_constructor,
sol::factories(&constructWidgetType<Stack>),
sol::factories([guard](const sol::table &children) {
return constructWidgetType<Stack>(children, guard);
}),
sol::base_classes,
sol::bases<Widget, Object, Thing>());
@@ -257,14 +266,18 @@ void addLayoutModule()
layout.new_usertype<TextEdit>(
"TextEdit",
sol::call_constructor,
sol::factories(&constructWidgetType<TextEdit>),
sol::factories([guard](const sol::table &children) {
return constructWidgetType<TextEdit>(children, guard);
}),
sol::base_classes,
sol::bases<Widget, Object, Thing>());
layout.new_usertype<SpinBox>(
"SpinBox",
sol::call_constructor,
sol::factories(&constructWidgetType<SpinBox>),
sol::factories([guard](const sol::table &children) {
return constructWidgetType<SpinBox>(children, guard);
}),
sol::base_classes,
sol::bases<Widget, Object, Thing>());
layout.new_usertype<Splitter>(
@@ -276,20 +289,26 @@ void addLayoutModule()
layout.new_usertype<ToolBar>(
"ToolBar",
sol::call_constructor,
sol::factories(&constructWidgetType<ToolBar>),
sol::factories([guard](const sol::table &children) {
return constructWidgetType<ToolBar>(children, guard);
}),
sol::base_classes,
sol::bases<Widget, Object, Thing>());
layout.new_usertype<TabWidget>(
"TabWidget",
sol::call_constructor,
sol::factories(&constructTabWidget),
sol::factories([guard](const sol::table &children) {
return constructTabWidget(children, guard);
}),
sol::base_classes,
sol::bases<Widget, Object, Thing>());
layout.new_usertype<Group>(
"Group",
sol::call_constructor,
sol::factories(&constructWidgetType<Group>),
sol::factories([guard](const sol::table &children) {
return constructWidgetType<Group>(children, guard);
}),
sol::base_classes,
sol::bases<Widget, Object, Thing>());
@@ -300,22 +319,6 @@ void addLayoutModule()
layout["noMargin"] = &noMargin;
layout["normalMargin"] = &normalMargin;
//layout["customMargin"] = [](int left, int top, int right, int bottom) {
// return customMargin(QMargins(left, top, right, bottom));
//};
// layout["withFormAlignment"] = &withFormAlignment;
// layout["columnStretch"] = &columnStretch;
// layout["spacing"] = &spacing;
// layout["windowTitle"] = &windowTitle;
// layout["fieldGrowthPolicy"] = &fieldGrowthPolicy;
// layout["id"] = &id;
layout["onTextChanged"] = [](const sol::function &f) {
return onTextChanged([f](const QString &text) {
auto res = LuaEngine::void_safe_call(f, text);
QTC_CHECK_EXPECTED(res);
});
};
return layout;
});
}

View File

@@ -12,30 +12,31 @@ namespace Lua::Internal {
void addProcessModule()
{
LuaEngine::registerProvider(
"Process", [](sol::state_view lua) -> sol::object {
sol::table async = lua.script("return require('async')", "_process_").get<sol::table>();
sol::function wrap = async["wrap"];
LuaEngine::registerProvider("Process", [](sol::state_view lua) -> sol::object {
const ScriptPluginSpec *pluginSpec = lua.get<ScriptPluginSpec *>("PluginSpec");
sol::table process = lua.create_table();
sol::table async = lua.script("return require('async')", "_process_").get<sol::table>();
sol::function wrap = async["wrap"];
process["runInTerminal_cb"] = [](const QString &cmdline, const sol::function &cb) {
sol::table process = lua.create_table();
process["runInTerminal_cb"] =
[guard
= pluginSpec->connectionGuard.get()](const QString &cmdline, const sol::function &cb) {
Process *p = new Process;
p->setTerminalMode(TerminalMode::Run);
p->setCommand(CommandLine::fromUserInput((cmdline)));
p->setEnvironment(Environment::systemEnvironment());
QObject::connect(p, &Process::done, &LuaEngine::instance(), [p, cb]() {
cb(p->exitCode());
});
QObject::connect(p, &Process::done, guard, [p, cb]() { cb(p->exitCode()); });
p->start();
};
process["runInTerminal"] = wrap(process["runInTerminal_cb"]);
process["runInTerminal"] = wrap(process["runInTerminal_cb"]);
return process;
});
return process;
});
}
} // namespace Lua::Internal

View File

@@ -19,16 +19,19 @@ void addUtilsModule()
LuaEngine::registerProvider(
"Utils",
[futureSync = Utils::FutureSynchronizer()](sol::state_view lua) mutable -> sol::object {
const ScriptPluginSpec *pluginSpec = lua.get<ScriptPluginSpec *>("PluginSpec");
auto async = lua.script("return require('async')", "_utils_").get<sol::table>();
sol::table utils = lua.create_table();
utils.set_function("waitms_cb", [](int ms, const sol::function &cb) {
QTimer::singleShot(ms, &LuaEngine::instance(), [cb]() { cb(); });
utils.set_function("waitms_cb", [guard = pluginSpec->connectionGuard.get()](int ms, const sol::function &cb) {
QTimer::singleShot(ms, guard, [cb]() { cb(); });
});
auto dirEntries_cb =
[&futureSync](const FilePath &p, const sol::table &options, const sol::function &cb) {
[&futureSync, guard = pluginSpec->connectionGuard.get()](
const FilePath &p, const sol::table &options, const sol::function &cb) {
const QStringList nameFilters = options.get_or<QStringList>("nameFilters", {});
QDir::Filters fileFilters
= (QDir::Filters) options.get_or<int>("fileFilters", QDir::NoFilter);
@@ -53,22 +56,22 @@ void addUtilsModule()
futureSync.addFuture<FilePath>(future);
Utils::onFinished<FilePath>(
future, &LuaEngine::instance(), [cb](const QFuture<FilePath> &future) {
cb(future.results());
});
Utils::onFinished<FilePath>(future, guard, [cb](const QFuture<FilePath> &future) {
cb(future.results());
});
};
auto searchInPath_cb = [&futureSync](const FilePath &p, const sol::function &cb) {
auto searchInPath_cb = [&futureSync,
guard = pluginSpec->connectionGuard
.get()](const FilePath &p, const sol::function &cb) {
QFuture<FilePath> future = Utils::asyncRun(
[p](QPromise<FilePath> &promise) { promise.addResult(p.searchInPath()); });
futureSync.addFuture<FilePath>(future);
Utils::onFinished<FilePath>(
future, &LuaEngine::instance(), [cb](const QFuture<FilePath> &future) {
cb(future.result());
});
Utils::onFinished<FilePath>(future, guard, [cb](const QFuture<FilePath> &future) {
cb(future.result());
});
};
utils.set_function("__dirEntries_cb__", dirEntries_cb);

View File

@@ -156,14 +156,13 @@ expected_str<void> LuaEngine::prepareSetup(
const FilePath appDataPath = Core::ICore::userResourcePath() / "plugin-data" / "lua"
/ pluginSpec.location().fileName();
sol::environment env(lua, sol::create, lua.globals());
lua.new_usertype<ScriptPluginSpec>(
"PluginSpec", sol::no_constructor, "name", sol::property([](ScriptPluginSpec &self) {
return self.name;
}));
lua["PluginSpec"] = ScriptPluginSpec{pluginSpec.name(), appDataPath};
lua["PluginSpec"]
= ScriptPluginSpec{pluginSpec.name(), appDataPath, std::make_unique<QObject>()};
// TODO: only register what the plugin requested
for (const auto &[name, func] : d->m_providers.asKeyValueRange()) {

View File

@@ -37,6 +37,7 @@ struct ScriptPluginSpec
{
QString name;
Utils::FilePath appDataPath;
std::unique_ptr<QObject> connectionGuard;
};
class LUA_EXPORT LuaEngine final : public QObject