From 5d7fc0ab17ff256ced981d3a9dbcfad2953e929f Mon Sep 17 00:00:00 2001
From: uramer <antonuramer@gmail.com>
Date: Tue, 10 May 2022 19:30:46 +0200
Subject: [PATCH 1/2] Limit maximum Lua UI layout depth to prevent stack
 overflow

---
 components/lua_ui/element.cpp | 38 ++++++++++++++++++++---------------
 1 file changed, 22 insertions(+), 16 deletions(-)

diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp
index b55d89e613..1356409340 100644
--- a/components/lua_ui/element.cpp
+++ b/components/lua_ui/element.cpp
@@ -22,6 +22,8 @@ namespace LuaUi
 
     const std::string defaultWidgetType = "LuaWidget";
 
+    const uint64_t maxDepth = 250;
+
     std::string widgetType(const sol::table& layout)
     {
         sol::object typeField = LuaUtil::getFieldOrNil(layout, LayoutKeys::type);
@@ -44,12 +46,13 @@ namespace LuaUi
         MyGUI::Gui::getInstancePtr()->destroyWidget(ext->widget());
     }
 
-    WidgetExtension* createWidget(const sol::table& layout);
-    void updateWidget(WidgetExtension* ext, const sol::table& layout);
+    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)
+        const std::vector<WidgetExtension*>& children, const sol::object& contentObj, uint64_t depth)
     {
+        ++depth;
         std::vector<WidgetExtension*> result;
         if (contentObj == sol::nil)
         {
@@ -68,28 +71,29 @@ namespace LuaUi
             sol::table newLayout = content.at(i);
             if (ext->widget()->getTypeName() == widgetType(newLayout))
             {
-                updateWidget(ext, newLayout);
+                updateWidget(ext, newLayout, depth);
             }
             else
             {
                 destroyWidget(ext);
-                ext = createWidget(newLayout);
+                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));
+            result[i] = createWidget(content.at(i), depth);
         return result;
     }
 
-    void setTemplate(WidgetExtension* ext, const sol::object& templateLayout)
+    void setTemplate(WidgetExtension* ext, const sol::object& templateLayout, uint64_t depth)
     {
+        ++depth;
         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));
+        ext->setTemplateChildren(updateContent(ext->templateChildren(), content, depth));
     }
 
     void setEventCallbacks(LuaUi::WidgetExtension* ext, const sol::object& eventsObj)
@@ -112,7 +116,7 @@ namespace LuaUi
         });
     }
 
-    WidgetExtension* createWidget(const sol::table& layout)
+    WidgetExtension* createWidget(const sol::table& layout, uint64_t depth)
     {
         static auto widgetTypeMap = widgetTypeToName();
         std::string type = widgetType(layout);
@@ -130,19 +134,21 @@ namespace LuaUi
             throw std::runtime_error("Invalid widget!");
         ext->initialize(layout.lua_state(), widget);
 
-        updateWidget(ext, layout);
+        updateWidget(ext, layout, depth);
         return ext;
     }
 
-    void updateWidget(WidgetExtension* ext, const sol::table& layout)
+    void updateWidget(WidgetExtension* ext, const sol::table& layout, uint64_t depth)
     {
+        if (depth >= maxDepth)
+            throw std::runtime_error("Maximum layout depth exceeded, probably caused by a circular reference");
         ext->reset();
         ext->setLayout(layout);
         ext->setExternal(layout.get<sol::object>(LayoutKeys::external));
-        setTemplate(ext, layout.get<sol::object>(LayoutKeys::templateLayout));
+        setTemplate(ext, layout.get<sol::object>(LayoutKeys::templateLayout), depth);
         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)));
+        ext->setChildren(updateContent(ext->children(), layout.get<sol::object>(LayoutKeys::content), depth));
         ext->updateCoord();
     }
 
@@ -184,7 +190,7 @@ namespace LuaUi
         assert(!mRoot);
         if (!mRoot)
         {
-            mRoot = createWidget(mLayout);
+            mRoot = createWidget(mLayout, 0);
             mLayer = setLayer(mRoot, mLayout);
             updateAttachment();
         }
@@ -197,11 +203,11 @@ namespace LuaUi
             if (mRoot->widget()->getTypeName() != widgetType(mLayout))
             {
                 destroyWidget(mRoot);
-                mRoot = createWidget(mLayout);
+                mRoot = createWidget(mLayout, 0);
             }
             else
             {
-                updateWidget(mRoot, mLayout);
+                updateWidget(mRoot, mLayout, 0);
             }
             mLayer = setLayer(mRoot, mLayout);
             updateAttachment();

From 9042f47f0ac091487a0beecec6570ba956ab027d Mon Sep 17 00:00:00 2001
From: uramer <antonuramer@gmail.com>
Date: Thu, 19 May 2022 16:10:49 +0200
Subject: [PATCH 2/2] Use an anonymous namespace in element.cpp

---
 components/lua_ui/element.cpp | 287 +++++++++++++++++-----------------
 1 file changed, 145 insertions(+), 142 deletions(-)

diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp
index 1356409340..bd9f84fec2 100644
--- a/components/lua_ui/element.cpp
+++ b/components/lua_ui/element.cpp
@@ -8,162 +8,165 @@
 
 namespace LuaUi
 {
-    namespace LayoutKeys
+    namespace
     {
-        constexpr std::string_view type = "type";
-        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";
-
-    const uint64_t maxDepth = 250;
-
-    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)
+        namespace LayoutKeys
         {
-            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;
+            constexpr std::string_view type = "type";
+            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";
         }
-        return type;
-    }
 
-    void destroyWidget(LuaUi::WidgetExtension* ext)
-    {
-        ext->deinitialize();
-        MyGUI::Gui::getInstancePtr()->destroyWidget(ext->widget());
-    }
+        const std::string defaultWidgetType = "LuaWidget";
 
-    WidgetExtension* createWidget(const sol::table& layout, uint64_t depth);
-    void updateWidget(WidgetExtension* ext, const sol::table& layout, uint64_t depth);
+        constexpr uint64_t maxDepth = 250;
 
-    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)
+        std::string widgetType(const sol::table& layout)
         {
-            for (WidgetExtension* w : children)
-                destroyWidget(w);
+            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);
+                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;
         }
-        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++)
+
+        void setTemplate(WidgetExtension* ext, const sol::object& templateLayout, uint64_t depth)
         {
-            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;
+            ++depth;
+            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, depth));
         }
-        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;
-    }
 
-    void setTemplate(WidgetExtension* ext, const sol::object& templateLayout, uint64_t depth)
-    {
-        ++depth;
-        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, depth));
-    }
-
-    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)
+        void setEventCallbacks(LuaUi::WidgetExtension* ext, const sol::object& eventsObj)
         {
-            if (name.is<std::string>() && callback.is<LuaUtil::Callback>())
-                ext->setCallback(name.as<std::string>(), callback.as<LuaUtil::Callback>());
-            else if (!name.is<std::string>())
-                Log(Debug::Warning) << "UI event key must be a string";
-            else if (!callback.is<LuaUtil::Callback>())
-                Log(Debug::Warning) << "UI event handler for key \"" << name.as<std::string>()
-                                    << "\" must be an openmw.async.callback";
-        });
-    }
-
-    WidgetExtension* createWidget(const sol::table& layout, uint64_t depth)
-    {
-        static auto widgetTypeMap = widgetTypeToName();
-        std::string type = widgetType(layout);
-        if (widgetTypeMap.find(type) == widgetTypeMap.end())
-            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(
-            type, "",
-            MyGUI::IntCoord(), MyGUI::Align::Default,
-            std::string(), name);
-
-        WidgetExtension* ext = dynamic_cast<WidgetExtension*>(widget);
-        if (!ext)
-            throw std::runtime_error("Invalid widget!");
-        ext->initialize(layout.lua_state(), widget);
-
-        updateWidget(ext, layout, depth);
-        return ext;
-    }
-
-    void updateWidget(WidgetExtension* ext, const sol::table& layout, uint64_t depth)
-    {
-        if (depth >= maxDepth)
-            throw std::runtime_error("Maximum layout depth exceeded, probably caused by a circular reference");
-        ext->reset();
-        ext->setLayout(layout);
-        ext->setExternal(layout.get<sol::object>(LayoutKeys::external));
-        setTemplate(ext, layout.get<sol::object>(LayoutKeys::templateLayout), depth);
-        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), depth));
-        ext->updateCoord();
-    }
-
-    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());
+            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->setCallback(name.as<std::string>(), callback.as<LuaUtil::Callback>());
+                else if (!name.is<std::string>())
+                    Log(Debug::Warning) << "UI event key must be a string";
+                else if (!callback.is<LuaUtil::Callback>())
+                    Log(Debug::Warning) << "UI event handler for key \"" << name.as<std::string>()
+                                        << "\" must be an openmw.async.callback";
+            });
+        }
+
+        WidgetExtension* createWidget(const sol::table& layout, uint64_t depth)
+        {
+            static auto widgetTypeMap = widgetTypeToName();
+            std::string type = widgetType(layout);
+            if (widgetTypeMap.find(type) == widgetTypeMap.end())
+                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(
+                type, "",
+                MyGUI::IntCoord(), MyGUI::Align::Default,
+                std::string(), name);
+
+            WidgetExtension* ext = dynamic_cast<WidgetExtension*>(widget);
+            if (!ext)
+                throw std::runtime_error("Invalid widget!");
+            ext->initialize(layout.lua_state(), widget);
+
+            updateWidget(ext, layout, depth);
+            return ext;
+        }
+
+        void updateWidget(WidgetExtension* ext, const sol::table& layout, uint64_t depth)
+        {
+            if (depth >= maxDepth)
+                throw std::runtime_error("Maximum layout depth exceeded, probably caused by a circular reference");
+            ext->reset();
+            ext->setLayout(layout);
+            ext->setExternal(layout.get<sol::object>(LayoutKeys::external));
+            setTemplate(ext, layout.get<sol::object>(LayoutKeys::templateLayout), depth);
+            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), depth));
+            ext->updateCoord();
+        }
+
+        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;