这一篇结合了好多篇东西。。说明下关于 Lua 中的环境和沙盒相关的内容
环境(Environments)
Lua 中的全局变量都是存在表中的,这个表可以通过 _G 来获取
for k,v in pairs(_G) do
print(k,v)
end
打印一下表中的值(太多了,省略一些)
string table: 00000000050174C0
xpcall function: 0000000005019EE0
package table: 0000000021CF07B0
tostring function: 000000000501A060
print function: 000000000501A120
.
.
.
dofile function: 0000000005017210
_VERSION Lua 5.1
setfenv function: 0000000005019F70
error function: 0000000005017060
loadfile function: 00000000050172D0
在 Lua 5.1 中,可以通过 getfenv 和 setfenv 函数来操作这个环境表
getfenv 函数有一个参数,可以是一个函数,代表取这个函数中的环境,也可以是数字,代表堆栈级别,0代表取全局环境,1代表取当前函数的环境,2代表取调用当前函数的函数的环境,3代表更上一级,依此类推
setfenv 函数的第一个参数和 getfenv 一样,第二个参数代表了新的环境表
print(getfenv(1) == _G) -- true
a = 1
local function f(t)
local print = print -- 改变环境表后就没有 print 函数了,所以这里提前定义一个内部的变量
setfenv(1, t) -- 修改当前函数内的环境表为 t
print(getmetatable) -- false,因为当前的环境中没有 getmetatable 函数
a = 2 -- 定义当前环境中的全局变量,此变量不会影响到外部定义的 a
b = 3 -- 同上
end
local t = {}
f(t)
print(a, b) -- 1 nil
print(t.a, t.b) -- 2 3
在 Lua 5.2 中,操作环境则使用 _ENV 这个变量,直接附上 wiki 上的例子简单说明下吧:
print(_ENV == _G) -- true
a = 1
local function f(t)
local print = print
local _ENV = t
print(getmetatable) -- nil
a = 2
b = 3
end
local t = {}
f(t)
print(a, b) -- 1 nil
print(t.a, t.b) -- 2 3
再附上一个自己写的小栗子,能更好的看出各个环境中的变量:
local x = "xxx"
function test1()
a = 1
print("in test1:",a,b,x)
end
function test2(f,env)
setfenv(f,env)
b = 2
f()
print("in env: ",env.a,env.b,env.x)
print("in test2:",a,b,x)
end
local env = {print=print}
print(" ","a","b","x")
test2(test1,env)
栗子运行结果:
a b x
in test1: 1 nil xxx
in env: 1 nil nil
in test2: nil 2 xxx
解释一下:
-
在 test2 中将 test1 函数的内部环境置为 env 表
-
变量 a 存在于 test1 函数内 及 test1 函数的环境内
-
变量 b 只存在于 test2 函数内
-
变量 xxx 可以在 test1 和 test2 函数内访问
沙盒(Sandbox)
讲了环境,就不得不说在 Lua 中使用环境能做些什么,比较直观的就是沙盒,使用沙盒,能在不影响全局环境的前提下,执行不可信的代码,并得知执行结果
附上 wiki 上的一个简单的沙盒例子吧
local env = {}
local function run(untrusted_code)
if untrusted_code:byte(1) == 27 then return nil, "binary bytecode prohibited" end
local untrusted_function, message = loadstring(untrusted_code)
if not untrusted_function then return nil, message end
setfenv(untrusted_function, env)
return pcall(untrusted_function)
end
assert(not run [[print(debug.getinfo(1))]])
assert(run [[x=1]])
assert(run [[while 1 do end]])
这段代码里有几个之前没接触过的点:
-
assert()
-
pcall()
-
loadstring()
-
assert(not run xxx) 中的 not
稍微研究了下,就分别解释一下吧
assert
断言表达式(条件)是否为 nil 或 false
v = assert(v, message)
如果表达式 v 是 nil 或 false,则返回错误 message,message 参数是可选的,默认为 “assertion failed!”,如果断言没有错误,则返回表达式 v
function f()
print(123)
end
local func,err = assert(f,"error!")
print(func,func == f,err) --function: 00000000586C9120 true error!
pcall
和 pcall 相关的还有 xpcall 和 error,这些东西放到下一篇再一起说
loadstring
编译一段字符串,并返回编译好的代码块作为函数,但不执行它
f = loadstring(str, debugname)
第二个参数是用于表示调试时的错误信息,当字符串不能编译成正确的函数时,则会返回 nil
local f,err = loadstring([[looocal i = 1]],"wrrrrrong")
print(err)
--[string "wrrrrrong"]:1: '=' expected near 'i'
f()
--stdin:1: attempt to call global 'f' (a nil value)
提到 loadstring 就也要说到 string.dump ,这个库函数的作用是把一个现有的函数(此函数不能包含 upvalue)转成一个二进制字符串,之后就可以再使用 loadstring 获取该函数的一个副本
function f () print "hello, world" end
s = string.dump (f)
assert (loadstring (s)) () -- hello, world
not?
上面代码中 assert 中的 not,其实就是对 run 方法的结果取反,没有 not 时,结果是这样的
assert(run [[print(debug.getinfo(1))]])
--[string "print(debug.getinfo(1))"]:1: attempt to index global 'debug' (a nil value)
这个打印其实是出错的,断言的结果就是出错了,前面再加一个 not ,断言就变成了 true
参考
http://lua-users.org/wiki/SandBoxes
http://lua-users.org/wiki/EnvironmentsTutorial
https://www.gammon.com.au/scripts/doc.php?lua=assert