ThePhD / sol2

Sol3 (sol2 v3.0) - a C++ <-> Lua API wrapper with advanced features and top notch performance - is here, and it's great! Documentation:
http://sol2.rtfd.io/
MIT License
4.16k stars 504 forks source link

memory smashing with container wrapping and coroutine #1400

Open otristan opened 2 years ago

otristan commented 2 years ago

Hi,

The following code leads to some memory smashing from time to time. It seems related to a custom wrapping of const std::vector

Using lua 5.2 on Xcode Version 12.2 (12B45b) Probably similar to https://github.com/ThePhD/sol2/issues/1315

#define SOL_ALL_SAFETIES_ON 1
#define SOL_NO_CHECK_NUMBER_PRECISION 1
#define SOL_EXCEPTIONS_SAFE_PROPAGATION 1
#define SOL_USING_CXX_LUA 1

#include "sol/sol.hpp"
#include <vector>
#include <memory>

struct A
{
public:
  A(int value_) : value(value_) { }
  int value;

  int getValue() { return value; }
  std::vector<std::shared_ptr<A>> children;
};

struct ArrayProxy
{
  using value_type = std::weak_ptr<A>;

  struct iterator
  {
    using iterator_category = std::random_access_iterator_tag;
    using difference_type   = std::ptrdiff_t;
    using value_type        = std::weak_ptr<A>;
    using pointer           = std::weak_ptr<A>*;  // or also value_type*
    using reference         = std::weak_ptr<A>&;  // or also value_type&

    const ArrayProxy &a;
    size_t index;

    iterator(const ArrayProxy &a_, size_t index_) : a(a_), index(index_) {}

    value_type operator*() const
    {
      size_t size = a.mpParent.children.size();
      if(index >= 0 && index < size)
      {
        return a.mpParent.children[index];
      }
      return std::weak_ptr<A>();
    }

    // Operators : arithmetic
    inline iterator& operator++() {++index; return *this;}
    inline iterator& operator--() {--index; return *this;}
    inline iterator& operator+=(const size_t& rhs) {index += rhs; return *this;}
    inline iterator& operator-=(const size_t& rhs) {index -= rhs; return *this;}

    // Operators : comparison
    inline bool operator==(const iterator& rhs) {return index == rhs.index;}
    inline bool operator!=(const iterator& rhs) {return index != rhs.index;}
    inline bool operator>(const iterator& rhs) {return index > rhs.index;}
    inline bool operator<(const iterator& rhs) {return index < rhs.index;}
    inline bool operator>=(const iterator& rhs) {return index >= rhs.index;}
    inline bool operator<=(const iterator& rhs) {return index <= rhs.index;}
  };

  ArrayProxy(const A &parent)
  : mpParent(parent)
  {
  }

  ~ArrayProxy()
  {
  }

  auto begin() const
  {
    return iterator(*this, 0);
  }
  auto end() const
  {
    return iterator(*this, mpParent.children.size());
  }
  size_t size() const
  {
    return mpParent.children.size();
  }

  const A &mpParent;
};

namespace sol {
  template<typename T>
  struct unique_usertype_traits<std::weak_ptr<T>> {
    static T* get(lua_State*, const std::weak_ptr<T>& ptr) noexcept {
      return ptr.lock().get();
    }

    static bool is_null(lua_State*, const std::weak_ptr<T>& ptr) noexcept {
      return ptr.expired();
    }
  };

  template <>
  struct is_container<ArrayProxy> : std::true_type { };
}

static ArrayProxy getChildren(const A &a)
{
  return ArrayProxy(a);
}

int main(void)
{
  A a(0);
  for (int i = 0; i < 100; i++)
  {
    auto child = std::make_shared<A>(i*100);
    for (int j = 0; j < 100; j++)
    {
      auto child2 = std::make_shared<A>(i*100+j);
      child->children.push_back(child2);
    }
    a.children.push_back(child);
  }
  sol::state lua;
  lua.open_libraries(sol::lib::base,
                     sol::lib::package,
                     sol::lib::table,
                     sol::lib::debug,
                     sol::lib::string,
                     sol::lib::math,
                     sol::lib::bit32,
                     sol::lib::coroutine
                     );

  lua.new_usertype<A>("A",
                      "value", sol::property(&A::getValue),
                      "children",  sol::property(&getChildren));

  lua.globals()["A"] = &a;

  const auto& code = R"(
      print(A.value)
  for i=1, 10 do
  co = coroutine.create( function()
  for _, child in pairs(A.children) do
    for _, child2 in pairs(child.children) do
      print(child2.value)
    end
  end
  end)
  coroutine.resume(co)
  end

    )";

  // call lua code directly
  lua.script(code);

  return 0;
}

Don't hesitate if you need further detail.

Thanks !