lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]

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:  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
    setfenv(3,env) -- overrides environment set by module()

So now mymod.lua would look like this:

module(...,require 'closed')

function first (x)
    return second(x)

function second (x)
    return math.sin(math.sqrt(x))

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

And then second() can be written more neatly (and efficiently) as

function second (x)
    return sin(sqrt(x))

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.