From 4bd10b6fe81c0a56eb9e01e24fba10e655966870 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 29 Dec 2020 13:15:54 -0300 Subject: [PATCH] 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. --- ldebug.c | 23 +++++++++++++++++------ ldebug.h | 1 + ldo.c | 2 +- testes/db.lua | 6 ++++-- testes/errors.lua | 14 +++++++++++++- testes/locals.lua | 17 ++++++++++++++++- 6 files changed, 52 insertions(+), 11 deletions(-) diff --git a/ldebug.c b/ldebug.c index 8cb00e51..819550d7 100644 --- a/ldebug.c +++ b/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)); diff --git a/ldebug.h b/ldebug.h index a0a58486..55b3ae09 100644 --- a/ldebug.h +++ b/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, diff --git a/ldo.c b/ldo.c index d39edab0..5e3828f4 100644 --- a/ldo.c +++ b/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 */ diff --git a/testes/db.lua b/testes/db.lua index fdb0da4a..ce559ad9 100644 --- a/testes/db.lua +++ b/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 diff --git a/testes/errors.lua b/testes/errors.lua index a3f07021..4249f570 100644 --- a/testes/errors.lua +++ b/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'") diff --git a/testes/locals.lua b/testes/locals.lua index 1b43609b..add023ca 100644 --- a/testes/locals.lua +++ b/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 = func2close(function (_, msg) + assert(string.find(msg, "number value")) + error(12) + end) + local a2 = setmetatable({}, {__close = print}) + local a3 = 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