From 6d04537ea660fd12fc16c328366b701fabaf4919 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 29 Nov 2018 16:02:44 -0200 Subject: [PATCH] A to-be-closed variable must have a closable value (or be nil) It is an error for a to-be-closed variable to have a non-closable non-nil value when it is being closed. This situation does not seem to be useful and often hints to an error. (Particularly in the C API, it is easy to change a to-be-closed index by mistake.) --- lapi.c | 2 +- ldebug.c | 18 ++++++++++-------- ldebug.h | 2 ++ lfunc.c | 6 ++++++ lvm.c | 7 +++---- manual/manual.of | 25 ++++++++++++++++--------- testes/api.lua | 21 ++++++++++++++------- testes/db.lua | 20 ++++++++++---------- testes/locals.lua | 21 +++++++++++++++++++++ 9 files changed, 83 insertions(+), 39 deletions(-) diff --git a/lapi.c b/lapi.c index 147ed0ff0..9cabe7ca4 100644 --- a/lapi.c +++ b/lapi.c @@ -1299,7 +1299,7 @@ static const char *aux_upvalue (TValue *fi, int n, TValue **val, *val = f->upvals[n-1]->v; if (owner) *owner = obj2gco(f->upvals[n - 1]); name = p->upvalues[n-1].name; - return (name == NULL) ? "(*no name)" : getstr(name); + return (name == NULL) ? "(no name)" : getstr(name); } default: return NULL; /* not a closure */ } diff --git a/ldebug.c b/ldebug.c index 766a52319..bd471e0c0 100644 --- a/ldebug.c +++ b/ldebug.c @@ -192,15 +192,14 @@ static const char *findvararg (CallInfo *ci, int n, StkId *pos) { int nextra = ci->u.l.nextraargs; if (n <= nextra) { *pos = ci->func - nextra + (n - 1); - return "(*vararg)"; /* generic name for any vararg */ + return "(vararg)"; /* generic name for any vararg */ } } return NULL; /* no such vararg */ } -static const char *findlocal (lua_State *L, CallInfo *ci, int n, - StkId *pos) { +const char *luaG_findlocal (lua_State *L, CallInfo *ci, int n, StkId *pos) { StkId base = ci->func + 1; const char *name = NULL; if (isLua(ci)) { @@ -211,12 +210,15 @@ static const char *findlocal (lua_State *L, CallInfo *ci, int n, } if (name == NULL) { /* no 'standard' name? */ StkId limit = (ci == L->ci) ? L->top : ci->next->func; - if (limit - base >= n && n > 0) /* is 'n' inside 'ci' stack? */ - name = "(*temporary)"; /* generic name for any valid slot */ + if (limit - base >= n && n > 0) { /* is 'n' inside 'ci' stack? */ + /* generic name for any valid slot */ + name = isLua(ci) ? "(temporary)" : "(C temporary)"; + } else return NULL; /* no name */ } - *pos = base + (n - 1); + if (pos) + *pos = base + (n - 1); return name; } @@ -232,7 +234,7 @@ LUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n) { } else { /* active function; get information through 'ar' */ StkId pos = NULL; /* to avoid warnings */ - name = findlocal(L, ar->i_ci, n, &pos); + name = luaG_findlocal(L, ar->i_ci, n, &pos); if (name) { setobjs2s(L, L->top, pos); api_incr_top(L); @@ -247,7 +249,7 @@ LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n) { StkId pos = NULL; /* to avoid warnings */ const char *name; lua_lock(L); - name = findlocal(L, ar->i_ci, n, &pos); + name = luaG_findlocal(L, ar->i_ci, n, &pos); if (name) { setobjs2s(L, pos, L->top - 1); L->top--; /* pop value */ diff --git a/ldebug.h b/ldebug.h index f080711d5..1fe0efab0 100644 --- a/ldebug.h +++ b/ldebug.h @@ -22,6 +22,8 @@ #define ABSLINEINFO (-0x80) LUAI_FUNC int luaG_getfuncline (const Proto *f, int pc); +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_forerror (lua_State *L, const TValue *o, diff --git a/lfunc.c b/lfunc.c index aa6ce58f1..11d2850f7 100644 --- a/lfunc.c +++ b/lfunc.c @@ -14,6 +14,7 @@ #include "lua.h" +#include "ldebug.h" #include "ldo.h" #include "lfunc.h" #include "lgc.h" @@ -140,6 +141,11 @@ static int closeupval (lua_State *L, TValue *uv, StkId level, int status) { if (likely(status == LUA_OK)) { if (prepclosingmethod(L, uv, &G(L)->nilvalue)) /* something to call? */ callclose(L, NULL); /* call closing method */ + else if (!ttisnil(uv)) { /* non-closable non-nil value? */ + const char *vname = luaG_findlocal(L, L->ci, level - L->ci->func, NULL); + if (vname == NULL) vname = "?"; + luaG_runerror(L, "attempt to close non-closable variable '%s'", vname); + } } else { /* there was an error */ /* save error message and set stack top to 'level + 1' */ diff --git a/lvm.c b/lvm.c index a6dcc9ffe..50d967c6b 100644 --- a/lvm.c +++ b/lvm.c @@ -1427,7 +1427,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) { } vmcase(OP_CLOSE) { L->top = ra + 1; /* everything is free after this slot */ - ProtectNT(luaF_close(L, ra, LUA_OK)); + Protect(luaF_close(L, ra, LUA_OK)); vmbreak; } vmcase(OP_TBC) { @@ -1717,9 +1717,8 @@ void luaV_execute (lua_State *L, CallInfo *ci) { vmbreak; } vmcase(OP_TFORPREP) { - /* is 'toclose' a function or has a '__close' metamethod? */ - if (ttisfunction(s2v(ra + 3)) || - !ttisnil(luaT_gettmbyobj(L, s2v(ra + 3), TM_CLOSE))) { + /* is 'toclose' not nil? */ + if (!ttisnil(s2v(ra + 3))) { /* create to-be-closed upvalue for it */ halfProtect(luaF_newtbcupval(L, ra + 3)); } diff --git a/manual/manual.of b/manual/manual.of index f891c33e9..3902f2f35 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -1063,11 +1063,16 @@ which start with @T{0x} or @T{0X}. Hexadecimal constants also accept an optional fractional part plus an optional binary exponent, marked by a letter @Char{p} or @Char{P}. + A numeric constant with a radix point or an exponent denotes a float; otherwise, -if its value fits in an integer, -it denotes an integer. +if its value fits in an integer or it is a hexadecimal constant, +it denotes an integer; +otherwise (that is, a decimal integer numeral that overflows), +it denotes a float. +(Hexadecimal integer numerals that overflow @emph{wrap around}; +they always denote an integer value.) Examples of valid integer constants are @verbatim{ 3 345 0xff 0xBEBADA @@ -1542,7 +1547,8 @@ If the value of the variable when it goes out of scope is a function, that function is called; otherwise, if the value has a @idx{__close} metamethod, that metamethod is called; -otherwise, nothing is done. +otherwise, if the value is @nil, nothing is done; +otherwise, an error is raised. In the function case, if the scope is being closed by an error, the error object is passed as an argument to the function; @@ -1665,7 +1671,7 @@ If both operands are integers, the operation is performed over integers and the result is an integer. Otherwise, if both operands are numbers, then they are converted to floats, -the operation is performed following the usual rules +the operation is performed following the machine's rules for floating-point arithmetic (usually the @x{IEEE 754} standard), and the result is a float. @@ -4998,7 +5004,7 @@ This call leaves the final string on the top of the stack. } -If you know beforehand the total size of the resulting string, +If you know beforehand the maximum size of the resulting string, you can use the buffer like this: @itemize{ @@ -5012,7 +5018,8 @@ size @id{sz} with a call @T{luaL_buffinitsize(L, &b, sz)}.} @item{ Finish by calling @T{luaL_pushresultsize(&b, sz)}, where @id{sz} is the total size of the resulting string -copied into that space. +copied into that space (which may be smaller than or +equal to the preallocated size). } } @@ -5028,8 +5035,8 @@ when you call a buffer operation, the stack is at the same level it was immediately after the previous buffer operation. (The only exception to this rule is @Lid{luaL_addvalue}.) -After calling @Lid{luaL_pushresult} the stack is back to its -level when the buffer was initialized, +After calling @Lid{luaL_pushresult}, +the stack is back to its level when the buffer was initialized, plus the final string on its top. } @@ -7118,7 +7125,7 @@ empty string as a match immediately after another match. As an example, consider the results of the following code: @verbatim{ -> string.gsub("abc", "()a*()", print) +> string.gsub("abc", "()a*()", print); --> 1 2 --> 3 3 --> 4 4 diff --git a/testes/api.lua b/testes/api.lua index ed857fd05..6f35e1324 100644 --- a/testes/api.lua +++ b/testes/api.lua @@ -366,7 +366,7 @@ do -- "argerror" without frames assert(T.checkpanic("loadstring 4") == "bad argument #4 (string expected, got no value)") - + -- memory error T.totalmem(T.totalmem()+10000) -- set low memory limit (+10k) @@ -987,12 +987,12 @@ do local a, b = T.testC([[ call 0 1 # create resource - pushint 34 + pushnil toclose -2 # mark call result to be closed - toclose -1 # mark number to be closed (will be ignored) + toclose -1 # mark nil to be closed (will be ignored) return 2 ]], newresource) - assert(a[1] == 11 and b == 34) + assert(a[1] == 11 and b == nil) assert(#openresource == 0) -- was closed -- repeat the test, but calling function in a 'multret' context @@ -1005,7 +1005,7 @@ do assert(#openresource == 0) -- was closed -- error - local a, b = pcall(T.testC, [[ + local a, b = pcall(T.makeCfunc[[ call 0 1 # create resource toclose -1 # mark it to be closed error # resource is the error object @@ -1038,6 +1038,13 @@ do ]], newresource, check) assert(a == 3) -- no extra items left in the stack + -- non-closable value + local a, b = pcall(T.makeCfunc[[ + pushint 32 + toclose -1 + ]]) + assert(not a and string.find(b, "(C temporary)")) + end @@ -1249,9 +1256,9 @@ do -- closing state with no extra memory T.closestate(L) T.alloccount() end - + do -- garbage collection with no extra memory - local L = T.newstate() + local L = T.newstate() T.loadlib(L) local res = (T.doremote(L, [[ _ENV = require"_G" diff --git a/testes/db.lua b/testes/db.lua index 5b243c399..976962b02 100644 --- a/testes/db.lua +++ b/testes/db.lua @@ -214,14 +214,14 @@ local function foo (a, ...) local t = table.pack(...) for i = 1, t.n do local n, v = debug.getlocal(1, -i) - assert(n == "(*vararg)" and v == t[i]) + assert(n == "(vararg)" and v == t[i]) end assert(not debug.getlocal(1, -(t.n + 1))) assert(not debug.setlocal(1, -(t.n + 1), 30)) if t.n > 0 then (function (x) - assert(debug.setlocal(2, -1, x) == "(*vararg)") - assert(debug.setlocal(2, -t.n, x) == "(*vararg)") + assert(debug.setlocal(2, -1, x) == "(vararg)") + assert(debug.setlocal(2, -t.n, x) == "(vararg)") end)(430) assert(... == 430) end @@ -328,9 +328,9 @@ assert(a[f] and a[g] and a[assert] and a[debug.getlocal] and not a[print]) -- tests for manipulating non-registered locals (C and Lua temporaries) local n, v = debug.getlocal(0, 1) -assert(v == 0 and n == "(*temporary)") +assert(v == 0 and n == "(C temporary)") local n, v = debug.getlocal(0, 2) -assert(v == 2 and n == "(*temporary)") +assert(v == 2 and n == "(C temporary)") assert(not debug.getlocal(0, 3)) assert(not debug.getlocal(0, 0)) @@ -607,7 +607,7 @@ co = load[[ local a = 0 -- 'A' should be visible to debugger only after its complete definition debug.sethook(function (e, l) - if l == 3 then a = a + 1; assert(debug.getlocal(2, 1) == "(*temporary)") + if l == 3 then a = a + 1; assert(debug.getlocal(2, 1) == "(temporary)") elseif l == 4 then a = a + 1; assert(debug.getlocal(2, 1) == "A") end end, "l") @@ -875,15 +875,15 @@ local debug = require'debug' local a = 12 -- a local variable local n, v = debug.getlocal(1, 1) -assert(n == "(*temporary)" and v == debug) -- unkown name but known value +assert(n == "(temporary)" and v == debug) -- unkown name but known value n, v = debug.getlocal(1, 2) -assert(n == "(*temporary)" and v == 12) -- unkown name but known value +assert(n == "(temporary)" and v == 12) -- unkown name but known value -- a function with an upvalue local f = function () local x; return a end n, v = debug.getupvalue(f, 1) -assert(n == "(*no name)" and v == 12) -assert(debug.setupvalue(f, 1, 13) == "(*no name)") +assert(n == "(no name)" and v == 12) +assert(debug.setupvalue(f, 1, 13) == "(no name)") assert(a == 13) local t = debug.getinfo(f) diff --git a/testes/locals.lua b/testes/locals.lua index 90a8b8456..24681dd9e 100644 --- a/testes/locals.lua +++ b/testes/locals.lua @@ -266,6 +266,27 @@ do -- errors in __close end +do + + -- errors due to non-closable values + local function foo () + local *toclose x = 34 + end + local stat, msg = pcall(foo) + assert(not stat and string.find(msg, "variable 'x'")) + + + -- with other errors, non-closable values are ignored + local function foo () + local *toclose x = 34 + local *toclose y = function () error(32) end + end + local stat, msg = pcall(foo) + assert(not stat and msg == 32) + +end + + if rawget(_G, "T") then -- memory error inside closing function