1
0
mirror of https://github.com/lua/lua.git synced 2025-02-04 06:13:04 +08:00

Debug information about extra arguments from __call

'debug.getinfo' can return number of extra arguments added to a call by
a chain of __call metavalues. That information is being used to improve
error messages about errors in these extra arguments.
This commit is contained in:
Roberto Ierusalimschy 2024-11-19 14:09:18 -03:00
parent b117bdb344
commit 50c7c915ee
9 changed files with 83 additions and 12 deletions

View File

@ -170,19 +170,27 @@ LUALIB_API void luaL_traceback (lua_State *L, lua_State *L1,
LUALIB_API int luaL_argerror (lua_State *L, int arg, const char *extramsg) { LUALIB_API int luaL_argerror (lua_State *L, int arg, const char *extramsg) {
lua_Debug ar; lua_Debug ar;
const char *argword;
if (!lua_getstack(L, 0, &ar)) /* no stack frame? */ if (!lua_getstack(L, 0, &ar)) /* no stack frame? */
return luaL_error(L, "bad argument #%d (%s)", arg, extramsg); return luaL_error(L, "bad argument #%d (%s)", arg, extramsg);
lua_getinfo(L, "n", &ar); lua_getinfo(L, "nt", &ar);
if (strcmp(ar.namewhat, "method") == 0) { if (arg <= ar.extraargs) /* error in an extra argument? */
arg--; /* do not count 'self' */ argword = "extra argument";
if (arg == 0) /* error is in the self argument itself? */ else {
arg -= ar.extraargs; /* do not count extra arguments */
if (strcmp(ar.namewhat, "method") == 0) { /* colon syntax? */
arg--; /* do not count (extra) self argument */
if (arg == 0) /* error in self argument? */
return luaL_error(L, "calling '%s' on bad self (%s)", return luaL_error(L, "calling '%s' on bad self (%s)",
ar.name, extramsg); ar.name, extramsg);
/* else go through; error in a regular argument */
}
argword = "argument";
} }
if (ar.name == NULL) if (ar.name == NULL)
ar.name = (pushglobalfuncname(L, &ar)) ? lua_tostring(L, -1) : "?"; ar.name = (pushglobalfuncname(L, &ar)) ? lua_tostring(L, -1) : "?";
return luaL_error(L, "bad argument #%d to '%s' (%s)", return luaL_error(L, "bad %s #%d to '%s' (%s)",
arg, ar.name, extramsg); argword, arg, ar.name, extramsg);
} }

View File

@ -191,8 +191,10 @@ static int db_getinfo (lua_State *L) {
settabsi(L, "ftransfer", ar.ftransfer); settabsi(L, "ftransfer", ar.ftransfer);
settabsi(L, "ntransfer", ar.ntransfer); settabsi(L, "ntransfer", ar.ntransfer);
} }
if (strchr(options, 't')) if (strchr(options, 't')) {
settabsb(L, "istailcall", ar.istailcall); settabsb(L, "istailcall", ar.istailcall);
settabsi(L, "extraargs", ar.extraargs);
}
if (strchr(options, 'L')) if (strchr(options, 'L'))
treatstackoption(L, L1, "activelines"); treatstackoption(L, L1, "activelines");
if (strchr(options, 'f')) if (strchr(options, 'f'))

View File

@ -352,7 +352,15 @@ static int auxgetinfo (lua_State *L, const char *what, lua_Debug *ar,
break; break;
} }
case 't': { case 't': {
ar->istailcall = (ci != NULL && (ci->callstatus & CIST_TAIL)); if (ci != NULL) {
ar->istailcall = !!(ci->callstatus & CIST_TAIL);
ar->extraargs =
cast_uchar((ci->callstatus & MAX_CCMT) >> CIST_CCMT);
}
else {
ar->istailcall = 0;
ar->extraargs = 0;
}
break; break;
} }
case 'n': { case 'n': {

View File

@ -1900,6 +1900,10 @@ static struct X { int x; } x;
else if EQ("closeslot") { else if EQ("closeslot") {
lua_closeslot(L1, getnum); lua_closeslot(L1, getnum);
} }
else if EQ("argerror") {
int arg = getnum;
luaL_argerror(L1, arg, getstring);
}
else luaL_error(L, "unknown instruction %s", buff); else luaL_error(L, "unknown instruction %s", buff);
} }
return 0; return 0;

1
lua.h
View File

@ -504,6 +504,7 @@ struct lua_Debug {
unsigned char nups; /* (u) number of upvalues */ unsigned char nups; /* (u) number of upvalues */
unsigned char nparams;/* (u) number of parameters */ unsigned char nparams;/* (u) number of parameters */
char isvararg; /* (u) */ char isvararg; /* (u) */
unsigned char extraargs; /* (t) number of extra arguments */
char istailcall; /* (t) */ char istailcall; /* (t) */
int ftransfer; /* (r) index of first value transferred */ int ftransfer; /* (r) index of first value transferred */
int ntransfer; /* (r) number of transferred values */ int ntransfer; /* (r) number of transferred values */

View File

@ -4850,6 +4850,7 @@ typedef struct lua_Debug {
unsigned char nups; /* (u) number of upvalues */ unsigned char nups; /* (u) number of upvalues */
unsigned char nparams; /* (u) number of parameters */ unsigned char nparams; /* (u) number of parameters */
char isvararg; /* (u) */ char isvararg; /* (u) */
unsigned char extraargs; /* (t) number of extra arguments */
char istailcall; /* (t) */ char istailcall; /* (t) */
int ftransfer; /* (r) index of first value transferred */ int ftransfer; /* (r) index of first value transferred */
int ntransfer; /* (r) number of transferred values */ int ntransfer; /* (r) number of transferred values */
@ -4938,6 +4939,14 @@ true if this function invocation was called by a tail call.
In this case, the caller of this level is not in the stack. In this case, the caller of this level is not in the stack.
} }
@item{@id{extraargs}|
The number of extra arguments added by the call
to functions called through @idx{__call} metamethods.
(Each @idx{__call} metavalue adds a single extra argument,
the object being called,
but there may be a chain of @idx{__call} metavalues.)
}
@item{@id{nups}| @item{@id{nups}|
the number of upvalues of the function. the number of upvalues of the function.
} }
@ -5045,7 +5054,7 @@ fills in the fields @id{source}, @id{short_src},
@id{linedefined}, @id{lastlinedefined}, and @id{what}; @id{linedefined}, @id{lastlinedefined}, and @id{what};
} }
@item{@Char{t}| fills in the field @id{istailcall}; @item{@Char{t}| fills in the fields @id{istailcall} and @id{extraargs};
} }
@item{@Char{u}| fills in the fields @item{@Char{u}| fills in the fields
@ -7993,7 +8002,7 @@ returns @fail plus the position of the first invalid byte.
@LibEntry{utf8.offset (s, n [, i])| @LibEntry{utf8.offset (s, n [, i])|
Returns the the position of the @id{n}-th character of @id{s} Returns the position of the @id{n}-th character of @id{s}
(counting from byte position @id{i}) as two integers: (counting from byte position @id{i}) as two integers:
The index (in bytes) where its encoding starts and the The index (in bytes) where its encoding starts and the
index (in bytes) where it ends. index (in bytes) where it ends.

View File

@ -204,6 +204,17 @@ do print"testing chains of '__call'"
assert(Res[i][1] == i) assert(Res[i][1] == i)
end end
assert(Res[N + 1] == "a" and Res[N + 2] == "b" and Res[N + 3] == "c") assert(Res[N + 1] == "a" and Res[N + 2] == "b" and Res[N + 3] == "c")
local function u (...)
local n = debug.getinfo(1, 't').extraargs
assert(select("#", ...) == n)
return n
end
for i = 0, N do
assert(u() == i)
u = setmetatable({}, {__call = u})
end
end end

View File

@ -624,6 +624,9 @@ local function f (x)
end end
end end
assert(debug.getinfo(print, 't').istailcall == false)
assert(debug.getinfo(print, 't').extraargs == 0)
function g(x) return f(x) end function g(x) return f(x) end
function g1(x) g(x) end function g1(x) g(x) end

View File

@ -117,6 +117,31 @@ else
return 1 return 1
]] ]]
assert(string.find(res, "xuxu.-main chunk")) assert(string.find(res, "xuxu.-main chunk"))
do -- tests for error messages about extra arguments from __call
local function createobj (n)
-- function that raises an error on its n-th argument
local code = string.format("argerror %d 'msg'", n)
local func = T.makeCfunc(code)
-- create a chain of 2 __call objects
local M = setmetatable({}, {__call = func})
M = setmetatable({}, {__call = M})
-- put it as a method for a new object
return {foo = M}
end
_G.a = createobj(1) -- error in first (extra) argument
checkmessage("a:foo()", "bad extra argument #1")
_G.a = createobj(2) -- error in second (extra) argument
checkmessage("a:foo()", "bad extra argument #2")
_G.a = createobj(3) -- error in self (after two extra arguments)
checkmessage("a:foo()", "bad self")
_G.a = createobj(4) -- error in first regular argument (after self)
checkmessage("a:foo()", "bad argument #1")
end
end end