diff -urN lua-5.1.3/etc/checkglobals.lua lua-5.1.3-checkglobals/etc/checkglobals.lua --- lua-5.1.3/etc/checkglobals.lua 1969年12月31日 19:00:00.000000000 -0500 +++ lua-5.1.3-checkglobals/etc/checkglobals.lua 2008年03月22日 22:46:19.227500000 -0400 @@ -0,0 +1,87 @@ +-- checkglobals.lua +-- Undeclared global variable detection for Lua. +-- +-- This module consists of and returns a single function: +-- +-- f = checkglobals(f, env) +-- +-- In short, checkglobals validates that the function f uses only +-- global variables defined in the table env. +-- +-- Often, checkglobals() is called without arguments. If f is +-- unspecified (nil), the calling function is used. If f is a number, +-- the function at stack level f is used (1 is the calling function). +-- If env is unspecified (nil), the environment of the calling +-- function is used. +-- +-- The test passes only if all global variables "directly" read from +-- or written to lexically inside the function f (including functions +-- lexically nested in f) exist in the table env. That is, +-- env[varname] ~= nil for variable with name varname. Access to +-- globals "indirectly" via _G or getfenv() don't count. +-- +-- On success, returns f. On failure, raises error. The error +-- contains a line number unless the source was stripped (luac -s). +-- +-- This module requires a patched version of Lua that makes minor +-- additions to ldebug.c (lua_getinfo 'g' option) and ldblib.c +-- (debug.getinfo 'globals' field). Internally, it retrieves +-- GETGLOBAL and SETGLOBAL bytecodes. +-- +-- This module can be used in various ways including... +-- +-- Usage mode #1: Define globals, then check. +-- -- foo.lua +-- x = 1 +-- function foo() x = x + 1; print(x) end +-- function bar() X = X + 1; print(X) end -- opps! +-- foo() +-- require 'checkglobals' () +-- +-- Usage mode #2: Check, then define only locals. +-- -- foo.lua +-- require 'checkglobals' () +-- local x = 1 +-- local function foo() x = x + 1; print(x) end +-- local function bar() X = X + 1; print(X) end -- opps! +-- foo() +-- +-- Usage mode #3: Check specified function. +-- -- foo.lua +-- local checkglobals = require 'checkglobals' +-- function foo() +-- print(mAtH.pi) -- opps! +-- end +-- checkglobals(foo) +-- foo() +-- +-- David Manura, 2008. Licensed under the same terms as Lua itself +-- (MIT License). + + +-- copy in case a sandbox removes these +local getinfo = debug.getinfo +local unpack = unpack +local type = type +local getfenv = getfenv +local error = error + +local function checkglobals(f, env) + local fp = f or 1 + if type(fp) == 'number' then fp = fp + 1 end + env = env or getfenv(2) + local gref = getinfo(fp, 'g').globals + for i=1,#gref,gref.ncols do + local op,name,linenum = unpack(gref, i,i+2) + if env[name] == nil then + error('accessed undefined variable "' .. name .. '"' .. + (linenum and ' at line ' .. linenum or ''), + 2) + end + end + return f +end + +checkglobals() -- check oneself :) + +return checkglobals diff -urN lua-5.1.3/src/ldblib.c lua-5.1.3-checkglobals/src/ldblib.c --- lua-5.1.3/src/ldblib.c 2008年01月21日 08:11:21.000000000 -0500 +++ lua-5.1.3-checkglobals/src/ldblib.c 2008年03月22日 21:56:56.227500000 -0400 @@ -136,6 +136,11 @@ treatstackoption(L, L1, "activelines"); if (strchr(options, 'f')) treatstackoption(L, L1, "func"); + + /* PATCH - checkglobals */ + if (strchr(options, 'g')) + treatstackoption(L, L1, "globals"); + return 1; /* return table */ } diff -urN lua-5.1.3/src/ldebug.c lua-5.1.3-checkglobals/src/ldebug.c --- lua-5.1.3/src/ldebug.c 2007年12月28日 10:32:23.000000000 -0500 +++ lua-5.1.3-checkglobals/src/ldebug.c 2008年03月22日 21:59:35.399375000 -0400 @@ -219,6 +219,10 @@ } break; } + + /* PATCH - checkglobals */ + case 'g': + case 'L': case 'f': /* handled by lua_getinfo */ break; @@ -229,6 +233,31 @@ } +/* PATCH - checkglobals */ +static void auxgetinfoglobals(lua_State *L, Proto *p, Table *t, int *c) { + TValue *k = p->k; + int j; + for (j = 0; j < p->sizecode; j++) { + const Instruction i = p->code[j]; + OpCode op = GET_OPCODE(i); + const TValue *ts; + if (op != OP_GETGLOBAL && op != OP_SETGLOBAL) + continue; + ts = k+GETARG_Bx(i); + lua_assert(ttisstring(ts)); + setobj2t(L, luaH_setnum(L, t, (*c)++), + (L->top - (OP_GETGLOBAL ? 2 : 1))) + setobj2t(L, luaH_setnum(L, t, (*c)++), ts); + if(p->lineinfo) { + setnvalue(luaH_setnum(L, t, (*c)++), p->lineinfo[j]); + } + } + for (j = 0; j < p->sizep; j++) { /* lexically nested functions */ + auxgetinfoglobals(L, p->p[j], t, c); + } +} + + LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar) { int status; Closure *f = NULL; @@ -254,6 +283,22 @@ } if (strchr(what, 'L')) collectvalidlines(L, f); + + /* PATCH - checkglobals */ + if (strchr(what, 'g')) { + lua_newtable(L); + if (f != NULL || !f->c.isC) { + Table *t = hvalue(L->top-1); + int c = 1; + lua_pushnumber(L, f->l.p->lineinfo ? 3 : 2); + lua_setfield(L, -2, "ncols"); + lua_pushliteral(L, "GETGLOBAL"); + lua_pushliteral(L, "SETGLOBAL"); + auxgetinfoglobals(L, f->l.p, t, &c); + lua_pop(L, 2); + } + } + lua_unlock(L); return status; } diff -urN lua-5.1.3/test/checkglobalstest.lua lua-5.1.3-checkglobals/test/checkglobalstest.lua --- lua-5.1.3/test/checkglobalstest.lua 1969年12月31日 19:00:00.000000000 -0500 +++ lua-5.1.3-checkglobals/test/checkglobalstest.lua 2008年03月22日 22:46:56.821250000 -0400 @@ -0,0 +1,55 @@ +_=[[--filtered + +-- Test suite for checkglobals.lua. +-- D.Manura, 2008. + +package.path = package.path .. ';etc/?.lua' + +local checkglobals = require 'checkglobals' + +-- some utility functions for the test suite. +local function fail(f) + local ok,msg = pcall(f) + if ok then error('fail expected', 2) end +end +local function pass(f) f() end +local function env(f) + local newenv = setmetatable({}, {__index=_G}) + return setfenv(f, newenv) +end + +-- empty function +checkglobals(function() end) + +-- basic tests, check outside +checkglobals(function() print() end) +checkglobals(function() foo() end, {foo=print}) +fail<< checkglobals(function() print() end, {})>> +pass<< + local G = _G + G.setfenv(1, {print = print}) + checkglobals(function() print() end) + fail<< checkglobals(function() print(math.pi) end)>> +>> +pass<< checkglobals(function() print = 1 end)>> +fail<< checkglobals(function() prinT = 1 end)>> + +-- basic tests, check inside +pass(env<< checkglobals()>>) +pass(env<< checkglobals(nil, 1)>>) +pass(env<< x = 1; y=math.sqrt(x); checkglobals()>>) +fail(env<< checkglobals(); x = 1>>) +fail(env<< checkglobals(nil, 1); x = 1>>) +fail(env<< if false then x = 1 end; checkglobals()>>) + +-- nested functions +pass<< checkglobals(function() return function() print() end end)>> +fail<< checkglobals(function() return function() print() end end, {})>> + +print 'DONE' + +]] +_=_:gsub('<<', ' (function() '):gsub('>>', ' end) ') +assert(loadstring(_))() +-- The above source filters itself to allow << ...>> as shorthand +-- for (function() ... end).