Ver Fonte

Better error messages for calling non-callable objects

When available, use the calling code to find a suitable name for what
was being called; this is particularly useful for errors of non-callable
metamethods. This commit also improved the debug information for
order metamethods.
Roberto Ierusalimschy há 4 anos atrás
pai
commit
4bd10b6fe8
6 ficheiros alterados com 52 adições e 11 exclusões
  1. 17 6
      ldebug.c
  2. 1 0
      ldebug.h
  3. 1 1
      ldo.c
  4. 4 2
      testes/db.lua
  5. 13 1
      testes/errors.lua
  6. 16 1
      testes/locals.lua

+ 17 - 6
ldebug.c

@@ -629,12 +629,10 @@ static const char *funcnamefromcode (lua_State *L, CallInfo *ci,
     case OP_LEN: tm = TM_LEN; break;
     case OP_CONCAT: tm = TM_CONCAT; break;
     case OP_EQ: tm = TM_EQ; break;
-    case OP_LT: case OP_LE: case OP_LTI: case OP_LEI:
-      *name = "order";  /* '<=' can call '__lt', etc. */
-      return "metamethod";
-    case OP_CLOSE: case OP_RETURN:
-      *name = "close";
-      return "metamethod";
+    /* no cases for OP_EQI and OP_EQK, as they don't call metamethods */
+    case OP_LT: case OP_LTI: case OP_GTI: tm = TM_LT; break;
+    case OP_LE: case OP_LEI: case OP_GEI: tm = TM_LE; break;
+    case OP_CLOSE: case OP_RETURN: tm = TM_CLOSE; break;
     default:
       return NULL;  /* cannot find a reasonable name */
   }
@@ -697,6 +695,19 @@ l_noret luaG_typeerror (lua_State *L, const TValue *o, const char *op) {
 }
 
 
+l_noret luaG_callerror (lua_State *L, const TValue *o) {
+  CallInfo *ci = L->ci;
+  const char *name = NULL;  /* to avoid warnings */
+  const char *what = (isLua(ci)) ? funcnamefromcode(L, ci, &name) : NULL;
+  if (what != NULL) {
+    const char *t = luaT_objtypename(L, o);
+    luaG_runerror(L, "%s '%s' is not callable (a %s value)", what, name, t);
+  }
+  else
+    luaG_typeerror(L, o, "call");
+}
+
+
 l_noret luaG_forerror (lua_State *L, const TValue *o, const char *what) {
   luaG_runerror(L, "bad 'for' %s (number expected, got %s)",
                    what, luaT_objtypename(L, o));

+ 1 - 0
ldebug.h

@@ -31,6 +31,7 @@ LUAI_FUNC const char *luaG_findlocal (lua_State *L, CallInfo *ci, int n,
                                                     StkId *pos);
 LUAI_FUNC l_noret luaG_typeerror (lua_State *L, const TValue *o,
                                                 const char *opname);
+LUAI_FUNC l_noret luaG_callerror (lua_State *L, const TValue *o);
 LUAI_FUNC l_noret luaG_forerror (lua_State *L, const TValue *o,
                                                const char *what);
 LUAI_FUNC l_noret luaG_concaterror (lua_State *L, const TValue *p1,

+ 1 - 1
ldo.c

@@ -372,7 +372,7 @@ void luaD_tryfuncTM (lua_State *L, StkId func) {
   const TValue *tm = luaT_gettmbyobj(L, s2v(func), TM_CALL);
   StkId p;
   if (unlikely(ttisnil(tm)))
-    luaG_typeerror(L, s2v(func), "call");  /* nothing to call */
+    luaG_callerror(L, s2v(func));  /* nothing to call */
   for (p = L->top; p > func; p--)  /* open space for metamethod */
     setobjs2s(L, p, p-1);
   L->top++;  /* stack space pre-allocated by the caller */

+ 4 - 2
testes/db.lua

@@ -823,8 +823,10 @@ assert(a + 30000 == "add" and a - 3.0 == "sub" and a * 3.0 == "mul" and
        -a == "unm" and #a == "len" and a & 3 == "band")
 assert(a|3 == "bor" and 3~a == "bxor" and a<<3 == "shl" and a>>1 == "shr")
 assert (a==b and a.op == "eq")
-assert (a>=b and a.op == "order")
-assert (a>b and a.op == "order")
+assert (a>=b and a.op == "le")
+assert ("x">=a and a.op == "le")
+assert (a>b and a.op == "lt")
+assert (a>10 and a.op == "lt")
 assert(~a == "bnot")
 
 do   -- testing for-iterator name

+ 13 - 1
testes/errors.lua

@@ -24,8 +24,9 @@ local function doit (s)
 end
 
 
-local function checkmessage (prog, msg)
+local function checkmessage (prog, msg, debug)
   local m = doit(prog)
+  if debug then print(m) end
   assert(string.find(m, msg, 1, true))
 end
 
@@ -120,6 +121,17 @@ assert(not string.find(doit"a={13}; local bbbb=1; a[bbbb](3)", "'bbbb'"))
 checkmessage("a={13}; local bbbb=1; a[bbbb](3)", "number")
 checkmessage("a=(1)..{}", "a table value")
 
+-- calls
+checkmessage("local a; a(13)", "local 'a'")
+checkmessage([[
+  local a = setmetatable({}, {__add = 34})
+  a = a + 1
+]], "metamethod 'add'")
+checkmessage([[
+  local a = setmetatable({}, {__lt = {}})
+  a = a > a
+]], "metamethod 'lt'")
+
 -- tail calls
 checkmessage("local a={}; return a.bbbb(3)", "field 'bbbb'")
 checkmessage("a={}; do local a=1 end; return a:bbbb(3)", "method 'bbbb'")

+ 16 - 1
testes/locals.lua

@@ -459,7 +459,22 @@ do   -- errors due to non-closable values
     getmetatable(xyz).__close = nil   -- remove metamethod
   end
   local stat, msg = pcall(foo)
-  assert(not stat and string.find(msg, "attempt to call a nil value"))
+  assert(not stat and string.find(msg, "metamethod 'close'"))
+
+  local function foo ()
+    local a1 <close> = func2close(function (_, msg)
+      assert(string.find(msg, "number value"))
+      error(12)
+    end)
+    local a2 <close> = setmetatable({}, {__close = print})
+    local a3 <close> = func2close(function (_, msg)
+      assert(msg == nil)
+      error(123)
+    end)
+    getmetatable(a2).__close = 4   -- invalidate metamethod
+  end
+  local stat, msg = pcall(foo)
+  assert(not stat and msg == 12)
 end