4
votes

I'm working on improving the way we handle Lua scripting for robot players in Bitfighter. Currently, each robot gets its own L instance, and we're trying to get them to all share one by swapping out environment tables. Note that bots may be completely different scripts.

I realize that this method is deprecated in Lua 5.2, but we are currently using lua-vec, which still uses Lua 5.1. The game is written in C++.

So...

First we create an environment, and call it :

// Create a table with room for 0 array and 1 non-array elements
lua_createtable(L, 0, 1);                 // -- tab

// Set the globals table to handle any requests that the 
// script's environment can't
lua_pushstring(L, "__index");             // -- tab, "__index"
lua_pushvalue(L, LUA_GLOBALSINDEX);       // -- tab, "__index", _G

// Set table["__index"] = _G, pops top two items from stack
lua_settable(L, -3);                      // -- tab

// Store the new table in the retistry for future use
lua_setfield(L, LUA_REGISTRYINDEX, name); // -- <<empty stack>>

Later, we load some Lua code, and recall the environment table:

luaL_loadfile(L, "luascripts.lua");

lua_getfield(L, LUA_REGISTRYINDEX, name); // -- function, table 
lua_setfenv(L, -2);                       // -- function

Then run the loaded code:

lua_pcall(L, 0, 0, 0);

When the loaded Lua tries to use a basic function, such as print, it fails with the error:

attempt to call global 'print' (a nil value) 

But, the script can do the following:

__index["print"](12)

So... why can't we access print directly? What are we missing? Or is there a fundamentally better way to run multiple scripts in the same Lua instance?

1

1 Answers

3
votes

Your code is close to correct, but contains several issues - You are attempting to do something that won't work, and your attempt did the wrong thing in the wrong way..

You are setting the function environment of the function to a table which looks like this:

{__index = _G}

Naturally, when you attempt to access print, it is not found in this table.

From your comments, I infer that actually wanted to set the __index field of the metatable of the environment table. That is, you wanted to make the environment tables be like t in the example below:

t = {}
setmetatable(t, {__index = _G})

(The C++ translation of this is fairly straightforward).

Don't do this. It will solve your immediate problem, but it will not provide sufficient sandboxing. Consider for example a script like this:

table.sort = 10

"table" will be found by in _G by the metatable event handler. sort is just an element of the table table, and so can be replaced with impunity. Now, other scripts will be unable to sort tables with table.sort.


One way to perform this kind of separation is to mediate access to all the global values through some sort of (possibly recursive) userdata with hand-written handlers for the relevant metatable events. (This way probably has the most potential for performance and control, but could be difficult to implement).

Another way is to create an environment table for for each script, and to copy the safe/sandboxed elements from the global table into this environment table (so that each script has a totally separate version of all the mutable elements of the global table).

I'm sorry I don't have time to properly explain my suggested solutions to your problem. I hope that I have given you somewhere to start. Feel free to come back and edit this to include the solution that you ultimately use.