| ------------------------------------------------------------------------------- |
| -- Copyright (c) 2006-2014 Fabien Fleutot and others. |
| -- |
| -- All rights reserved. |
| -- |
| -- This program and the accompanying materials are made available |
| -- under the terms of the Eclipse Public License v1.0 which |
| -- accompanies this distribution, and is available at |
| -- http://www.eclipse.org/legal/epl-v10.html |
| -- |
| -- This program and the accompanying materials are also made available |
| -- under the terms of the MIT public license which accompanies this |
| -- distribution, and is available at http://www.lua.org/license.html |
| -- |
| -- Contributors: |
| -- Fabien Fleutot - API and implementation |
| -- |
| ------------------------------------------------------------------------------- |
| |
| -- |
| -- Example of treequery usage: list all the unauthorized global |
| -- variable occurrences in a given source file. |
| -- |
| -- Usage: lua globals.lua file_to_analyze.lua |
| -- |
| -- TODO: |
| -- * take command-line options |
| -- * |
| |
| require 'metalua.loader' -- needed to load "metalua/treequery.mlua" |
| |
| mlc = require 'metalua.compiler'.new() -- compiler |
| Q = require 'metalua.treequery' -- AST queries |
| pp = require 'metalua.pprint' -- pretty-printer |
| srcfile = assert(..., "no file name") -- filename to analyze... |
| ast = mlc :srcfile_to_ast(srcfile) -- ...converted in an AST |
| |
| -- Global variable names which won't be reported: |
| |
| allowed = [[ _G _VERSION assert collectgarbage coroutine |
| coroutine.create coroutine.resume coroutine.running coroutine.status |
| coroutine.wrap coroutine.yield debug debug.debug debug.getfenv |
| debug.gethook debug.getinfo debug.getlocal debug.getmetatable |
| debug.getregistry debug.getupvalue debug.setfenv debug.sethook |
| debug.setlocal debug.setmetatable debug.setupvalue debug.traceback |
| dofile error gcinfo getfenv getmetatable io io.close io.flush io.input |
| io.lines io.open io.output io.popen io.read io.stderr io.stdin |
| io.stdout io.tmpfile io.type io.write ipairs list load loadfile |
| loadstring math math.abs math.acos math.asin math.atan math.atan2 |
| math.ceil math.cos math.cosh math.deg math.exp math.floor math.fmod |
| math.frexp math.huge math.ldexp math.log math.log10 math.max math.min |
| math.mod math.modf math.pi math.pow math.rad math.random |
| math.randomseed math.sin math.sinh math.sqrt math.tan math.tanh module |
| newproxy next os os.clock os.date os.difftime os.execute os.exit |
| os.getenv os.remove os.rename os.setlocale os.time os.tmpname package |
| package.config package.cpath package.loaded package.loaders |
| package.loadlib package.path package.preload package.seeall pairs |
| pcall print rawequal rawget rawset require select setfenv setmetatable |
| string string.byte string.char string.dump string.find string.format |
| string.gfind string.gmatch string.gsub string.len string.lower |
| string.match string.rep string.reverse string.sub string.upper table |
| table.concat table.foreach table.foreachi table.getn table.insert |
| table.maxn table.remove table.setn table.sort tonumber tostring type |
| unpack xpcall ]] |
| |
| |
| -- The same, put in a hash-table for easy retrieval: |
| allowed_set = { } |
| for word in allowed :gmatch "%S+" do allowed_set[word]=true end |
| |
| function analyze(ast) |
| local globals = { } -- variable name -> list of line numbers |
| |
| -- add the line number where node `id` occurs into table `globals`: |
| local function log_global(id, ...) |
| local name = id[1] |
| local line = id.lineinfo and id.lineinfo.first.line or "no info" |
| local full_chain = {[0]=id, ...} |
| -- Retrieve the subfields x.subfield_1.....subfield_n |
| for i, ancestor in ipairs(full_chain) do |
| -- `Index{ x, `String y } if x==full_chain[i-1] |
| if ancestor.tag=='Index' and |
| ancestor[1]==full_chain[i-1] and |
| ancestor[2].tag=='String' then |
| name = name.."."..ancestor[2][1] |
| else break end |
| end |
| local lines = globals[name] |
| if lines then table.insert(lines, line) |
| else globals[name] = { line } end |
| end |
| |
| -- Treequery command to retrieve global variable occurrences: |
| Q(ast) -- globals are... |
| :filter 'Id' -- ...nodes with tag 'Id'... |
| :filter_not (Q.is_binder) -- ...which are not local binders... |
| :filter_not (Q.get_binder) -- ...and aren't bound; |
| :foreach (log_global) -- log each of them in `globals`. |
| |
| -- format and sort the result |
| local sort_them = { } |
| for name, lines in pairs(globals) do |
| if not allowed_set[name] then |
| local line = name .. ": " .. pp.tostring(lines) :sub (2, -2) |
| table.insert(sort_them, line) |
| end |
| end |
| |
| -- print the result |
| if next(sort_them) then |
| table.sort(sort_them) |
| print(table.concat(sort_them, "\n")) |
| else print "no global found" end |
| end |
| |
| function list() |
| local banned = { ['package.loaded'] = true } |
| local acc = { } |
| local seen = { } |
| local function rec(prefix, t) |
| if seen[t] then return end |
| seen[t]=true |
| for k, v in pairs(t) do |
| if type(k)=='string' then |
| local path = prefix and prefix..'.'..k or k |
| table.insert(acc, path) |
| if type(v)=='table' and not banned[path] then rec(path, v) end |
| end |
| end |
| end |
| rec(nil, _G) |
| table.sort(acc) |
| return table.concat(acc, ' ') |
| end |
| |
| analyze(ast) |
| |