[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: "Require" in a sandboxed environment
- From: Nick Gammon <nick@...>
- Date: Tue, 14 Aug 2007 10:26:02 +1000
On 14/08/2007, at 2:07 AM, Tom Miles wrote:
I need to read up on environments as I don't really understand what
setfenv does.
The first thing to understand about environments is that Lua doesn't
really have a concept of global variables. With the exception of
variables declared "local" and upvalues, variables are looked up in
the current environment. Effectively, the current environment is the
"root" of the variables tree. As an example:
a = 2
Is really like doing this:
_G.a = 2
Where _G is a convenience variable created by Lua to refer to "the
current environment". However you could delete _G, and the statement
"a = 2" would still work:
_G = nil
a = 2
What Lua is really doing when you say "a = 2" is:
* fetch the current environment table
* assign 2 to be the value of key "a" in that table
You can see that _G and the current environment are the same thing by
doing this:
--------
> print (_G)
table: 0x300350
> print (getfenv ())
table: 0x300350
>
--------
Now the powerful thing about environments is you can make "sandboxes"
by changing the environment. This small example shows how you can
eliminate all existing "global" variables:
--------
function f ()
print ("hello")
end -- f
f () --> prints "hello"
t = {} -- some new environment
setfenv (f, t)
f () --> error: attempt to call global 'print' (a nil value)
--------
After the setfenv the function f, which worked when called the first
time, no longer can find the function "print".
I could add the single "global" variable "print" to my new
environment like this:
--------
function f ()
print ("hello")
end -- f
f () --> prints "hello"
t = { print = print } -- some new environment
setfenv (f, t)
f () --> prints "hello"
--------
Now the environment "t" has the function "print" in it, with the
value from the original environment.
An interesting extension to this idea is to make *all* of the
original global variables available to the new environment, by using
a metatable:
--------
function f ()
print ("hello")
end -- f
f () --> prints "hello"
t = { } -- some new environment
setmetatable (t, { __index = getfenv () })
setfenv (f, t)
f () --> prints "hello"
--------
This example adds a metatable to the new environment, which looks up
variables which we are trying to read from, and don't exist, in the
original environment. This is effectively what package.seeall does -
adds read-access to the global environment, for your module.
Does your approach work with sharing data across
environments?
The "module" function call creates an environment, so the trick to
sharing environments is to work out what you want to do exactly. The
purpose of doing it (the module call) in the first place is to have
local data (in the module's environment) that does not corrupt or
affect similarly named data in other modules.
I think the problem with modules loading submodules is that the
sharing is a level removed from the real "global" environment. A few
debugging prints of various tables should show which module uses
which environment.
I think you will find that if you load "utilities" early on (in your
main state) then attempts to load it in sub-modules will find an
already-existing utilities entry in the main environment, and
therefore not create one in the sub-module environment. This may well
solve your variable-sharing problem.
- Nick