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.12k stars 500 forks source link

Memory leak when returning usertype A from a usertype B member function when usertype B does not have a registered member variable. #1435

Open KaiNorberg opened 1 year ago

KaiNorberg commented 1 year ago

I am currently using C++ in Visual Studio 2022. I noticed a weird memory leak in a program I was making and after some debugging, I found it. It is possible that I'm just doing something wrong but from what I can find, It seems that as the title says if you return a usertype A from a function that is a member of usertype B and usertype B does not have any member variables it will not delete the new instance of usertype A properly. Here is some example code to demonstrate the issue:

struct A
{
    int X;
    int Y;
};

struct B
{
    int Z;

    A Get()
    {
     return A();
    }
};

struct C
{
    A Get()
    {
     return A();
    }
};

int Main()
{
    std::string BScript =
    "local T = B:new()\n"
    "for i = 0, 1000 ^ 2.5 do\n"
    "    local Temp = T:Get()\n"
    "end\n";

    std::string CScript =
     "local T = C:new()\n"
     "for i = 0, 1000 ^ 2.5 do\n"
     "    local Temp = T:Get()\n"
     "end\n";

    {    
     std::cout << "This will not create a memory leak!" << std::endl;

     sol::state State;

     State.new_usertype<A>("A", sol::constructors<void()>(),
         "X", &A::X,
         "Y", &A::Y);

     State.new_usertype<B>("B", sol::constructors<void()>(),
         "Z", &B::Z,
         "Get", &B::Get);

     State.script(BScript);

     std::cout << "Done! Waiting..." << std::endl;

     _sleep(1000);
    }

    {
     std::cout << "This will create a memory leak!" << std::endl;

     sol::state State;

     State.new_usertype<A>("A", sol::constructors<void()>(),
         "X", &A::X,
         "Y", &A::Y);

     State.new_usertype<B>("B", sol::constructors<void()>(),
         "Get", &B::Get);

     State.script(BScript);      

     std::cout << "Done! Waiting..." << std::endl;

     _sleep(1000);
    }

    {
     std::cout << "This will also create a memory leak!" << std::endl;

     sol::state State;

     State.new_usertype<A>("A", sol::constructors<void()>(),
         "X", &A::X,
         "Y", &A::Y);

     State.new_usertype<C>("C", sol::constructors<void()>(),
         "Get", &C::Get);

     State.script(CScript);

     std::cout << "Done! Waiting..." << std::endl;

     _sleep(1000);
    }

    std::cout << "Program complete" << std::endl;

    while (true);

    return EXIT_SUCCESS;
}

The code contains three structs A, B and C, the functions B::Get and C::Get return a new instance of A.

The first block of code will register the structs A and B and the struct B will be registered with both a function and a variable. It then runs BScript which just calls the B::Get function a lot of times NOT CREATING a memory leak.

The second block of code will register the structs A and B and the struct B will register only its function. It then runs BScript which just calls the B::Get function a lot of times CREATING a memory leak. Presumably, because it doesn't have a registered variable?

The third block of code will register the structs A and C and the struct C will only contain one function which is registered. The purpose of this is to show that what's causing the memory leak isn't forgetting to register all member variables of a struct. It then runs CScript which just calls the C::Get function a lot of times CREATING a memory leak.

So, is this intended behaviour, am I doing something wrong or is this a bug?