为Lua声明变量和范围问题

时间:2009-06-18 19:38:51

标签: lua scope

我是Bitfighter的首席开发者,我们使用Lua作为脚本语言,允许玩家编写自己的定制机器人船。

在Lua中,除非另有声明,否则不需要声明变量,并且所有变量都默认为全局范围。这导致一些问题。请使用以下代码段:

loc = bot:getLoc()
items = bot:findItems(ShipType)     -- Find a Ship

minDist = 999999
found = false

for indx, item in ipairs(items) do           
   local d = loc:distSquared(item:getLoc())  

   if(d < minDist) then
      closestItem = item
      minDist = d
   end
end

if(closestItem != nil) then 
   firingAngle = getFiringSolution(closestItem) 
end

在这个片段中,如果findItems()没有返回候选者,那么nearestItem仍将引用它最后一次发现的任何船只,并且在此期间,该船可能已被杀死。如果船被杀死,它将不再存在,并且getFiringSolution()将失败。

你发现了这个问题吗?好吧,我的用户也不会。它很微妙,但具有戏剧性的效果。

一种解决方案是要求声明所有变量,并且所有变量都要默认为本地范围。虽然这种改变不会使程序员无法引用不再存在的对象,但这会使得无意中更难以这样做。

有没有办法告诉Lua默认所有变量到本地范围,和/或要求它们被声明?我知道其他一些语言(例如Perl)可以使用此选项。

谢谢!


这里有很多好的答案,谢谢!

我决定使用Lua'tright'模块的略微修改版本。这似乎让我想到了我想去的地方,我会稍微改进一下这些消息并使它们更适合我的特定背景。

4 个答案:

答案 0 :(得分:3)

没有选项来设置此行为,但标准安装提供了一个模块'strict',它正是这样做的(通过修改元表)。 用法: 要求'严格'

有关更深入的信息和其他解决方案:http://lua-users.org/wiki/DetectingUndefinedVariables,但我建议'严格'。

答案 1 :(得分:2)

八九不离十。

在Lua中,全局变量存在于全局表_G中(现实情况稍微复杂一些,但从Lua方面来说,没有办法告诉AFAIK)。与所有其他Lua表一样,可以将__newindex metatable附加到_G,以控制如何向其添加变量。当有人创建全局时,让这个__newindex处理程序做你想做的事情:抛出错误,允许它但是打印警告等等。

要干涉_G,使用setfenv最简单,最干净。请参阅documentation

答案 2 :(得分:2)

“默认为”本地错误“。请参阅

http://lua-users.org/wiki/LocalByDefault

http://lua-users.org/wiki/LuaScopingDiscussion

您需要使用某种全球环境保护。有一些静态工具可以做到这一点(不太成熟),但最常见的解决方案是使用运行时保护,基于__index__newindex的{​​{1}}的metatable。

Shameles插件:此页面也可能有用:

http://code.google.com/p/lua-alchemy/wiki/LuaGlobalEnvironmentProtection

请注意,虽然它讨论了嵌入到swf中的Lua,但所描述的技术(请参阅sources)确实适用于通用Lua。我们在工作中的生产代码中使用了这些内容。

答案 3 :(得分:0)

实际上,具有过时参考船舶的额外全局变量将足以防止GC丢弃该对象。所以它可以在运行时通过注意到船现在“死”并且拒绝对它做任何事情来检测。它仍然不是正确的船,但至少你不会崩溃。

您可以做的一件事是将用户脚本保存在sandbox中,可能是每个脚本的沙箱。通过正确操作沙箱的环境表或其元表,您可以安排在调用用户代码之前(或之后)从沙箱中丢弃所有或大多数全局变量。

调用后清理沙箱的好处是可以放弃对不应该挂起的东西的额外引用。这可以通过保留允许保留在环境中的字段的白名单来完成,并删除所有其余字段。

例如,以下内容实现了对用户提供的函数的沙盒调用,其中的环境仅包含为每个调用提供的全新临时表后面的白名单。

-- table of globals that will available to user scripts
local user_G = {
        print=_G.print,
        math=_G.math,
        -- ...
    }
-- metatable for user sandbox
local env_mt = { __index=user_G }


-- call the function in a sandbox with an environment in which new global 
-- variables can be created and modified but they will be discarded when the 
-- user code completes.
function doUserCode(user_code, ...)
    local env = setmetatable({}, env_mt) -- create a fresh user environment with RO globals
    setfenv(user_code, env)        -- hang it on the user code
    local results = {pcall(user_code, ...)}
    setfenv(user_code,{})
    return unpack(results)
end

如果你愿意的话,可以通过将全局表推回到一个更多的metatable访问后来扩展它以使其成为只读。

请注意,完整的沙盒解决方案还会考虑如何处理意外(或恶意)执行无限(或仅非常长)循环或其他操作的用户代码。针对此问题的一般解决方案偶尔会讨论Lua list,但很难找到好的解决方案。