local M = {} local T = require "luainspect.types" -- signatures of known globals M.global_signatures = { assert = "assert (v [, message])", collectgarbage = "collectgarbage (opt [, arg])", dofile = "dofile (filename)", error = "error (message [, level])", _G = "(table)", getfenv = "getfenv ([f])", getmetatable = "getmetatable (object)", ipairs = "ipairs (t)", load = "load (func [, chunkname])", loadfile = "loadfile ([filename])", loadstring = "loadstring (string [, chunkname])", next = "next (table [, index])", pairs = "pairs (t)", pcall = "pcall (f, arg1, ...)", print = "print (...)", rawequal = "rawequal (v1, v2)", rawget = "rawget (table, index)", rawset = "rawset (table, index, value)", select = "select (index, ...)", setfenv = "setfenv (f, table)", setmetatable = "setmetatable (table, metatable)", tonumber = "tonumber (e [, base])", tostring = "tostring (e)", type = "type (v)", unpack = "unpack (list [, i [, j]])", _VERSION = "(string)", xpcall = "xpcall (f, err)", module = "module (name [, ...])", require = "require (modname)", coroutine = "(table) coroutine manipulation library", debug = "(table) debug facilities library", io = "(table) I/O library", math = "(table) math functions libary", os = "(table) OS facilities library", package = "(table) package library", string = "(table) string manipulation library", table = "(table) table manipulation library", ["coroutine.create"] = "coroutine.create (f)", ["coroutine.resume"] = "coroutine.resume (co [, val1, ...])", ["coroutine.running"] = "coroutine.running ()", ["coroutine.status"] = "coroutine.status (co)", ["coroutine.wrap"] = "coroutine.wrap (f)", ["coroutine.yield"] = "coroutine.yield (...)", ["debug.debug"] = "debug.debug ()", ["debug.getfenv"] = "debug.getfenv (o)", ["debug.gethook"] = "debug.gethook ([thread])", ["debug.getinfo"] = "debug.getinfo ([thread,] function [, what])", ["debug.getlocal"] = "debug.getlocal ([thread,] level, local)", ["debug.getmetatable"] = "debug.getmetatable (object)", ["debug.getregistry"] = "debug.getregistry ()", ["debug.getupvalue"] = "debug.getupvalue (func, up)", ["debug.setfenv"] = "debug.setfenv (object, table)", ["debug.sethook"] = "debug.sethook ([thread,] hook, mask [, count])", ["debug.setlocal"] = "debug.setlocal ([thread,] level, local, value)", ["debug.setmetatable"] = "debug.setmetatable (object, table)", ["debug.setupvalue"] = "debug.setupvalue (func, up, value)", ["debug.traceback"] = "debug.traceback ([thread,] [message] [, level])", ["io.close"] = "io.close ([file])", ["io.flush"] = "io.flush ()", ["io.input"] = "io.input ([file])", ["io.lines"] = "io.lines ([filename])", ["io.open"] = "io.open (filename [, mode])", ["io.output"] = "io.output ([file])", ["io.popen"] = "io.popen (prog [, mode])", ["io.read"] = "io.read (...)", ["io.tmpfile"] = "io.tmpfile ()", ["io.type"] = "io.type (obj)", ["io.write"] = "io.write (...)", ["math.abs"] = "math.abs (x)", ["math.acos"] = "math.acos (x)", ["math.asin"] = "math.asin (x)", ["math.atan"] = "math.atan (x)", ["math.atan2"] = "math.atan2 (y, x)", ["math.ceil"] = "math.ceil (x)", ["math.cos"] = "math.cos (x)", ["math.cosh"] = "math.cosh (x)", ["math.deg"] = "math.deg (x)", ["math.exp"] = "math.exp (x)", ["math.floor"] = "math.floor (x)", ["math.fmod"] = "math.fmod (x, y)", ["math.frexp"] = "math.frexp (x)", ["math.huge"] = "math.huge", ["math.ldexp"] = "math.ldexp (m, e)", ["math.log"] = "math.log (x)", ["math.log10"] = "math.log10 (x)", ["math.max"] = "math.max (x, ...)", ["math.min"] = "math.min (x, ...)", ["math.modf"] = "math.modf (x)", ["math.pi"] = "math.pi", ["math.pow"] = "math.pow (x, y)", ["math.rad"] = "math.rad (x)", ["math.random"] = "math.random ([m [, n]])", ["math.randomseed"] = "math.randomseed (x)", ["math.sin"] = "math.sin (x)", ["math.sinh"] = "math.sinh (x)", ["math.sqrt"] = "math.sqrt (x)", ["math.tan"] = "math.tan (x)", ["math.tanh"] = "math.tanh (x)", ["os.clock"] = "os.clock ()", ["os.date"] = "os.date ([format [, time]])", ["os.difftime"] = "os.difftime (t2, t1)", ["os.execute"] = "os.execute ([command])", ["os.exit"] = "os.exit ([code])", ["os.getenv"] = "os.getenv (varname)", ["os.remove"] = "os.remove (filename)", ["os.rename"] = "os.rename (oldname, newname)", ["os.setlocale"] = "os.setlocale (locale [, category])", ["os.time"] = "os.time ([table])", ["os.tmpname"] = "os.tmpname ()", ["package.cpath"] = "package.cpath", ["package.loaded"] = "package.loaded", ["package.loaders"] = "package.loaders", ["package.loadlib"] = "package.loadlib (libname, funcname)", ["package.path"] = "package.path", ["package.preload"] = "package.preload", ["package.seeall"] = "package.seeall (module)", ["string.byte"] = "string.byte (s [, i [, j]])", ["string.char"] = "string.char (...)", ["string.dump"] = "string.dump (function)", ["string.find"] = "string.find (s, pattern [, init [, plain]])", ["string.format"] = "string.format (formatstring, ...)", ["string.gmatch"] = "string.gmatch (s, pattern)", ["string.gsub"] = "string.gsub (s, pattern, repl [, n])", ["string.len"] = "string.len (s)", ["string.lower"] = "string.lower (s)", ["string.match"] = "string.match (s, pattern [, init])", ["string.rep"] = "string.rep (s, n)", ["string.reverse"] = "string.reverse (s)", ["string.sub"] = "string.sub (s, i [, j])", ["string.upper"] = "string.upper (s)", ["table.concat"] = "table.concat (table [, sep [, i [, j]]])", ["table.insert"] = "table.insert (table, [pos,] value)", ["table.maxn"] = "table.maxn (table)", ["table.remove"] = "table.remove (table [, pos])", ["table.sort"] = "table.sort (table [, comp])", } -- utility function. Converts e.g. name 'math.sqrt' to its value. local function resolve_global_helper_(name) local o = _G for fieldname in name:gmatch'[^%.]+' do o = o[fieldname] end return o end local function resolve_global(name) local a, b = pcall(resolve_global_helper_, name) if a then return b else return nil, b end end -- Same as global_signatures but maps value (not name) to signature. M.value_signatures = {} local isobject = {['function']=true, ['table']=true, ['userdata']=true, ['coroutine']=true} for name,sig in pairs(M.global_signatures) do local val, err = resolve_global(name) if isobject[type(val)] then M.value_signatures[val] = sig end end -- min,max argument counts. M.argument_counts = { [assert] = {1,2}, [collectgarbage] = {1,2}, [dofile] = {1}, [error] = {1,2}, [getfenv or false] = {0,1}, [getmetatable] = {1,1}, [ipairs] = {1,1}, [load] = {1,2}, [loadfile] = {0,1}, [loadstring] = {1,2}, [next] = {1,2}, [pairs] = {1,1}, [pcall] = {1,math.huge}, [print] = {0,math.huge}, [rawequal] = {2,2}, [rawget] = {2,2}, [rawset] = {3,3}, [select] = {1, math.huge}, [setfenv or false] = {2,2}, [setmetatable] = {2,2}, [tonumber] = {1,2}, [tostring] = {1}, [type] = {1}, [unpack] = {1,3}, [xpcall] = {2,2}, [module] = {1,math.huge}, [require] = {1,1}, [coroutine.create] = {1,1}, [coroutine.resume] = {1, math.huge}, [coroutine.running] = {0,0}, [coroutine.status] = {1,1}, [coroutine.wrap] = {1,1}, [coroutine.yield] = {0,math.huge}, [debug.debug] = {0,0}, [debug.getfenv or false] = {1,1}, [debug.gethook] = {0,1}, [debug.getinfo] = {1,3}, [debug.getlocal] = {2,3}, [debug.getmetatable] = {1,1}, [debug.getregistry] = {0,0}, [debug.getupvalue] = {2,2}, [debug.setfenv or false] = {2,2}, [debug.sethook] = {2,4}, [debug.setlocal] = {3,4}, [debug.setmetatable] = {2,2}, [debug.setupvalue] = {3,3}, [debug.traceback] = {0,3}, [io.close] = {0,1}, [io.flush] = {0,0}, [io.input] = {0,1}, [io.lines] = {0,1}, [io.open] = {1,2}, [io.output] = {0,1}, [io.popen] = {1,2}, [io.read] = {0,math.huge}, [io.tmpfile] = {0}, [io.type] = {1}, [io.write] = {0,math.huge}, [math.abs] = {1}, [math.acos] = {1}, [math.asin] = {1}, [math.atan] = {1}, [math.atan2] = {2,2}, [math.ceil] = {1,1}, [math.cos] = {1,1}, [math.cosh] = {1,1}, [math.deg] = {1,1}, [math.exp] = {1,1}, [math.floor] = {1,1}, [math.fmod] = {2,2}, [math.frexp] = {1,1}, [math.ldexp] = {2,2}, [math.log] = {1,1}, [math.log10] = {1,1}, [math.max] = {1,math.huge}, [math.min] = {1,math.huge}, [math.modf] = {1,1}, [math.pow] = {2,2}, [math.rad] = {1,1}, [math.random] = {0,2}, [math.randomseed] = {1,1}, [math.sin] = {1,1}, [math.sinh] = {1,1}, [math.sqrt] = {1,1}, [math.tan] = {1,1}, [math.tanh] = {1,1}, [os.clock] = {0,0}, [os.date] = {0,2}, [os.difftime] = {2,2}, [os.execute] = {0,1}, [os.exit] = {0,1}, [os.getenv] = {1,1}, [os.remove] = {1,1}, [os.rename] = {2,2}, [os.setlocale] = {1,2}, [os.time] = {0,1}, [os.tmpname] = {0,0}, [package.loadlib] = {2,2}, [package.seeall] = {1,1}, [string.byte] = {1,3}, [string.char] = {0,math.huge}, [string.dump] = {1,1}, [string.find] = {2,4}, [string.format] = {1,math.huge}, [string.gmatch] = {2,2}, [string.gsub] = {3,4}, [string.len] = {1,1}, [string.lower] = {1,1}, [string.match] = {2,3}, [string.rep] = {2,2}, [string.reverse] = {1,1}, [string.sub] = {2,3}, [string.upper] = {1,1}, [table.concat] = {1,4}, [table.insert] = {2,3}, [table.maxn] = {1,1}, [table.remove] = {1,2}, [table.sort] = {1,2}, [false] = nil -- trick (relies on potentially undefined behavior) } -- functions with zero or nearly zero side-effects, and with deterministic results, that may be evaluated by the analyzer. M.safe_function = { [require] = true, [rawequal] = true, [rawget] = true, [require] = true, -- sort of [select] = true, [tonumber] = true, [tostring] = true, [type] = true, [unpack] = true, [coroutine.create] = true, -- [coroutine.resume] [coroutine.running] = true, [coroutine.status] = true, [coroutine.wrap] = true, --[coroutine.yield] -- [debug.debug] --[debug.getfenv] = true, [debug.gethook] = true, [debug.getinfo] = true, [debug.getlocal] = true, [debug.getmetatable] = true, [debug.getregistry] = true, [debug.getupvalue] = true, -- [debug.setfenv] -- [debug.sethook] -- [debug.setlocal] -- [debug.setmetatable] -- [debug.setupvalue] -- [debug.traceback] = true, [io.type] = true, -- skip all other io.* [math.abs] = true, [math.acos] = true, [math.asin] = true, [math.atan] = true, [math.atan2] = true, [math.ceil] = true, [math.cos] = true, [math.cosh] = true, [math.deg] = true, [math.exp] = true, [math.floor] = true, [math.fmod] = true, [math.frexp] = true, [math.ldexp] = true, [math.log] = true, [math.log10] = true, [math.max] = true, [math.min] = true, [math.modf] = true, [math.pow] = true, [math.rad] = true, --[math.random] --[math.randomseed] [math.sin] = true, [math.sinh] = true, [math.sqrt] = true, [math.tan] = true, [math.tanh] = true, [os.clock] = true, -- safe but non-deterministic [os.date] = true,-- safe but non-deterministic [os.difftime] = true, --[os.execute] --[os.exit] [os.getenv] = true, -- though depends on environment --[os.remove] --[os.rename] --[os.setlocale] [os.time] = true, -- safe but non-deterministic --[os.tmpname] [string.byte] = true, [string.char] = true, [string.dump] = true, [string.find] = true, [string.format] = true, [string.gmatch] = true, [string.gsub] = true, [string.len] = true, [string.lower] = true, [string.match] = true, [string.rep] = true, [string.reverse] = true, [string.sub] = true, [string.upper] = true, [table.maxn] = true, } M.mock_functions = {} -- TODO:IMPROVE local function mockfunction(func, ...) local inputs = {n=0} local outputs = {n=0} local isoutputs for i=1,select('#', ...) do local v = select(i, ...) if type(v) == 'table' then v = v[1] end if v == 'N' or v == 'I' then v = T.number end if v == '->' then isoutputs = true elseif isoutputs then outputs[#outputs+1] = v; outputs.n = outputs.n + 1 else inputs[#inputs+1] = v; inputs.n = inputs.n + 1 end end M.mock_functions[func] = {inputs=inputs, outputs=outputs} end mockfunction(math.abs, 'N', '->', {'N',0,math.huge}) mockfunction(math.acos, {'N',-1,1}, '->', {'N',0,math.pi/2}) mockfunction(math.asin, {'N',-1,1}, '->', {'N',-math.pi/2,math.pi/2}) mockfunction(math.atan, {'N',-math.huge,math.huge}, '->', {'N',-math.pi/2,math.pi/2}) --FIX atan2 mockfunction(math.ceil, 'N','->','I') mockfunction(math.cos, 'N','->',{'N',-1,1}) mockfunction(math.cosh, 'N','->',{'N',1,math.huge}) mockfunction(math.deg, 'N','->','N') mockfunction(math.exp, 'N','->',{'N',0,math.huge}) mockfunction(math.floor, 'N','->','I') mockfunction(math.fmod, 'N','N','->','N') mockfunction(math.frexp, 'N','->',{'N',-1,1},'->','I') mockfunction(math.ldexp, {'N','I'},'->','N') mockfunction(math.log, {'N',0,math.huge},'->','N') mockfunction(math.log10, {'N',0,math.huge},'->','N') -- function max(...) print 'NOT IMPL'end -- function min(...) print 'NOT IMPL'end mockfunction(math.modf, 'N','->','I',{'N',-1,1}) mockfunction(math.pow, 'N','N','->','N') -- improve? mockfunction(math.rad, 'N','->','N') -- random = function() print 'NOT IMPL' end mockfunction(math.randomseed, 'N') mockfunction(math.sin, 'N','->',{'N',-1,1}) mockfunction(math.sinh, 'N','->','N') mockfunction(math.sqrt, {'N',0,math.huge},'->',{'N',0,math.huge}) mockfunction(math.tan, 'N','->','N') -- improve? mockfunction(math.tanh, 'N','->',{'N',-1,1}) return M