#!/usr/bin/env lua local function usage() print ([[Lua static checker Usage: ]] .. arg[0]:match('/?([^/]+)$') .. [[ LUASOURCE Reports globals. Warning: it does not report false negative, but is not exhaustive either. ]]) end if #arg < 1 then usage() os.exit() end local builtins = { assert = true, collectgarbage = true, dofile = true, error = true, _G = true, getfenv = true, getmetatable = true, ipairs = true, load = true, loadfile = true, loadstring = true, next = true, pairs = true, pcall = true, print = true, rawequal = true, rawget = true, rawset = true, select = true, setfenv = true, setmetatable = true, tonumber = true, tostring = true, type = true, unpack = true, _VERSION = true, xpcall = true, module = true, package = true, require = true, coroutine = true, debug = true, io = true, math = true, os = true, string = true, table = true, } for _, file in ipairs(arg) do local p = io.popen('luac -p -l -- ' .. file) local globals = {} for m in p:lines() do if m:match('ETTABUP.*_ENV') then local line, op, field = m:match('%[([%d]+)%].*(.)ETTABUP.*"([_%w]+)"') if not builtins[field] then if op == 'G' then -- Global is used. if not globals[field] then globals[field] = {set = false, used = false, lines={}} end globals[field].used = true globals[field].lines[#globals[field].lines+1] = tonumber(line) else -- Global is set. if not globals[field] then globals[field] = {set = false, used = false, lines={}} end globals[field].set = true globals[field].lines[#globals[field].lines+1] = tonumber(line) end end end end p:close() local sorted = {} local out = {} for field, v in pairs(globals) do if v.set and v.used then -- Report only first appearance. sorted[#sorted+1] = v.lines[1] if not out[v.lines[1]] then out[v.lines[1]] = {} end out[v.lines[1]][#out[v.lines[1]]+1] = field else for _, line in ipairs(v.lines) do sorted[#sorted+1] = line if not out[line] then out[line] = {} end out[line][#out[line]+1] = field end end -- Free some memory. v.lines = nil end table.sort(sorted) for k, line in ipairs(sorted) do -- Sorted can contain the same line several times. if k == 1 or line ~= sorted[k-1] then for _, field in pairs(out[line]) do if globals[field].set and globals[field].used then io.stderr:write(string.format("%s:%s: global '%s' set and used\n", file, line, field)) elseif globals[field].set and not globals[field].used then io.stderr:write(string.format("%s:%s: global '%s' set but not used\n", file, line, field)) else io.stderr:write(string.format("%s:%s: global '%s' used but not set\n", file, line, field)) end end end end end