From 42a7741a66850a328e2606243cc365c07a006f7d Mon Sep 17 00:00:00 2001
From: NAUD Maxence <maxence.naud@cea.fr>
Date: Mon, 28 Aug 2023 16:15:17 +0000
Subject: [PATCH] [Fix] Memory leak due to containers serialization

- [Add] minimal std::any implementation compatible with C++14
GenericOperator parameters are accessed by reference
---
 include/aidge/operator/GenericOperator.hpp   |  11 +-
 include/aidge/utils/Any.hpp                  | 134 +++++++++++++++++++
 include/aidge/utils/CParameter.hpp           |  59 ++++----
 unit_tests/operator/Test_GenericOperator.cpp |   6 +-
 4 files changed, 172 insertions(+), 38 deletions(-)
 create mode 100644 include/aidge/utils/Any.hpp

diff --git a/include/aidge/operator/GenericOperator.hpp b/include/aidge/operator/GenericOperator.hpp
index 7efbe62a4..00d07d951 100644
--- a/include/aidge/operator/GenericOperator.hpp
+++ b/include/aidge/operator/GenericOperator.hpp
@@ -62,7 +62,12 @@ class GenericOperator_Op
      * @return template<class T> The parameter.
      */
     template <class T>
-    T getParameter(std::string const &key) const {
+    const T& getParameter(std::string const &key) const {
+        return mParams.Get<const T>(key);
+    }
+
+    template <class T>
+    T& getParameter(std::string const &key) {
         return mParams.Get<T>(key);
     }
 
@@ -75,8 +80,8 @@ class GenericOperator_Op
     /// internal buffer in a new location (previous value is still in memory at
     /// its previous location)
     template <class T>
-    void addParameter(std::string const &key, T const &value) {
-        mParams.Add<T>(key, value);
+    void addParameter(std::string const &key, T&& value) {
+        mParams.Add<T>(key, std::forward<T>(value));
     }
 
 
diff --git a/include/aidge/utils/Any.hpp b/include/aidge/utils/Any.hpp
new file mode 100644
index 000000000..bbf83eecf
--- /dev/null
+++ b/include/aidge/utils/Any.hpp
@@ -0,0 +1,134 @@
+
+/********************************************************************************
+ * Copyright (c) 2023 CEA-List
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+#ifndef AIDGE_ANY_H_
+#define AIDGE_ANY_H_
+
+
+#include <typeinfo>    // typeid
+#include <type_traits> // enable_if_t, decay_t, is_same, is_copy_constructible, remove_cv, remove_reference
+#include <assert.h>
+#include <new>
+
+class _any {
+private:
+    enum _Op { _Op_access, _Op_get_type_info, _Op_clone, _Op_destroy };
+
+    union _Arg {
+        const std::type_info* _M_typeinfo;
+        _any* _M_any;
+    };
+
+    void* _M_data;
+    void (*_M_manager)(_Op, const _any*, _Arg*);
+
+public:
+    template <typename Tp>
+    struct Manager {
+        static void manage(_Op which, const _any* __any, _Arg* __arg) {
+            auto ptr = static_cast<const Tp*>(__any->_M_data);
+            switch (which)
+            {
+            case _Op_get_type_info:
+                __arg->_M_typeinfo = &typeid(Tp);
+                break;
+            case _Op_clone:
+                __arg->_M_any->_M_data = new Tp(*ptr);
+                __arg->_M_any->_M_manager = __any->_M_manager;
+                break;
+            case _Op_destroy:
+                delete ptr;
+                break;
+            }
+
+        }
+        static Tp* access(const _any* __any) {
+            return static_cast<Tp*>(__any->_M_data);
+        }
+
+        // template <typename Up>
+        // static void create(void* data, Up&& value) {
+        //     data = new Tp(std::forward<Up>(value));
+        // }
+    };
+
+private:
+    template<typename _Tp, typename _VTp = std::decay_t<_Tp>>
+    using _Decay_if_not_any = std::enable_if_t<!std::is_same<_VTp, _any>::value, _VTp>;
+
+public:
+    _any() noexcept : _M_manager(nullptr) { }
+
+    _any(const _any& __other)
+    {
+        if (!__other._M_manager)
+            _M_manager = nullptr;
+        else
+        {
+            _Arg __arg;
+            __arg._M_any = this;
+            __other._M_manager(_Op_clone, &__other, &__arg);
+        }
+    }
+
+    _any(_any&& __other)
+    {
+        if (!__other._M_manager)
+            _M_manager = nullptr;
+        else
+        {
+            _M_data = __other._M_data;
+            _M_manager = __other._M_manager;
+            const_cast<_any*>(&__other)->_M_manager = nullptr;
+        }
+    }
+
+    template<typename T, typename VT = _Decay_if_not_any<T>, std::enable_if_t<std::is_copy_constructible<VT>::value, bool> = true>
+    explicit _any(T&& value)
+        : _M_manager(&Manager<VT>::manage),
+            _M_data(new VT{std::forward<T>(value)})
+    {}
+
+
+    ~_any()
+    {
+        if(_M_manager) {
+            _M_manager(_Op_destroy, this, nullptr);
+            _M_manager = nullptr;
+        }
+    }
+
+    const std::type_info& type() const
+    {
+        if (!_M_manager)
+            return typeid(void);
+        _Arg __arg;
+        _M_manager(_Op_get_type_info, this, &__arg);
+        return *__arg._M_typeinfo;
+    }
+};
+
+template<typename _ValueType>
+inline _ValueType any_cast(const _any& __any)
+{
+    using _Up =  std::remove_cv_t<std::remove_reference_t<_ValueType>>;
+    assert((std::__or_<std::is_reference<_ValueType>, std::is_copy_constructible<_ValueType>>::value && "Template argument must be a reference or CopyConstructible type"));
+    assert((std::is_constructible<_ValueType, const _Up&>::value && "Template argument must be constructible from a const value."));
+    assert(std::is_object<_Up>::value);
+    assert(__any.type() == typeid(_Up));
+    auto __p = static_cast<_Up*>(__any._M_data);
+    if (__p)
+        return static_cast<_ValueType>(*__p);
+    throw std::bad_cast();
+}
+
+#endif /* AIDGE_ANY_H_ */
\ No newline at end of file
diff --git a/include/aidge/utils/CParameter.hpp b/include/aidge/utils/CParameter.hpp
index 0f4c74ab8..c13d9ca74 100644
--- a/include/aidge/utils/CParameter.hpp
+++ b/include/aidge/utils/CParameter.hpp
@@ -12,23 +12,30 @@
 #ifndef AIDGE_CPARAMETER_H_
 #define AIDGE_CPARAMETER_H_
 
-#include <assert.h>
 #include <map>
 #include <vector>
+#include "aidge/utils/Any.hpp"
+
 
 namespace Aidge {
 
 ///\todo store also a fix-sized code that indicates the type
 ///\todo managing complex types or excluding non-trivial, non-aggregate types
-class CParameter
-{
+class CParameter {
 private:
-    template <typename T>
-    struct is_vector : std::false_type {};
-
-    template <typename T, typename Alloc>
-    struct is_vector<std::vector<T, Alloc>> : std::true_type {};
-
+    template<typename _ValueType>
+    inline _ValueType& any_cast_ref(const _any& __any)
+    {
+        using _Up =  std::remove_cv_t<std::remove_reference_t<_ValueType>>;
+        assert((std::__or_<std::is_reference<_ValueType>, std::is_copy_constructible<_ValueType>>::value && "Template argument must be a reference or CopyConstructible type"));
+        assert((std::is_constructible<_ValueType, const _Up&>::value && "Template argument must be constructible from a const value."));
+        assert(std::is_object<_Up>::value);
+        assert(__any.type() == typeid(_Up));
+        if (_any::Manager<_Up>::access(&__any)) {
+            return *static_cast<_ValueType*>(_any::Manager<_Up>::access(&__any));
+        }
+        throw std::bad_cast();
+    }
 public:
     // not copyable, not movable
     CParameter(CParameter const &) = delete;
@@ -48,15 +55,16 @@ public:
      *  param buffer that will get invalid after the CParam death.
      * \note at() throws if the parameter does not exist, using find to test for parameter existance
      */
-    template<class T> T Get(std::string const i_ParamName) const
+    template<class T> T& Get(const std::string i_ParamName)
     {
-        assert(m_Params.find(i_ParamName) != m_Params.end());
-        assert(m_Types.find(i_ParamName) != m_Types.end());
-        assert(m_Params.at(i_ParamName) <= m_OffSet);
-        assert(typeid(T).name() == m_Types.at(i_ParamName));
-        return *reinterpret_cast<T *>(m_BeginBuffer + m_Params.at(i_ParamName));
+        return any_cast_ref<T>(m_Buffer[m_Params.at(i_ParamName)]);
     }
 
+    // template<class T> const T& Get(const std::string i_ParamName) const
+    // {
+    //     return any_cast<T>(m_Buffer[m_Params.at(i_ParamName)]);
+    // }
+
     ///\brief Add a parameter value, identified by its name
     ///\tparam T expected parameter type
     ///\param i_ParamName Parameter name
@@ -64,16 +72,10 @@ public:
     ///\todo Pass i_Value by ref if large or not trivial
     ///\bug If parameter already exists, its value is changed but written in the
     /// internal buffer in a new location (previous value is still in memory at its previous location)
-    template<class T> void Add(std::string const &i_ParamName, T const &i_Value)
+    template<class T> void Add(const std::string &i_ParamName, T&& i_Value)
     {
-        m_Buffer.resize(m_Buffer.size() + (sizeof(T) / sizeof(uint8_t)));
-        m_BeginBuffer = m_Buffer.data(); // Update buffer ptr in case of memory reordering
-        *reinterpret_cast<T *>(m_BeginBuffer + m_OffSet)
-            = i_Value; // Black-magic used to add anytype into the vector
-        m_Params[i_ParamName] = m_OffSet; // Copy pointer offset
-        m_OffSet += sizeof(T); // Increment offset
-
-        m_Types[i_ParamName] = typeid(i_Value).name();
+        m_Params[i_ParamName] = m_Buffer.size(); // Copy pointer offset
+        m_Buffer.push_back(_any(std::forward<T>(i_Value)));
     }
 
 
@@ -100,14 +102,7 @@ private:
     std::map<std::string, std::string> m_Types;
 
     ///\brief All parameters values concatenated in raw binary form.
-    std::vector<uint8_t> m_Buffer = {};
-
-    ///\brief Starting address of the buffer
-    uint8_t *m_BeginBuffer = m_Buffer.data();
-
-    ///\brief Offset, in number of uint8_t, of the next parameter to write
-    std::size_t m_OffSet = 0;
-
+    std::vector<_any> m_Buffer = {};
 };
 
 }
diff --git a/unit_tests/operator/Test_GenericOperator.cpp b/unit_tests/operator/Test_GenericOperator.cpp
index 886326214..220839989 100644
--- a/unit_tests/operator/Test_GenericOperator.cpp
+++ b/unit_tests/operator/Test_GenericOperator.cpp
@@ -20,10 +20,10 @@ using namespace Aidge;
 TEST_CASE("[core/operators] GenericOp(add & get parameters)", "[Operator]") {
     SECTION("INT") {
         GenericOperator_Op Testop("TestOp", 1, 1, 1);
-        int value = 5;
         const char* key = "intParam";
-        Testop.addParameter(key, value);
-        REQUIRE(Testop.getParameter<int>(key) == value);
+        Testop.addParameter(key, int(5));
+        int registeredVal = Testop.getParameter<int>(key);
+        REQUIRE(registeredVal == 5);
     }
     SECTION("LONG") {
         GenericOperator_Op Testop("TestOp", 1, 1, 1);
-- 
GitLab