ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Lua 정적 분석기(전역변수 사용 detection)
    카테고리 없음 2017. 6. 26. 22:30
    728x90

    local 로 선언되거지 않거나

    g_ 로 시작하지 않는 선언되지 않는 global 변수를 찾아서 화면에 표시한다

     

    #!/home/nop/lua-5.0/bin/lua
    
    
    
    --[[ 
    
    Usage: lualint [-r|-s] filename.lua [ [-r|-s] [filename.lua] ...]
    
    
    
    lualint performs static analysis of Lua source code's usage of global
    variables..  It uses luac's bytecode listing.  It reports all accesses
    to undeclared global variables, which catches many typing errors in
    variable names.  For example:
    
    local really_aborting
    local function abort() os.exit(1) end
    
    if not os.getenv("HOME") then
    	realy_aborting = true
        abortt()
    end
    
    reports:
      /tmp/example.lua:4: *** global SET of realy_aborting
      /tmp/example.lua:5: global get of abortt
    
    It is primarily designed for use on LTN7-style modules, where each
    source file only exports one global symbol.  (A module contained in
    the file "foobar.lua" should only export the symbol "foobar".)
    
    A "relaxed" mode is available for source not in LTN7 style.  It only
    detects reads from globals that were never set.  The switch "-r" puts
    lualint into relaxed mode for the following files; "-s" switches back
    to strict.
    
    Required packages are tracked, although not recursively.  If you call
    "myext.process()" you should require "myext", and not depend on other
    dependencies to load it.  LUA_PATH is followed as usual to find
    requirements.
    
    Some (not strictly LTN7) modules may wish to export other variables
    into the global environment.  To do so, use the declare function:
    
    
    declare "xpairs"
      function xpairs(node)
        [...]
    
    
    
    Similarly, to quiet warnings about reading global variables you are
    aware may be unavailable:
    
    
      lint_ignore "lua_fltk_version"
      if lua_fltk_version then print("fltk loaded") end
    
    
    One way of defining these is in a module "declare.lua":
    
      function declare(s)
      end
    
      declare "lint_ignore"
      
      function lint_ignore(s)
      end
    
    
    
    (Setting declare is OK, because it's in the "declare" module.)  These
    
    functions don't have to do anything, or in fact actually exist!  They
    
    can be in dead code:
    
    
    
      if false then declare "xpairs" end
    
        
    
    This is because lualint only performs a rather primitive and cursory
    
    scan of the bytecode.  Perhaps declarations should only be allowed in
    
    the main chunk.
    
    
    
    TODO:
    
    
    
    The errors don't come out in any particular order.
    
    
    
    Should switch to Rici's parser, which should do a much better job of
    
    this, and allow detection of some other common situations.
    
    
    
    CREDITS:
    
    
    
    Jay Carlson (nop@nop.com)
    
    
    
    This is all Ben Jackson's (ben@ben.com) fault, who did some similar
    
    tricks in MOO.
    
    
    
    ]]
    
    
    
    
    
    local function Set(l) 
      local t = {}
      for _,v in ipairs(l) do
        t[v] = true
      end
      return t
    end
    
    
    
    local ignoreget = Set{
    
    "LUA_PATH", "_G", "_LOADED", "_TRACEBACK", "_VERSION", "__pow", "arg",
    
    "assert", "collectgarbage", "coroutine", "debug", "dofile", "error",
    
    "gcinfo", "getfenv", "getmetatable", "io", "ipairs", "loadfile",
    
    "loadlib", "loadstring", "math", "newproxy", "next", "os", "pairs",
    
    "pcall", "print", "rawequal", "rawget", "rawset", "require",
    
    "setfenv", "setmetatable", "string", "table", "tonumber", "tostring",
    
    "type", "unpack", "xpcall",
    
    }
    
    
    
    local function fileexists(fname)
    
      local f = io.open(fname)
    
      if f then 
    
        f:close()
    
        return true
    
      else
    
        return false
    
      end
    
    end
    
    
    
    -- borrowed from LTN11
    
    local function locate(name)
    
      local path = LUA_PATH
    
      if type(path) ~= "string" then
    
        path = os.getenv "LUA_PATH" or "./?.lua"
    
      end
    
      for path in string.gfind(path, "[^;]+") do
    
        path = string.gsub(path, "?", name)
    
        if fileexists(path) then
    
          return path
    
        end
    
      end
    
      return nil
    
    end
    
    
    
    
    
    local function scanfile(filename)
    
      local modules = {}
    
      local declared = {}
    
      local lint_ignored = {}
    
      local refs = {}
    
      local saw_last = nil
    
    
    
      local context, curfunc
    
    
    
      if not fileexists(filename) then
    
        return nil, "file "..filename.." does not exist"
    
      end
    
    
    
      -- Run once to see if it parses correctly
    
      
    
      if not os.execute("luac -o lualint.tmp "..filename) then
    
        return nil, "file "..filename.." did not successfully parse"
    
      end
    
    
    
      if not fileexists("lualint.tmp") then
    
        return nil, "file "..filename.." did not successfully parse"
    
      end
    
    
    
      assert(os.remove("lualint.tmp"))
    
    
    
      local bc = assert(io.popen("luac -l -p "..filename))
    
      
    
      for line in bc:lines() do
    
        -- main <examples/xhtml2wiki.lua:0> (64 instructions, 256 bytes at 0x805c1a0)
    
        -- function <examples/xhtml2wiki.lua:13> (6 instructions, 24 bytes at 0x805c438)
    
        local found, _, type, fname = string.find(line, "(%w+) <([^>]+)>")
    
        if found then 
    
          if context == "main" then fname="*MAIN*" end
    
          curfunc = fname
    
        end
    
        
    
        -- print("sawlast", saw_last)
    
        --  2 [1] LOADK     1 1 ; "lazytree"
    
        local found, _, constname = string.find(line, '%sLOADK .-;%s"(.-)"')
    
        if saw_last and found then
    
          if saw_last == "require" then
    
            -- print("require", constname)
    
            table.insert(modules, constname)
    
          elseif saw_last == "declare" then
    
            -- print("declare", constname)
    
            table.insert(declared, constname)
    
          elseif saw_last == "lint_ignore" then
    
            lint_ignored[constname] = true
    
          end
    
        end
    
        
    
        --  4 [2] GETGLOBAL 0 0 ; require
    
        local found, _, lineno, instr, gname = string.find(line, "%[(%d+)%]%s+([SG]ETGLOBAL).-; (.+)")
    
        if found then
    
          local t = refs[curfunc] or {SETGLOBAL={n=0}, GETGLOBAL={n=0}}
    
          local err = {name=gname, lineno=lineno}
    
          table.insert(t[instr], err)
    
          refs[curfunc] = t
    
          saw_last = gname
    
        else
    
          saw_last = nil
    
        end
    
      end
    
      bc:close()
    
      return modules, declared, lint_ignored, refs
    
    end
    
    
    
    local found_sets = false
    
    local found_gets = false
    
    local parse_failed = false
    
    local import_failed = true
    
    
    
    -- print("args", arg[1])
    
    local function lint(filename, relaxed)
    
      local modules, declared, lint_ignored, refs = scanfile(filename)
    
    
    
      if not modules then
    
        print(string.format("%s:%d: *** could not parse: %s ", filename, 1, declared))
    
        parse_failed = true
    
        return
    
      end
    
      
    
      local imported_declare_set = {}
    
      for i,module in ipairs(modules) do
    
        local path = locate(module)
    
        if not path then 
    
          print(string.format("%s:%d: could not find imported module %s ", filename, 1, module))
    
          import_failed = true
    
        else
    
          local success, imported_declare, _, _ = scanfile(path)
    
          if not success then
    
            print(string.format("%s:%d: could not parse import: %s ", path, 1, imported_declare))
    
            import_failed = true
    
          else
    
            for i,declared in ipairs(imported_declare) do
    
              imported_declare_set[declared] = true
    
            end
    
          end
    
        end
    
      end
    
        
    
      local moduleset = Set(modules)
    
      local declaredset = Set(declared)
    
      
    
      local self_name = nil
    
      do
    
        local _
    
        if string.find(filename, "/") then
    
          _, _, self_name = string.find(filename, ".-/(%w+)%.lua")
    
        else
    
          _,_,self_name = string.find(filename, "(%w+)%.lua")
    
        end
    
      end
    
      -- print("selfname", self_name)
    
    
    
      local was_set = {}
    
      
    
      local function will_warn_for(name)
    
        if relaxed and was_set[name] then
    
          return false
    
        end
    
        if name == self_name or
    
          lint_ignored[name] or
    
          ignoreget[name] or
    
          moduleset[name] or
    
          declaredset[name] or
    
          imported_declare_set[name] then
    
          return false
    
        end
    
        return true
    
      end
    
    
    
      for f,t in pairs(refs) do
    
        for _,r in ipairs(t.SETGLOBAL) do
    
          if r.name ~= self_name and not declaredset[r.name] then
    
            was_set[r.name] = true
    
            if not relaxed then
    
              if string.sub(r.name, 1, 2) ~= 'g_' then
    
                print(string.format("%s:%d: *** global SET of %s", filename, r.lineno, r.name))
    
              end
    
              found_sets = true
    
            end
    
          end
    
        end
    
      end
    
    
    
      for f,t in pairs(refs) do
    
        for _,r in ipairs(t.GETGLOBAL) do
    
          if will_warn_for(r.name) then
    
            if string.sub(r.name, 1, 2) ~= 'g_' then
    
              print(string.format("%s:%d: global get of %s", filename, r.lineno, r.name))
    
            end
    
            found_gets = true
    
          end
    
        end
    
      end
    
    end
    
    
    
    if arg.n == 0 then
    
      print("usage: lualint filename.lua [filename.lua ...]")
    
      os.exit(1)
    
    end
    
    
    
    local relaxed_mode = false
    
    
    
    for i,v in ipairs(arg) do
    
      if v == "-r" then
    
        relaxed_mode = true
    
      elseif v == "-s" then
    
        relaxed_mode = false
    
      else
    
        lint(v, relaxed_mode)
    
      end
    
    end
    
    
    
    if parse_failed then
    
      os.exit(3)
    
    elseif import_failed then
    
      os.exit(1)
    
    elseif found_sets then
    
      os.exit(2)
    
    elseif found_gets then
    
      os.exit(1)
    
    else
    
      os.exit(0)
    
    end

     

    #!/home/nop/lua-5.0/bin/lua

     

    --[[ 

     

    Usage: lualint [-r|-s] filename.lua [ [-r|-s] [filename.lua] ...]

     

    lualint performs static analysis of Lua source code's usage of global

    variables..  It uses luac's bytecode listing.  It reports all accesses

    to undeclared global variables, which catches many typing errors in

    variable names.  For example:

     

      local really_aborting

      local function abort() os.exit(1) end

      if not os.getenv("HOME") then

        realy_aborting = true

        abortt()

      end

     

    reports:

     

      /tmp/example.lua:4: *** global SET of realy_aborting

      /tmp/example.lua:5: global get of abortt

     

    It is primarily designed for use on LTN7-style modules, where each

    source file only exports one global symbol.  (A module contained in

    the file "foobar.lua" should only export the symbol "foobar".)

     

    A "relaxed" mode is available for source not in LTN7 style.  It only

    detects reads from globals that were never set.  The switch "-r" puts

    lualint into relaxed mode for the following files; "-s" switches back

    to strict.

     

    Required packages are tracked, although not recursively.  If you call

    "myext.process()" you should require "myext", and not depend on other

    dependencies to load it.  LUA_PATH is followed as usual to find

    requirements.

     

    Some (not strictly LTN7) modules may wish to export other variables

    into the global environment.  To do so, use the declare function:

     

      declare "xpairs"

      function xpairs(node)

        [...]

     

    Similarly, to quiet warnings about reading global variables you are

    aware may be unavailable:

     

      lint_ignore "lua_fltk_version"

      if lua_fltk_version then print("fltk loaded") end

     

    One way of defining these is in a module "declare.lua":

     

      function declare(s)

      end

      declare "lint_ignore"

      function lint_ignore(s)

      end

     

    (Setting declare is OK, because it's in the "declare" module.)  These

    functions don't have to do anything, or in fact actually exist!  They

    can be in dead code:

     

      if false then declare "xpairs" end

        

    This is because lualint only performs a rather primitive and cursory

    scan of the bytecode.  Perhaps declarations should only be allowed in

    the main chunk.

     

    TODO:

     

    The errors don't come out in any particular order.

     

    Should switch to Rici's parser, which should do a much better job of

    this, and allow detection of some other common situations.

     

    CREDITS:

     

    Jay Carlson (nop@nop.com)

     

    This is all Ben Jackson's (ben@ben.com) fault, who did some similar

    tricks in MOO.

     

    ]]

     

     

    local function Set(l) 

      local t = {}

      for _,v in ipairs(l) do

        t[v] = true

      end

      return t

    end

     

    local ignoreget = Set{

    "LUA_PATH", "_G", "_LOADED", "_TRACEBACK", "_VERSION", "__pow", "arg",

    "assert", "collectgarbage", "coroutine", "debug", "dofile", "error",

    "gcinfo", "getfenv", "getmetatable", "io", "ipairs", "loadfile",

    "loadlib", "loadstring", "math", "newproxy", "next", "os", "pairs",

    "pcall", "print", "rawequal", "rawget", "rawset", "require",

    "setfenv", "setmetatable", "string", "table", "tonumber", "tostring",

    "type", "unpack", "xpcall",

    }

     

    local function fileexists(fname)

      local f = io.open(fname)

      if f then 

        f:close()

        return true

      else

        return false

      end

    end

     

    -- borrowed from LTN11

    local function locate(name)

      local path = LUA_PATH

      if type(path) ~= "string" then

        path = os.getenv "LUA_PATH" or "./?.lua"

      end

      for path in string.gfind(path, "[^;]+") do

        path = string.gsub(path, "?", name)

        if fileexists(path) then

          return path

        end

      end

      return nil

    end

     

     

    local function scanfile(filename)

      local modules = {}

      local declared = {}

      local lint_ignored = {}

      local refs = {}

      local saw_last = nil

     

      local context, curfunc

     

      if not fileexists(filename) then

        return nil, "file "..filename.." does not exist"

      end

     

      -- Run once to see if it parses correctly

      

      if not os.execute("luac -o lualint.tmp "..filename) then

        return nil, "file "..filename.." did not successfully parse"

      end

     

      if not fileexists("lualint.tmp") then

        return nil, "file "..filename.." did not successfully parse"

      end

     

      assert(os.remove("lualint.tmp"))

     

      local bc = assert(io.popen("luac -l -p "..filename))

      

      for line in bc:lines() do

        -- main <examples/xhtml2wiki.lua:0> (64 instructions, 256 bytes at 0x805c1a0)

        -- function <examples/xhtml2wiki.lua:13> (6 instructions, 24 bytes at 0x805c438)

        local found, _, type, fname = string.find(line, "(%w+) <([^>]+)>")

        if found then 

          if context == "main" then fname="*MAIN*" end

          curfunc = fname

        end

        

        -- print("sawlast", saw_last)

        --  2 [1] LOADK     1 1 ; "lazytree"

        local found, _, constname = string.find(line, '%sLOADK .-;%s"(.-)"')

        if saw_last and found then

          if saw_last == "require" then

            -- print("require", constname)

            table.insert(modules, constname)

          elseif saw_last == "declare" then

            -- print("declare", constname)

            table.insert(declared, constname)

          elseif saw_last == "lint_ignore" then

            lint_ignored[constname] = true

          end

        end

        

        --  4 [2] GETGLOBAL 0 0 ; require

        local found, _, lineno, instr, gname = string.find(line, "%[(%d+)%]%s+([SG]ETGLOBAL).-; (.+)")

        if found then

          local t = refs[curfunc] or {SETGLOBAL={n=0}, GETGLOBAL={n=0}}

          local err = {name=gname, lineno=lineno}

          table.insert(t[instr], err)

          refs[curfunc] = t

          saw_last = gname

        else

          saw_last = nil

        end

      end

      bc:close()

      return modules, declared, lint_ignored, refs

    end

     

    local found_sets = false

    local found_gets = false

    local parse_failed = false

    local import_failed = true

     

    -- print("args", arg[1])

    local function lint(filename, relaxed)

      local modules, declared, lint_ignored, refs = scanfile(filename)

     

      if not modules then

        print(string.format("%s:%d: *** could not parse: %s ", filename, 1, declared))

        parse_failed = true

        return

      end

      

      local imported_declare_set = {}

      for i,module in ipairs(modules) do

        local path = locate(module)

        if not path then 

          print(string.format("%s:%d: could not find imported module %s ", filename, 1, module))

          import_failed = true

        else

          local success, imported_declare, _, _ = scanfile(path)

          if not success then

            print(string.format("%s:%d: could not parse import: %s ", path, 1, imported_declare))

            import_failed = true

          else

            for i,declared in ipairs(imported_declare) do

              imported_declare_set[declared] = true

            end

          end

        end

      end

        

      local moduleset = Set(modules)

      local declaredset = Set(declared)

      

      local self_name = nil

      do

        local _

        if string.find(filename, "/") then

          _, _, self_name = string.find(filename, ".-/(%w+)%.lua")

        else

          _,_,self_name = string.find(filename, "(%w+)%.lua")

        end

      end

      -- print("selfname", self_name)

     

      local was_set = {}

      

      local function will_warn_for(name)

        if relaxed and was_set[name] then

          return false

        end

        if name == self_name or

          lint_ignored[name] or

          ignoreget[name] or

          moduleset[name] or

          declaredset[name] or

          imported_declare_set[name] then

          return false

        end

        return true

      end

     

      for f,t in pairs(refs) do

        for _,r in ipairs(t.SETGLOBAL) do

          if r.name ~= self_name and not declaredset[r.name] then

            was_set[r.name] = true

            if not relaxed then

              if string.sub(r.name, 1, 2) ~= 'g_' then

                print(string.format("%s:%d: *** global SET of %s", filename, r.lineno, r.name))

              end

              found_sets = true

            end

          end

        end

      end

     

      for f,t in pairs(refs) do

        for _,r in ipairs(t.GETGLOBAL) do

          if will_warn_for(r.name) then

            if string.sub(r.name, 1, 2) ~= 'g_' then

              print(string.format("%s:%d: global get of %s", filename, r.lineno, r.name))

            end

            found_gets = true

          end

        end

      end

    end

     

    if arg.n == 0 then

      print("usage: lualint filename.lua [filename.lua ...]")

      os.exit(1)

    end

     

    local relaxed_mode = false

     

    for i,v in ipairs(arg) do

      if v == "-r" then

        relaxed_mode = true

      elseif v == "-s" then

        relaxed_mode = false

      else

        lint(v, relaxed_mode)

      end

    end

     

    if parse_failed then

      os.exit(3)

    elseif import_failed then

      os.exit(1)

    elseif found_sets then

      os.exit(2)

    elseif found_gets then

      os.exit(1)

    else

      os.exit(0)

    end

     

    728x90

    댓글

Designed by Tistory.