1
0
mirror of https://github.com/lua/lua.git synced 2025-01-14 05:43:00 +08:00

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.)
This commit is contained in:
Roberto Ierusalimschy 2018-11-29 16:02:44 -02:00
parent 7696c6474f
commit 6d04537ea6
9 changed files with 83 additions and 39 deletions

2
lapi.c
View File

@ -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 */
}

View File

@ -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 */

View File

@ -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,

View File

@ -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' */

7
lvm.c
View File

@ -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));
}

View File

@ -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

View File

@ -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"

View File

@ -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)

View File

@ -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