Environments Tutorial |
|
The default global table is stored inside itself under the key "_G"
, this is useful if you want to get an actual reference to it.
The way environments work in Lua 5.2 is very different from 5.1. Both ways will be explained here.
A function's environment is stored in an upvalue, named _ENV
. As an example, here's a function that sets its environment to a custom one and uses variables from it:
print(_ENV == _G) -- prints true, since the default _ENV is set to the global table a = 1 local function f(t) local print = print -- since we will change the environment, standard functions will not be visible local _ENV = t -- change the environment. without the local, this would change the environment for the entire chunk print(getmetatable) -- prints nil, since global variables (including the standard functions) are not in the new env a = 2 -- create a new entry in t, doesn't touch the original "a" global b = 3 -- create a new entry in t end local t = {} f(t) print(a, b) --> 1 nil print(t.a, t.b) --> 2 3
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
In most cases, you don't need to use environments, unless you want to sandbox a loaded chunk, to give it convenient access to certain functions by making them look global, or to prevent it from seeing unsafe functions for security reasons. This is why 5.2's load function takes a parameter that lets you set the chunk's _ENV to a custom table, instead of _G.
local sandbox_env = { print = print, } local chunk = load("print('inside sandbox'); os.execute('echo unsafe')", "sandbox string", "bt", sandbox_env) chunk() -- prevents os.execute from being called, instead raises an error saying that os is nil
If you actually want to make a sandbox to run untrusted code, remember that it's easy to overlook a lot of things that can be exploited, and that you would need some kind of way to limit CPU usage and memory.
In Lua 5.1, environments are their own thing, not related to locals or upvalues. Instead, each function has an environment table associated with it, that can be manipulated with the getfenv
/setfenv
standard functions.
getfenv
and setfenv
both take a function or stack level (where 1 is the current function, 2 is the function that called the current function, etc.). setfenv
has a second parameter that takes the new env table, and getfenv
returns the functions's current env table.
The previous example rewritten for 5.1:
print(getfenv(1) == _G) -- prints true, since the default env is set to the global table a = 1 local function f(t) local print = print -- since we will change the environment, standard functions will not be visible setfenv(1, t) -- change the environment print(getmetatable) -- prints nil, since global variables (including the standard functions) are not in the new env a = 2 -- create a new entry in t, doesn't touch the original "a" global b = 3 -- create a new entry in t end local t = {} f(t) print(a, b) --> 1 nil print(t.a, t.b) --> 2 3
And the sandbox example rewritten for 5.1:
local sandbox_env = { print = print, } local chunk = loadstring("print('inside sandbox'); os.execute('echo unsafe')") setfenv(chunk, sandbox_env) chunk() -- prevents os.execute from being called, instead raises an error saying that os is nil
The 5.1 way is sometimes considered simpler and more versatile, but it also requires special treatment of environments (instead of using the existing local variable system). Also, the 5.2 way is designed with the idea that a function's environment is supposed to be private, instead of being accessible from everywhere without the debug library, so it can be considered safer.