lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


In Lua 5.1.2, lua_getinfo (or debug.getinfo) does not fill the
lua_Debug.namewhat and lua_Debug.name fields for metamethod calls.  One
implication is that luaL_check* calls, which rely on this information, do not
give meaningful error messages when used in metamethods.

As an example,


/* test1.c */
#include <stdio.h>
#include "lua.h"
#include "lauxlib.h"

  static int
lnewindex(lua_State * L) {
  luaL_checknumber(L, 3);
  return 0;
}

  static int
test(lua_State * L) {
  lua_newtable(L); /* t */

  /* setmetatable(t, {__newindex=lnewindex}) */
  lua_newtable(L);
  lua_pushcfunction(L, lnewindex);
  lua_setfield(L, -2, "__newindex");
  lua_setmetatable(L, -2);

  /* t[key] = value */
  #if 1
  lua_pushstring(L, "x");
  lua_pushboolean(L, 1);
  lua_settable(L, -3);
  #endif

  /* alternative via Lua */
  #if 0
  luaL_loadstring(L, "local t=...; t.x = true");
  lua_pushvalue(L, -2);
  lua_call(L, 1, 0);
  #endif

  return 0;
}

int main() {
  lua_State * L = luaL_newstate();
  luaL_openlibs(L);

  if (lua_cpcall(L, test, NULL) != 0) {
    puts(lua_tostring(L, -1));
  }
  lua_close(L);
  return 0;
}

  $ ./test1
  bad argument #3 to '?' (number expected, got boolean)


As another example but in Lua,


-- test2.lua
local mt = {}
function mt:__newindex(k,v)
  local inf = debug.getinfo(1)
  print(inf.name, inf.namewhat, inf.func)
end
local t = setmetatable({}, mt)
t.x = 1

  $ lua test2.lua
  nil             function: 0x6862d0


The solution I thought was to patch ldebug.c getfuncname so that metamethod
calls result in namewhat having a new value "meta" and name having the key
passed to __newindex, or probably more generally the name of the metamethod. 
This is shown below for __newindex:


diff -ru lua-5.1.2/src/ldebug.c lua-5.1.2-test1/src/ldebug.c
--- lua-5.1.2/src/ldebug.c      2007-03-23 12:06:19.000000000 -0500
+++ lua-5.1.2-test1/src/ldebug.c        2007-11-03 16:36:36.656250000 -0500
@@ -534,6 +534,15 @@
   if (GET_OPCODE(i) == OP_CALL || GET_OPCODE(i) == OP_TAILCALL ||
       GET_OPCODE(i) == OP_TFORLOOP)
     return getobjname(L, ci, GETARG_A(i), name);
+
+  /* PATCHED */
+  else if (GET_OPCODE(i) == OP_SETTABLE) {
+    /* if (isLua(ci)) { TValue * key = (ci+1)->base + (2 - 1);
+       *name = ttisstring(key) ? svalue(key) : NULL; } */
+    *name = getstr(G(L)->tmname[TM_NEWINDEX]); /* __newindex */
+    return "meta";
+  } /* TODO: support other metamethods */
+
   else
     return NULL;  /* no useful name can be found */
 }


The metamethod __newindex is deduced as before from opcodes and could probably
be extended to support all other metamethods.

Furthermore, lauxlib.c luaL_argerror is patched to display this information:


diff -ru lua-5.1.2/src/lauxlib.c lua-5.1.2-test1/src/lauxlib.c
--- lua-5.1.2/src/lauxlib.c     2006-03-21 14:31:09.000000000 -0500
+++ lua-5.1.2-test1/src/lauxlib.c       2007-11-03 17:08:09.968750000 -0500
@@ -51,6 +51,23 @@
       return luaL_error(L, "calling " LUA_QS " on bad self (%s)",
                            ar.name, extramsg);
   }
+
+  /* PATCHED */
+  else if (strcmp(ar.namewhat, "meta") == 0) {
+    if (strcmp(ar.name, "__newindex") == 0) {
+      const char * fieldname;
+      lua_checkstack(L, 2);
+      lua_getglobal(L, "tostring"); /* ensure baselib loaded? */
+      lua_pushvalue(L, 2);
+      lua_call(L, 1, 1);
+      fieldname = lua_tostring(L, -1);
+      if (fieldname == NULL) fieldname = "?";
+      return luaL_error(L, "bad %s in accessing field %s (%s)",
+          narg==3 ? "value" : narg==2 ? "key" : narg==1 ? "object" : "?",
+          fieldname, extramsg);
+    } /* TODO: support other metamethods */
+  }
+
   if (ar.name == NULL)
     ar.name = "?";
   return luaL_error(L, "bad argument #%d to " LUA_QS " (%s)",


We now have


  $ ./test1
  bad argument #3 to '?' (number expected, got boolean)

  $ lua test2.lua
  __newindex meta    function: 0x6862d8


As seen, the second example now displays the required information, but the first
example is unchanged.  The problem is that this approach of querying opcodes
does not work when called from C.  However, if the #ifdef's in the first example
are changed so that the metamethod is called from Lua, we correctly get


  $ ./a
  [string "local t=...; t.x = true"]:1: bad value in accessing field x
  (number expected, got boolean)


Comments?  Improvements?  Is argument checking in metamethods typically done
without luaL_check* functions? should it be?