1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-02-23 16:40:08 +00:00

Merge branch 'limit_layout_depth' into 'master'

Limit maximum Lua UI layout depth to prevent stack overflow

Closes #6733

See merge request OpenMW/openmw!1841
This commit is contained in:
Petr Mikheev 2022-05-19 16:56:59 +00:00
commit b832ba5c83

View file

@ -8,156 +8,165 @@
namespace LuaUi namespace LuaUi
{ {
namespace LayoutKeys namespace
{ {
constexpr std::string_view type = "type"; namespace LayoutKeys
constexpr std::string_view name = "name";
constexpr std::string_view layer = "layer";
constexpr std::string_view templateLayout = "template";
constexpr std::string_view props = "props";
constexpr std::string_view events = "events";
constexpr std::string_view content = "content";
constexpr std::string_view external = "external";
}
const std::string defaultWidgetType = "LuaWidget";
std::string widgetType(const sol::table& layout)
{
sol::object typeField = LuaUtil::getFieldOrNil(layout, LayoutKeys::type);
std::string type = LuaUtil::getValueOrDefault(typeField, defaultWidgetType);
sol::object templateTypeField = LuaUtil::getFieldOrNil(layout, LayoutKeys::templateLayout, LayoutKeys::type);
if (templateTypeField != sol::nil)
{ {
std::string templateType = LuaUtil::getValueOrDefault(templateTypeField, defaultWidgetType); constexpr std::string_view type = "type";
if (typeField != sol::nil && templateType != type) constexpr std::string_view name = "name";
throw std::logic_error(std::string("Template layout type ") + type constexpr std::string_view layer = "layer";
+ std::string(" doesn't match template type ") + templateType); constexpr std::string_view templateLayout = "template";
type = templateType; constexpr std::string_view props = "props";
constexpr std::string_view events = "events";
constexpr std::string_view content = "content";
constexpr std::string_view external = "external";
} }
return type;
}
void destroyWidget(LuaUi::WidgetExtension* ext) const std::string defaultWidgetType = "LuaWidget";
{
ext->deinitialize();
MyGUI::Gui::getInstancePtr()->destroyWidget(ext->widget());
}
WidgetExtension* createWidget(const sol::table& layout); constexpr uint64_t maxDepth = 250;
void updateWidget(WidgetExtension* ext, const sol::table& layout);
std::vector<WidgetExtension*> updateContent( std::string widgetType(const sol::table& layout)
const std::vector<WidgetExtension*>& children, const sol::object& contentObj)
{
std::vector<WidgetExtension*> result;
if (contentObj == sol::nil)
{ {
for (WidgetExtension* w : children) sol::object typeField = LuaUtil::getFieldOrNil(layout, LayoutKeys::type);
destroyWidget(w); std::string type = LuaUtil::getValueOrDefault(typeField, defaultWidgetType);
sol::object templateTypeField = LuaUtil::getFieldOrNil(layout, LayoutKeys::templateLayout, LayoutKeys::type);
if (templateTypeField != sol::nil)
{
std::string templateType = LuaUtil::getValueOrDefault(templateTypeField, defaultWidgetType);
if (typeField != sol::nil && templateType != type)
throw std::logic_error(std::string("Template layout type ") + type
+ std::string(" doesn't match template type ") + templateType);
type = templateType;
}
return type;
}
void destroyWidget(LuaUi::WidgetExtension* ext)
{
ext->deinitialize();
MyGUI::Gui::getInstancePtr()->destroyWidget(ext->widget());
}
WidgetExtension* createWidget(const sol::table& layout, uint64_t depth);
void updateWidget(WidgetExtension* ext, const sol::table& layout, uint64_t depth);
std::vector<WidgetExtension*> updateContent(
const std::vector<WidgetExtension*>& children, const sol::object& contentObj, uint64_t depth)
{
++depth;
std::vector<WidgetExtension*> result;
if (contentObj == sol::nil)
{
for (WidgetExtension* w : children)
destroyWidget(w);
return result;
}
if (!contentObj.is<Content>())
throw std::logic_error("Layout content field must be a openmw.ui.content");
Content content = contentObj.as<Content>();
result.resize(content.size());
size_t minSize = std::min(children.size(), content.size());
for (size_t i = 0; i < minSize; i++)
{
WidgetExtension* ext = children[i];
sol::table newLayout = content.at(i);
if (ext->widget()->getTypeName() == widgetType(newLayout))
{
updateWidget(ext, newLayout, depth);
}
else
{
destroyWidget(ext);
ext = createWidget(newLayout, depth);
}
result[i] = ext;
}
for (size_t i = minSize; i < children.size(); i++)
destroyWidget(children[i]);
for (size_t i = minSize; i < content.size(); i++)
result[i] = createWidget(content.at(i), depth);
return result; return result;
} }
if (!contentObj.is<Content>())
throw std::logic_error("Layout content field must be a openmw.ui.content"); void setTemplate(WidgetExtension* ext, const sol::object& templateLayout, uint64_t depth)
Content content = contentObj.as<Content>();
result.resize(content.size());
size_t minSize = std::min(children.size(), content.size());
for (size_t i = 0; i < minSize; i++)
{ {
WidgetExtension* ext = children[i]; ++depth;
sol::table newLayout = content.at(i); sol::object props = LuaUtil::getFieldOrNil(templateLayout, LayoutKeys::props);
if (ext->widget()->getTypeName() == widgetType(newLayout)) ext->setTemplateProperties(props);
{ sol::object content = LuaUtil::getFieldOrNil(templateLayout, LayoutKeys::content);
updateWidget(ext, newLayout); ext->setTemplateChildren(updateContent(ext->templateChildren(), content, depth));
}
else
{
destroyWidget(ext);
ext = createWidget(newLayout);
}
result[i] = ext;
} }
for (size_t i = minSize; i < children.size(); i++)
destroyWidget(children[i]);
for (size_t i = minSize; i < content.size(); i++)
result[i] = createWidget(content.at(i));
return result;
}
void setTemplate(WidgetExtension* ext, const sol::object& templateLayout) void setEventCallbacks(LuaUi::WidgetExtension* ext, const sol::object& eventsObj)
{
sol::object props = LuaUtil::getFieldOrNil(templateLayout, LayoutKeys::props);
ext->setTemplateProperties(props);
sol::object content = LuaUtil::getFieldOrNil(templateLayout, LayoutKeys::content);
ext->setTemplateChildren(updateContent(ext->templateChildren(), content));
}
void setEventCallbacks(LuaUi::WidgetExtension* ext, const sol::object& eventsObj)
{
ext->clearCallbacks();
if (eventsObj == sol::nil)
return;
if (!eventsObj.is<sol::table>())
throw std::logic_error("The \"events\" layout field must be a table of callbacks");
auto events = eventsObj.as<sol::table>();
events.for_each([ext](const sol::object& name, const sol::object& callback)
{ {
if (name.is<std::string>() && callback.is<LuaUtil::Callback>()) ext->clearCallbacks();
ext->setCallback(name.as<std::string>(), callback.as<LuaUtil::Callback>()); if (eventsObj == sol::nil)
else if (!name.is<std::string>()) return;
Log(Debug::Warning) << "UI event key must be a string"; if (!eventsObj.is<sol::table>())
else if (!callback.is<LuaUtil::Callback>()) throw std::logic_error("The \"events\" layout field must be a table of callbacks");
Log(Debug::Warning) << "UI event handler for key \"" << name.as<std::string>() auto events = eventsObj.as<sol::table>();
<< "\" must be an openmw.async.callback"; events.for_each([ext](const sol::object& name, const sol::object& callback)
}); {
} if (name.is<std::string>() && callback.is<LuaUtil::Callback>())
ext->setCallback(name.as<std::string>(), callback.as<LuaUtil::Callback>());
WidgetExtension* createWidget(const sol::table& layout) else if (!name.is<std::string>())
{ Log(Debug::Warning) << "UI event key must be a string";
static auto widgetTypeMap = widgetTypeToName(); else if (!callback.is<LuaUtil::Callback>())
std::string type = widgetType(layout); Log(Debug::Warning) << "UI event handler for key \"" << name.as<std::string>()
if (widgetTypeMap.find(type) == widgetTypeMap.end()) << "\" must be an openmw.async.callback";
throw std::logic_error(std::string("Invalid widget type ") += type); });
}
std::string name = layout.get_or(LayoutKeys::name, std::string());
MyGUI::Widget* widget = MyGUI::Gui::getInstancePtr()->createWidgetT( WidgetExtension* createWidget(const sol::table& layout, uint64_t depth)
type, "", {
MyGUI::IntCoord(), MyGUI::Align::Default, static auto widgetTypeMap = widgetTypeToName();
std::string(), name); std::string type = widgetType(layout);
if (widgetTypeMap.find(type) == widgetTypeMap.end())
WidgetExtension* ext = dynamic_cast<WidgetExtension*>(widget); throw std::logic_error(std::string("Invalid widget type ") += type);
if (!ext)
throw std::runtime_error("Invalid widget!"); std::string name = layout.get_or(LayoutKeys::name, std::string());
ext->initialize(layout.lua_state(), widget); MyGUI::Widget* widget = MyGUI::Gui::getInstancePtr()->createWidgetT(
type, "",
updateWidget(ext, layout); MyGUI::IntCoord(), MyGUI::Align::Default,
return ext; std::string(), name);
}
WidgetExtension* ext = dynamic_cast<WidgetExtension*>(widget);
void updateWidget(WidgetExtension* ext, const sol::table& layout) if (!ext)
{ throw std::runtime_error("Invalid widget!");
ext->reset(); ext->initialize(layout.lua_state(), widget);
ext->setLayout(layout);
ext->setExternal(layout.get<sol::object>(LayoutKeys::external)); updateWidget(ext, layout, depth);
setTemplate(ext, layout.get<sol::object>(LayoutKeys::templateLayout)); return ext;
ext->setProperties(layout.get<sol::object>(LayoutKeys::props)); }
setEventCallbacks(ext, layout.get<sol::object>(LayoutKeys::events));
ext->setChildren(updateContent(ext->children(), layout.get<sol::object>(LayoutKeys::content))); void updateWidget(WidgetExtension* ext, const sol::table& layout, uint64_t depth)
ext->updateCoord(); {
} if (depth >= maxDepth)
throw std::runtime_error("Maximum layout depth exceeded, probably caused by a circular reference");
std::string setLayer(WidgetExtension* ext, const sol::table& layout) ext->reset();
{ ext->setLayout(layout);
MyGUI::ILayer* layerNode = ext->widget()->getLayer(); ext->setExternal(layout.get<sol::object>(LayoutKeys::external));
std::string currentLayer = layerNode ? layerNode->getName() : std::string(); setTemplate(ext, layout.get<sol::object>(LayoutKeys::templateLayout), depth);
std::string newLayer = layout.get_or(LayoutKeys::layer, std::string()); ext->setProperties(layout.get<sol::object>(LayoutKeys::props));
if (!newLayer.empty() && !MyGUI::LayerManager::getInstance().isExist(newLayer)) setEventCallbacks(ext, layout.get<sol::object>(LayoutKeys::events));
throw std::logic_error(std::string("Layer ") + newLayer + " doesn't exist"); ext->setChildren(updateContent(ext->children(), layout.get<sol::object>(LayoutKeys::content), depth));
else if (newLayer != currentLayer) ext->updateCoord();
{ }
MyGUI::LayerManager::getInstance().attachToLayerNode(newLayer, ext->widget());
std::string setLayer(WidgetExtension* ext, const sol::table& layout)
{
MyGUI::ILayer* layerNode = ext->widget()->getLayer();
std::string currentLayer = layerNode ? layerNode->getName() : std::string();
std::string newLayer = layout.get_or(LayoutKeys::layer, std::string());
if (!newLayer.empty() && !MyGUI::LayerManager::getInstance().isExist(newLayer))
throw std::logic_error(std::string("Layer ") + newLayer + " doesn't exist");
else if (newLayer != currentLayer)
{
MyGUI::LayerManager::getInstance().attachToLayerNode(newLayer, ext->widget());
}
return newLayer;
} }
return newLayer;
} }
std::map<Element*, std::shared_ptr<Element>> Element::sAllElements; std::map<Element*, std::shared_ptr<Element>> Element::sAllElements;
@ -184,7 +193,7 @@ namespace LuaUi
assert(!mRoot); assert(!mRoot);
if (!mRoot) if (!mRoot)
{ {
mRoot = createWidget(mLayout); mRoot = createWidget(mLayout, 0);
mLayer = setLayer(mRoot, mLayout); mLayer = setLayer(mRoot, mLayout);
updateAttachment(); updateAttachment();
} }
@ -197,11 +206,11 @@ namespace LuaUi
if (mRoot->widget()->getTypeName() != widgetType(mLayout)) if (mRoot->widget()->getTypeName() != widgetType(mLayout))
{ {
destroyWidget(mRoot); destroyWidget(mRoot);
mRoot = createWidget(mLayout); mRoot = createWidget(mLayout, 0);
} }
else else
{ {
updateWidget(mRoot, mLayout); updateWidget(mRoot, mLayout, 0);
} }
mLayer = setLayer(mRoot, mLayout); mLayer = setLayer(mRoot, mLayout);
updateAttachment(); updateAttachment();