[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Closed Modules: a Useful Hack
- From: steve donovan <steve.j.donovan@...>
- Date: Mon, 4 Jan 2010 08:03:55 +0200
Hi all,
The hack du jour is another way to resolve some problems with module
[1]. In particular, using module('mymod',package.seeall) means that
everything in the global environment is exposed to users of mymod,
just by indexing: mymod.io. If you are thinking about secure
sandboxing you ignore this problem at your peril. On the other hand,
package.seeall is so .. convenient.
The key to resolving this is a quote from [1] "the module table and
the module environment table need not be the same". This function
creates a separate module enviroment (overriding the default set by
module()), and ensures that anything added to it will also be added to
the module table.
-- closed.lua
return function(mod)
local env,meta = {_M=mod._M,_NAME=mod._NAME,_PACKAGE=mod._PACKAGE},{}
env._SELF = env
meta.__index = _G -- unresolved references will be looked up as globals
function meta.__newindex(t,key,val)
rawset(mod,key,val) -- add to module table
rawset(env,key,val) -- add to module environment
end
setmetatable(env,meta)
setfenv(3,env) -- overrides environment set by module()
end
So now mymod.lua would look like this:
module(...,require 'closed')
function first (x)
return second(x)
end
function second (x)
return math.sin(math.sqrt(x))
end
As you can see, globals like math are available (thanks to env.__index
= _G) but only in the _module environment_. The actual module table
(available as the global 'mymod') cannot reference _G, so the module
is safely closed.
What is particularly cool is that we can now add any values to the
module environment, without contaminating the module table. mymod can
bring in all entries from math:
for k,v in pairs(math) do
if not k:find '^_' then -- ignore module vars
rawset(_SELF,k,v) -- don't say _SELF[k] = v! Will trigger __newindex
end
end
And then second() can be written more neatly (and efficiently) as
function second (x)
return sin(sqrt(x))
end
Keeping the module environment and module table separate, but in sync,
means that you can play these convenience tricks without affecting the
public interface of the module. (This was in fact the original
motivation for this approach)
With a little work, such import functions can be encapsulated as a
library function and made more intelligent. In particular, it could
warn you about namespace collisions explicitly at runtime
steve d.
[1] http://lua-users.org/wiki/LuaModuleFunctionCritiqued