Nanfeng

Notes on software development, code, and curious ideas

Understanding Environments in Lua

Lua presents global variables as entries in an ordinary table. The global environment is normally available through _G, so these expressions are equivalent:

1
2
answer = 42
_G["answer"] = 42

The table representation makes dynamically named globals straightforward, although an explicit application table is usually clearer than adding more global state.

Detecting undeclared globals

A development-time metatable can catch accidental reads and writes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
local declared = {}

setmetatable(_G, {
__newindex = function(t, name, value)
if not declared[name] then
local info = debug.getinfo(2, "S")
if info.what ~= "main" and info.what ~= "C" then
error("assign to undeclared global " .. name, 2)
end
declared[name] = true
end
rawset(t, name, value)
end,
__index = function(_, name)
if not declared[name] then
error("read of undeclared global " .. name, 2)
end
end
})

This is a debugging aid rather than a security boundary, and it relies on debug information.

_ENV in Lua 5.2 and later

Global-looking names are compiled as fields of a special lexical variable named _ENV:

1
2
3
local print = print
local _ENV = { x = 10, print = print }
print(x)

Changing _ENV affects only global-name resolution in its lexical scope; local variables continue to work normally. A private environment can inherit selected host values through __index, but an allowlist is safer for untrusted code than inheriting all of _G.

Modules and explicit environments

Modules are best represented by a local table returned from the file:

1
2
3
4
5
6
7
local M = {}

function M.double(x)
return x * 2
end

return M

The caller uses local mathx = require("mathx"), avoiding global namespace pollution.

load can compile code with a chosen environment:

1
2
3
4
local env = { print = print, value = 21 }
local chunk, err = load("print(value * 2)", "example", "t", env)
if not chunk then error(err) end
chunk()

An environment alone is not a complete sandbox. Restrict libraries and resource use as well, and never expose capabilities such as unrestricted io, os, package, load, or debug to untrusted scripts unless deliberately controlled.

+