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.18k stars 515 forks source link

confusing behaviour of environments for lua > 5.2 #1604

Closed lceWolf closed 4 months ago

lceWolf commented 4 months ago

This might not be an issue with sol2, but how lua itself works with environments, but here is the deal:

When loading a lua file via sol::script_file which contains e.g. two functions, setting the environment of one functions also changes the environment of the other. Same goes for when a call to sol::script defines two functions in one go. Is this behaviour expected? (might be related to https://github.com/ThePhD/sol2/issues/1079#issuecomment-752311165)

The example below covers two scenarios (1 OK and 1 NOK): First I create two functions with two seperate calls to sol::script. Setting the environment of one of these functions does not modify the environment of the other function - OK. The second time I create the two functions using only one call to sol::script. Now, modifying the environment of one of these functions will change the environment of the other function too - NOK.

Personally, I expected the second case to behave just like the first one did.

I am completely off into the blue, but it looks like changing the environment for one functions, changes it for every function defined in that chunk 🤔

#include <sol/sol.hpp>

int main()
{
    sol::state lua;
    lua.open_libraries(sol::lib::base);

    sol::environment env(lua, sol::create, lua.globals());

    lua.script("function foo() print(x) end");
    lua.script("function other() print(x) end");

    sol::function foo = lua["foo"];
    sol::function other = lua["other"];

    lua.script(R"(
               function foo2()
                    print(x)
               end
               function other2()
                print(x)
               end)");

    sol::function foo2 = lua["foo2"];
    sol::function other2 = lua["other2"];

    lua["x"] = 99;
    env["x"] = 42;

    env.set_on(other);
    foo(); // -> 99 Okay
    other(); // -> 42 Okay

    std::cout << "###" << std::endl;

    env.set_on(other2);
    foo2(); // -> 42 NOT okay
    other2(); // -> 42 okay

    return 0;
}

A potential workaround for the issue: • dump the bytecode of the to-be-isolated function using sol::function::dump • load it again using sol::script(dump.as_string_view()) • set the environment on the newly-loaded function

lceWolf commented 4 months ago

http://lua-users.org/wiki/EnvironmentsTutorial confirms that the _ENV table is changed for everything in the current chunk

When loading a chunk, the top-level function gets a new _ENV upvalue, and any nested functions inside it can see it. You can pretend that when loading works something like this:

local _ENV = _G return function (...) -- this function is what's returned from load -- code you passed to load goes here, with all global variable names replaced with _ENV lookups -- so, for example "a = b" becomes "_ENV.a = _ENV.b" if neither a nor b were declared local end Now you can see that _ENV is an ordinary local variable, how all the functions have access to the _ENV, and why if one function changes _ENV all other functions in the loaded chunks will see the change. That's why if you want a function to only change its own environment, you need to make a new _ENV local that shadows the original one.