Sfoglia il codice sorgente

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.)
Roberto Ierusalimschy 6 anni fa
parent
commit
6d04537ea6
9 ha cambiato i file con 83 aggiunte e 39 eliminazioni
  1. 1 1
      lapi.c
  2. 10 8
      ldebug.c
  3. 2 0
      ldebug.h
  4. 6 0
      lfunc.c
  5. 3 4
      lvm.c
  6. 16 9
      manual/manual.of
  7. 14 7
      testes/api.lua
  8. 10 10
      testes/db.lua
  9. 21 0
      testes/locals.lua

+ 1 - 1
lapi.c

@@ -1299,7 +1299,7 @@ static const char *aux_upvalue (TValue *fi, int n, TValue **val,
       *val = f->upvals[n-1]->v;
       *val = f->upvals[n-1]->v;
       if (owner) *owner = obj2gco(f->upvals[n - 1]);
       if (owner) *owner = obj2gco(f->upvals[n - 1]);
       name = p->upvalues[n-1].name;
       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 */
     default: return NULL;  /* not a closure */
   }
   }

+ 10 - 8
ldebug.c

@@ -192,15 +192,14 @@ static const char *findvararg (CallInfo *ci, int n, StkId *pos) {
     int nextra = ci->u.l.nextraargs;
     int nextra = ci->u.l.nextraargs;
     if (n <= nextra) {
     if (n <= nextra) {
       *pos = ci->func - nextra + (n - 1);
       *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 */
   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;
   StkId base = ci->func + 1;
   const char *name = NULL;
   const char *name = NULL;
   if (isLua(ci)) {
   if (isLua(ci)) {
@@ -211,12 +210,15 @@ static const char *findlocal (lua_State *L, CallInfo *ci, int n,
   }
   }
   if (name == NULL) {  /* no 'standard' name? */
   if (name == NULL) {  /* no 'standard' name? */
     StkId limit = (ci == L->ci) ? L->top : ci->next->func;
     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
     else
       return NULL;  /* no name */
       return NULL;  /* no name */
   }
   }
-  *pos = base + (n - 1);
+  if (pos)
+    *pos = base + (n - 1);
   return name;
   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' */
   else {  /* active function; get information through 'ar' */
     StkId pos = NULL;  /* to avoid warnings */
     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) {
     if (name) {
       setobjs2s(L, L->top, pos);
       setobjs2s(L, L->top, pos);
       api_incr_top(L);
       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 */
   StkId pos = NULL;  /* to avoid warnings */
   const char *name;
   const char *name;
   lua_lock(L);
   lua_lock(L);
-  name = findlocal(L, ar->i_ci, n, &pos);
+  name = luaG_findlocal(L, ar->i_ci, n, &pos);
   if (name) {
   if (name) {
     setobjs2s(L, pos, L->top - 1);
     setobjs2s(L, pos, L->top - 1);
     L->top--;  /* pop value */
     L->top--;  /* pop value */

+ 2 - 0
ldebug.h

@@ -22,6 +22,8 @@
 #define ABSLINEINFO	(-0x80)
 #define ABSLINEINFO	(-0x80)
 
 
 LUAI_FUNC int luaG_getfuncline (const Proto *f, int pc);
 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,
 LUAI_FUNC l_noret luaG_typeerror (lua_State *L, const TValue *o,
                                                 const char *opname);
                                                 const char *opname);
 LUAI_FUNC l_noret luaG_forerror (lua_State *L, const TValue *o,
 LUAI_FUNC l_noret luaG_forerror (lua_State *L, const TValue *o,

+ 6 - 0
lfunc.c

@@ -14,6 +14,7 @@
 
 
 #include "lua.h"
 #include "lua.h"
 
 
+#include "ldebug.h"
 #include "ldo.h"
 #include "ldo.h"
 #include "lfunc.h"
 #include "lfunc.h"
 #include "lgc.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 (likely(status == LUA_OK)) {
     if (prepclosingmethod(L, uv, &G(L)->nilvalue))  /* something to call? */
     if (prepclosingmethod(L, uv, &G(L)->nilvalue))  /* something to call? */
       callclose(L, NULL);  /* call closing method */
       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 */
   else {  /* there was an error */
     /* save error message and set stack top to 'level + 1' */
     /* save error message and set stack top to 'level + 1' */

+ 3 - 4
lvm.c

@@ -1427,7 +1427,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
       }
       }
       vmcase(OP_CLOSE) {
       vmcase(OP_CLOSE) {
         L->top = ra + 1;  /* everything is free after this slot */
         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;
         vmbreak;
       }
       }
       vmcase(OP_TBC) {
       vmcase(OP_TBC) {
@@ -1717,9 +1717,8 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
         vmbreak;
         vmbreak;
       }
       }
       vmcase(OP_TFORPREP) {
       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 */
           /* create to-be-closed upvalue for it */
           halfProtect(luaF_newtbcupval(L, ra + 3));
           halfProtect(luaF_newtbcupval(L, ra + 3));
         }
         }

+ 16 - 9
manual/manual.of

@@ -1063,11 +1063,16 @@ which start with @T{0x} or @T{0X}.
 Hexadecimal constants also accept an optional fractional part
 Hexadecimal constants also accept an optional fractional part
 plus an optional binary exponent,
 plus an optional binary exponent,
 marked by a letter @Char{p} or @Char{P}.
 marked by a letter @Char{p} or @Char{P}.
+
 A numeric constant with a radix point or an exponent
 A numeric constant with a radix point or an exponent
 denotes a float;
 denotes a float;
 otherwise,
 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
 Examples of valid integer constants are
 @verbatim{
 @verbatim{
 3   345   0xff   0xBEBADA
 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;
 that function is called;
 otherwise, if the value has a @idx{__close} metamethod,
 otherwise, if the value has a @idx{__close} metamethod,
 that metamethod is called;
 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,
 In the function case,
 if the scope is being closed by an error,
 if the scope is being closed by an error,
 the error object is passed as an argument to the function;
 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.
 the operation is performed over integers and the result is an integer.
 Otherwise, if both operands are numbers,
 Otherwise, if both operands are numbers,
 then they are converted to floats,
 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
 for floating-point arithmetic
 (usually the @x{IEEE 754} standard),
 (usually the @x{IEEE 754} standard),
 and the result is a float.
 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:
 you can use the buffer like this:
 @itemize{
 @itemize{
 
 
@@ -5012,7 +5018,8 @@ size @id{sz} with a call @T{luaL_buffinitsize(L, &b, sz)}.}
 @item{
 @item{
 Finish by calling @T{luaL_pushresultsize(&b, sz)},
 Finish by calling @T{luaL_pushresultsize(&b, sz)},
 where @id{sz} is the total size of the resulting string
 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
 the stack is at the same level
 it was immediately after the previous buffer operation.
 it was immediately after the previous buffer operation.
 (The only exception to this rule is @Lid{luaL_addvalue}.)
 (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.
 plus the final string on its top.
 
 
 }
 }
@@ -7118,7 +7125,7 @@ empty string as a match immediately after another match.
 As an example,
 As an example,
 consider the results of the following code:
 consider the results of the following code:
 @verbatim{
 @verbatim{
-> string.gsub("abc", "()a*()", print)
+> string.gsub("abc", "()a*()", print);
 --> 1   2
 --> 1   2
 --> 3   3
 --> 3   3
 --> 4   4
 --> 4   4

+ 14 - 7
testes/api.lua

@@ -366,7 +366,7 @@ do
   -- "argerror" without frames
   -- "argerror" without frames
   assert(T.checkpanic("loadstring 4") ==
   assert(T.checkpanic("loadstring 4") ==
       "bad argument #4 (string expected, got no value)")
       "bad argument #4 (string expected, got no value)")
-  
+
 
 
   -- memory error
   -- memory error
   T.totalmem(T.totalmem()+10000)   -- set low memory limit (+10k)
   T.totalmem(T.totalmem()+10000)   -- set low memory limit (+10k)
@@ -987,12 +987,12 @@ do
 
 
   local a, b = T.testC([[
   local a, b = T.testC([[
     call 0 1   # create resource
     call 0 1   # create resource
-    pushint 34
+    pushnil
     toclose -2  # mark call result to be closed
     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
     return 2
   ]], newresource)
   ]], newresource)
-  assert(a[1] == 11 and b == 34) 
+  assert(a[1] == 11 and b == nil)
   assert(#openresource == 0)    -- was closed
   assert(#openresource == 0)    -- was closed
 
 
   -- repeat the test, but calling function in a 'multret' context
   -- repeat the test, but calling function in a 'multret' context
@@ -1005,7 +1005,7 @@ do
   assert(#openresource == 0)    -- was closed
   assert(#openresource == 0)    -- was closed
 
 
   -- error
   -- error
-  local a, b = pcall(T.testC, [[
+  local a, b = pcall(T.makeCfunc[[
     call 0 1   # create resource
     call 0 1   # create resource
     toclose -1 # mark it to be closed
     toclose -1 # mark it to be closed
     error       # resource is the error object
     error       # resource is the error object
@@ -1038,6 +1038,13 @@ do
   ]], newresource, check)
   ]], newresource, check)
   assert(a == 3)   -- no extra items left in the stack
   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
 end
 
 
 
 
@@ -1249,9 +1256,9 @@ do   -- closing state with no extra memory
   T.closestate(L)
   T.closestate(L)
   T.alloccount()
   T.alloccount()
 end
 end
-  
+
 do   -- garbage collection with no extra memory
 do   -- garbage collection with no extra memory
-  local L = T.newstate() 
+  local L = T.newstate()
   T.loadlib(L)
   T.loadlib(L)
   local res = (T.doremote(L, [[
   local res = (T.doremote(L, [[
     _ENV = require"_G"
     _ENV = require"_G"

+ 10 - 10
testes/db.lua

@@ -214,14 +214,14 @@ local function foo (a, ...)
   local t = table.pack(...)
   local t = table.pack(...)
   for i = 1, t.n do
   for i = 1, t.n do
     local n, v = debug.getlocal(1, -i)
     local n, v = debug.getlocal(1, -i)
-    assert(n == "(*vararg)" and v == t[i])
+    assert(n == "(vararg)" and v == t[i])
   end
   end
   assert(not debug.getlocal(1, -(t.n + 1)))
   assert(not debug.getlocal(1, -(t.n + 1)))
   assert(not debug.setlocal(1, -(t.n + 1), 30))
   assert(not debug.setlocal(1, -(t.n + 1), 30))
   if t.n > 0 then
   if t.n > 0 then
     (function (x)
     (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)
      end)(430)
      assert(... == 430)
      assert(... == 430)
   end
   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)
 -- tests for manipulating non-registered locals (C and Lua temporaries)
 
 
 local n, v = debug.getlocal(0, 1)
 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)
 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, 3))
 assert(not debug.getlocal(0, 0))
 assert(not debug.getlocal(0, 0))
 
 
@@ -607,7 +607,7 @@ co = load[[
 local a = 0
 local a = 0
 -- 'A' should be visible to debugger only after its complete definition
 -- 'A' should be visible to debugger only after its complete definition
 debug.sethook(function (e, l)
 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")
   elseif l == 4 then a = a + 1; assert(debug.getlocal(2, 1) == "A")
   end
   end
 end, "l")
 end, "l")
@@ -875,15 +875,15 @@ local debug = require'debug'
 local a = 12  -- a local variable
 local a = 12  -- a local variable
 
 
 local n, v = debug.getlocal(1, 1)
 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)
 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
 -- a function with an upvalue
 local f = function () local x; return a end
 local f = function () local x; return a end
 n, v = debug.getupvalue(f, 1)
 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)
 assert(a == 13)
 
 
 local t = debug.getinfo(f)
 local t = debug.getinfo(f)

+ 21 - 0
testes/locals.lua

@@ -266,6 +266,27 @@ do   -- errors in __close
 end
 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
 if rawget(_G, "T") then
 
 
   -- memory error inside closing function
   -- memory error inside closing function