Skip to content

Commit

Permalink
A to-be-closed variable must have a closable value (or be nil)
Browse files Browse the repository at this point in the history
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.)
  • Loading branch information
roberto-ieru committed Nov 29, 2018
1 parent 7696c64 commit 6d04537
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 39 deletions.
2 changes: 1 addition & 1 deletion lapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
}
Expand Down
18 changes: 10 additions & 8 deletions ldebug.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -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;
}

Expand All @@ -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);
Expand All @@ -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 */
Expand Down
2 changes: 2 additions & 0 deletions ldebug.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions lfunc.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include "lua.h"

#include "ldebug.h"
#include "ldo.h"
#include "lfunc.h"
#include "lgc.h"
Expand Down Expand Up @@ -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' */
Expand Down
7 changes: 3 additions & 4 deletions lvm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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));
}
Expand Down
25 changes: 16 additions & 9 deletions manual/manual.of
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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{

Expand All @@ -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).
}

}
Expand All @@ -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.

}
Expand Down Expand Up @@ -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
Expand Down
21 changes: 14 additions & 7 deletions testes/api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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"
Expand Down
20 changes: 10 additions & 10 deletions testes/db.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))

Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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)
Expand Down
21 changes: 21 additions & 0 deletions testes/locals.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 6d04537

Please sign in to comment.