Getting Variables From Values

lua-users home
wiki

If we have a value, can we obtain its variable? For example, can a function return the names of the variables passed to it?

f(x,y) --> 'x', 'y'

Or can Lua support "pass-by-reference" (or maybe even "pass-by-name") calling semantics?

local a = 10; local b = 11

print(a,b) --> 10,11

swap(a,b)

print(a,b) --> 11,10

The answer is not normally. However, with some byte code inspection (using the lbci library [1]) and Lua debug library hackery, it may be possible...

Note first the bytecode in a typical function call:


$ echo -e 'local x = {2,3};\n local y = 4;\n f(x,y,z)' | luac -p -l -

main <stdin:0,0> (11 instructions, 44 bytes at 0x6d0ea8)

0+ params, 6 slots, 0 upvalues, 2 locals, 5 constants, 0 functions

        1       [1]     NEWTABLE        0 2 0

        2       [1]     LOADK           1 -1    ; 2

        3       [1]     LOADK           2 -2    ; 3

        4       [1]     SETLIST         0 2 1   ; 1

        5       [2]     LOADK           1 -3    ; 4

        6       [3]     GETGLOBAL       2 -4    ; f

        7       [3]     MOVE            3 0

        8       [3]     MOVE            4 1

        9       [3]     GETGLOBAL       5 -5    ; z

        10      [3]     CALL            2 4 1

        11      [3]     RETURN          0 1

The instructions prior to the CALL move the variables into place. Here's how we might make use of that:

-- D.Manura, 2009-10. Public domain.



require "bci"



-- Returns list of variables passed to function at given

-- stack level number `level`.  `level` defaults to 1, the calling

-- function, if omitted. If confused, returns nothing.

-- See code for format of variables.

-- WARNING: This code is experimental.  Not intended for production use.

local mt

local function getargvariables(level)

  mt = mt or {__tostring = function(t)

    local s = '{'

    for i=1,#t do

      s = s .. (i==1 and '' or ',') .. tostring(t[i])

    end

    s = s .. '}'

    return s

  end}



  -- Get function info.

  level = (level or 1) + 2

  local f = debug.getinfo(level,'f').func

  if not f then return end -- could be a tail call

  local currentline = debug.getinfo(level,'l').currentline

  local F = inspector.getheader(f)



  -- Get instruction pointer of call.

  -- Unfortunately, we only have the line number from which to infer the

  -- instruction pointer.  So, we can only do this unambiguously when the

  -- call is the only call on its line.  Perhaps bci or debug.getinfo

  -- could be patched to return the exact instruction pointer.

  local count = 0

  local currentip

  for i=1,F.instructions do

    local line, opcode, a, b, c = inspector.getinstruction(f,i)

    if line == currentline and opcode == "CALL" then

      currentip = i

      count = count + 1

    end

  end

  if count ~= 1 then return end -- ambiguous, return nothing



  -- Get CALL opcode data

  local _,_,idxfunc,nparamsp,_ = inspector.getinstruction(f,currentip)



  -- Get arguments.

  local names = {}

  for i=1,nparamsp-1 do

    local _,opcode,a,b,c = inspector.getinstruction(f,currentip-i)

    if opcode == 'MOVE' and a == idxfunc + nparamsp - i then -- local

      local varname,_,_ = inspector.getlocal(f,b+1)

      names[nparamsp - i] = setmetatable({'local', varname, b+1}, mt)

    elseif opcode == 'GETGLOBAL' and b < 0 then -- global

      local varname = inspector.getconstant(f,-b)

      names[nparamsp - i] = setmetatable({'global', varname}, mt)

    else

      return -- other possibilities not currently implemented

    end

  end

  return unpack(names, 1, nparamsp-1)

end



-- Set variable `var` (as returned by `getargvariables`) in context of

-- stack level number `level` to value `value`.

local function setvariable(level, var, value)

  level = (level or 1) + 1

  if var[1] == 'local' then

    local varname, idx = var[2], var[3]

    debug.setlocal(level, idx, value)

  elseif var[1] == 'global' then

    local varname = var[2]

    getfenv(2)[varname] = value

  else

    assert(false)

  end

end



-- TESTS



local function f(a,b)

  print(getargvariables()) --> {local,y,5} {local,z,6} {global,w}

end



do

  local y=1

  local z=2

  f(y,z,w)

end



local function swap(x,y)

  local xvar, yvar = getargvariables()

  assert(xvar and yvar)

  setvariable(2, xvar, y)

  setvariable(2, yvar, x)

end



a = 10

local b = 11

print(a,b) --> 10,11

swap(a,b)

print(a,b) --> 11,10



print 'DONE'

As is, the above is not intended for production use. The above could be generalized further. Please do so if you are so inclined.

--DavidManura

See Also


RecentChanges · preferences
edit · history
Last edited October 9, 2009 4:56 am GMT (diff)