Browse Source

Supressed errors in '__close' generate warnings

Roberto Ierusalimschy 6 years ago
parent
commit
ca13be9af7
10 changed files with 164 additions and 43 deletions
  1. 2 2
      lauxlib.c
  2. 5 1
      lfunc.c
  3. 1 6
      lgc.c
  4. 16 0
      lstate.c
  5. 1 0
      lstate.h
  6. 5 5
      ltests.c
  7. 1 1
      manual/manual.of
  8. 2 2
      testes/all.lua
  9. 4 1
      testes/coroutine.lua
  10. 127 25
      testes/locals.lua

+ 2 - 2
lauxlib.c

@@ -1010,9 +1010,9 @@ static int panic (lua_State *L) {
 static void warnf (void *ud, const char *message, int tocont) {
 static void warnf (void *ud, const char *message, int tocont) {
   int *warnstate = (int *)ud;
   int *warnstate = (int *)ud;
   if (*warnstate != 2 && !tocont && *message == '@') {  /* control message? */
   if (*warnstate != 2 && !tocont && *message == '@') {  /* control message? */
-    if (strcmp(message + 1, "off") == 0)
+    if (strcmp(message, "@off") == 0)
       *warnstate = 0;
       *warnstate = 0;
-    else if (strcmp(message + 1, "on") == 0)
+    else if (strcmp(message, "@on") == 0)
       *warnstate = 1;
       *warnstate = 1;
     return;
     return;
   }
   }

+ 5 - 1
lfunc.c

@@ -164,8 +164,12 @@ static int callclosemth (lua_State *L, StkId level, int status) {
       int newstatus = luaD_pcall(L, callclose, NULL, oldtop, 0);
       int newstatus = luaD_pcall(L, callclose, NULL, oldtop, 0);
       if (newstatus != LUA_OK && status == CLOSEPROTECT)  /* first error? */
       if (newstatus != LUA_OK && status == CLOSEPROTECT)  /* first error? */
         status = newstatus;  /* this will be the new error */
         status = newstatus;  /* this will be the new error */
-      else  /* leave original error (or nil) on top */
+      else {
+        if (newstatus != LUA_OK)  /* supressed error? */
+          luaE_warnerror(L, "__close metamethod");
+        /* leave original error (or nil) on top */
         L->top = restorestack(L, oldtop);
         L->top = restorestack(L, oldtop);
+      }
     }
     }
     /* else no metamethod; ignore this case and keep original error */
     /* else no metamethod; ignore this case and keep original error */
   }
   }

+ 1 - 6
lgc.c

@@ -854,12 +854,7 @@ static void GCTM (lua_State *L) {
     L->allowhook = oldah;  /* restore hooks */
     L->allowhook = oldah;  /* restore hooks */
     g->gcrunning = running;  /* restore state */
     g->gcrunning = running;  /* restore state */
     if (unlikely(status != LUA_OK)) {  /* error while running __gc? */
     if (unlikely(status != LUA_OK)) {  /* error while running __gc? */
-      const char *msg = (ttisstring(s2v(L->top - 1)))
-                        ? svalue(s2v(L->top - 1))
-                        : "error object is not a string";
-      luaE_warning(L, "error in __gc metamethod (", 1);
-      luaE_warning(L, msg, 1);
-      luaE_warning(L, ")", 0);
+      luaE_warnerror(L, "__gc metamethod");
       L->top--;  /* pops error object */
       L->top--;  /* pops error object */
     }
     }
   }
   }

+ 16 - 0
lstate.c

@@ -443,3 +443,19 @@ void luaE_warning (lua_State *L, const char *msg, int tocont) {
 }
 }
 
 
 
 
+/*
+** Generate a warning from an error message
+*/
+void luaE_warnerror (lua_State *L, const char *where) {
+  TValue *errobj = s2v(L->top - 1);  /* error object */
+  const char *msg = (ttisstring(errobj))
+                  ? svalue(errobj)
+                  : "error object is not a string";
+  /* produce warning "error in %s (%s)" (where, msg) */
+  luaE_warning(L, "error in ", 1);
+  luaE_warning(L, where, 1);
+  luaE_warning(L, " (", 1);
+  luaE_warning(L, msg, 1);
+  luaE_warning(L, ")", 0);
+}
+

+ 1 - 0
lstate.h

@@ -355,6 +355,7 @@ LUAI_FUNC void luaE_freeCI (lua_State *L);
 LUAI_FUNC void luaE_shrinkCI (lua_State *L);
 LUAI_FUNC void luaE_shrinkCI (lua_State *L);
 LUAI_FUNC void luaE_enterCcall (lua_State *L);
 LUAI_FUNC void luaE_enterCcall (lua_State *L);
 LUAI_FUNC void luaE_warning (lua_State *L, const char *msg, int tocont);
 LUAI_FUNC void luaE_warning (lua_State *L, const char *msg, int tocont);
+LUAI_FUNC void luaE_warnerror (lua_State *L, const char *where);
 
 
 
 
 #define luaE_exitCcall(L)	((L)->nCcalls++)
 #define luaE_exitCcall(L)	((L)->nCcalls++)

+ 5 - 5
ltests.c

@@ -95,15 +95,15 @@ static void warnf (void *ud, const char *msg, int tocont) {
   if (!lasttocont && !tocont && *msg == '@') {  /* control message? */
   if (!lasttocont && !tocont && *msg == '@') {  /* control message? */
     if (buff[0] != '\0')
     if (buff[0] != '\0')
       badexit("Control warning during warning: %s\naborting...\n", msg);
       badexit("Control warning during warning: %s\naborting...\n", msg);
-    if (strcmp(msg + 1, "off") == 0)
+    if (strcmp(msg, "@off") == 0)
       onoff = 0;
       onoff = 0;
-    else if (strcmp(msg + 1, "on") == 0)
+    else if (strcmp(msg, "@on") == 0)
       onoff = 1;
       onoff = 1;
-    else if (strcmp(msg + 1, "normal") == 0)
+    else if (strcmp(msg, "@normal") == 0)
       mode = 0;
       mode = 0;
-    else if (strcmp(msg + 1, "allow") == 0)
+    else if (strcmp(msg, "@allow") == 0)
       mode = 1;
       mode = 1;
-    else if (strcmp(msg + 1, "store") == 0)
+    else if (strcmp(msg, "@store") == 0)
       mode = 2;
       mode = 2;
     else
     else
       badexit("Invalid control warning in test mode: %s\naborting...\n", msg);
       badexit("Invalid control warning in test mode: %s\naborting...\n", msg);

+ 1 - 1
manual/manual.of

@@ -1556,7 +1556,7 @@ However, Lua may call the method one more time.
 After an error,
 After an error,
 the other pending closing methods will still be called.
 the other pending closing methods will still be called.
 Errors in these methods
 Errors in these methods
-interrupt the respective method,
+interrupt the respective method and generate a warning,
 but are otherwise ignored;
 but are otherwise ignored;
 the error reported is only the original one.
 the error reported is only the original one.
 
 

+ 2 - 2
testes/all.lua

@@ -209,12 +209,12 @@ if #msgs > 0 then
   warn("#tests not performed:\n  ", m, "\n")
   warn("#tests not performed:\n  ", m, "\n")
 end
 end
 
 
+print("(there should be two warnings now)")
+warn("#This is ", "an expected", " warning")
 warn("@off")
 warn("@off")
 warn("******** THIS WARNING SHOULD NOT APPEAR **********")
 warn("******** THIS WARNING SHOULD NOT APPEAR **********")
 warn("******** THIS WARNING ALSO SHOULD NOT APPEAR **********")
 warn("******** THIS WARNING ALSO SHOULD NOT APPEAR **********")
 warn("@on")
 warn("@on")
-print("(there should be two warnings now)")
-warn("#This is ", "an expected", " warning")
 warn("#This is", " another one")
 warn("#This is", " another one")
 
 
 -- no test module should define 'debug'
 -- no test module should define 'debug'

+ 4 - 1
testes/coroutine.lua

@@ -168,7 +168,7 @@ do
     local y <close> = func2close(function (self,err)
     local y <close> = func2close(function (self,err)
       if (err ~= 111) then os.exit(false) end   -- should not happen
       if (err ~= 111) then os.exit(false) end   -- should not happen
       x = 200
       x = 200
-      error(200)
+      error("200")
     end)
     end)
     local x <close> = func2close(function (self, err)
     local x <close> = func2close(function (self, err)
       assert(err == nil); error(111)
       assert(err == nil); error(111)
@@ -177,7 +177,10 @@ do
   end)
   end)
   coroutine.resume(co)
   coroutine.resume(co)
   assert(x == 0)
   assert(x == 0)
+  _WARN = nil; warn("@off"); warn("@store")
   local st, msg = coroutine.close(co)
   local st, msg = coroutine.close(co)
+  warn("@on"); warn("@normal")
+  assert(_WARN == nil or string.find(_WARN, "200"))
   assert(st == false and coroutine.status(co) == "dead" and msg == 111)
   assert(st == false and coroutine.status(co) == "dead" and msg == 111)
   assert(x == 200)
   assert(x == 200)
 
 

+ 127 - 25
testes/locals.lua

@@ -286,57 +286,149 @@ do
 end
 end
 
 
 
 
-do   -- errors in __close
-  local log = {}
-  local function foo (err)
+-- auxiliary functions for testing warnings in '__close'
+local function prepwarn ()
+  warn("@off")      -- do not show (lots of) warnings
+  if not T then
+    _WARN = "OFF"    -- signal that warnings are not being captured
+  else
+    warn("@store")    -- to test the warnings
+  end
+end
+
+
+local function endwarn ()
+  assert(T or _WARN == "OFF")
+  warn("@on")          -- back to normal
+  warn("@normal")
+  _WARN = nil
+end
+
+
+local function checkwarn (msg)
+  assert(_WARN == "OFF" or string.find(_WARN, msg))
+end
+
+
+do print("testing errors in __close")
+
+  prepwarn()
+
+  -- original error is in __close
+  local function foo ()
+
     local x <close> =
     local x <close> =
-       func2close(function (self, msg) log[#log + 1] = msg; error(1) end)
+      func2close(function (self, msg)
+        assert(string.find(msg, "@z"))
+        error("@x")
+      end)
+
     local x1 <close> =
     local x1 <close> =
-       func2close(function (self, msg) log[#log + 1] = msg; end)
+      func2close(function (self, msg)
+        checkwarn("@y")
+        assert(string.find(msg, "@z"))
+      end)
+
     local gc <close> = func2close(function () collectgarbage() end)
     local gc <close> = func2close(function () collectgarbage() end)
+
     local y <close> =
     local y <close> =
-      func2close(function (self, msg) log[#log + 1] = msg; error(2) end)
+      func2close(function (self, msg)
+        assert(string.find(msg, "@z"))  -- error in 'z'
+        error("@y")
+      end)
+
+    local first = true
     local z <close> =
     local z <close> =
+      -- 'z' close is called twice
       func2close(function (self, msg)
       func2close(function (self, msg)
-        log[#log + 1] = (msg or 10) + 1;
-        error(3)
+        if first then
+          assert(msg == nil)
+          first = false
+        else
+         assert(string.find(msg, "@z"))   -- own error
+        end
+        error("@z")
       end)
       end)
-    if err then error(4) end
+
+    return 200
   end
   end
+
   local stat, msg = pcall(foo, false)
   local stat, msg = pcall(foo, false)
-  assert(msg == 3)
-  -- 'z' close is called twice
-  assert(log[1] == 11 and log[2] == 4 and log[3] == 3 and log[4] == 3
-         and log[5] == 3 and #log == 5)
+  assert(string.find(msg, "@z"))
+  checkwarn("@x")
+
+
+  -- original error not in __close
+  local function foo ()
+
+    local x <close> =
+      func2close(function (self, msg)
+        assert(msg == 4)
+      end)
+
+    local x1 <close> =
+      func2close(function (self, msg)
+        checkwarn("@y")
+        assert(msg == 4)
+        error("@x1")
+      end)
+
+    local gc <close> = func2close(function () collectgarbage() end)
+
+    local y <close> =
+      func2close(function (self, msg)
+         assert(msg == 4)   -- error in body
+        error("@y")
+      end)
+
+    local first = true
+    local z <close> =
+      func2close(function (self, msg)
+        checkwarn("@z")
+        -- 'z' close is called once
+        assert(first and msg == 4)
+        first = false
+        error("@z")
+      end)
+
+    error(4)    -- original error
+  end
 
 
-  log = {}
   local stat, msg = pcall(foo, true)
   local stat, msg = pcall(foo, true)
   assert(msg == 4)
   assert(msg == 4)
-  -- 'z' close is called once
-  assert(log[1] == 5 and log[2] == 4 and log[3] == 4 and log[4] == 4
-         and #log == 4)
+  checkwarn("@x1")   -- last error
 
 
   -- error leaving a block
   -- error leaving a block
   local function foo (...)
   local function foo (...)
     do
     do
-      local x1 <close> = func2close(function () error("Y") end)
-      local x123 <close> = func2close(function () error("X") end)
+      local x1 <close> =
+        func2close(function ()
+          checkwarn("@X")
+          error("@Y")
+        end)
+
+      local x123 <close> =
+        func2close(function ()
+          error("@X")
+        end)
     end
     end
+    os.exit(false)    -- should not run
   end
   end
 
 
   local st, msg = xpcall(foo, debug.traceback)
   local st, msg = xpcall(foo, debug.traceback)
-  assert(string.match(msg, "^[^ ]* X"))
+  assert(string.match(msg, "^[^ ]* @X"))
   assert(string.find(msg, "in metamethod 'close'"))
   assert(string.find(msg, "in metamethod 'close'"))
 
 
   -- error in toclose in vararg function
   -- error in toclose in vararg function
   local function foo (...)
   local function foo (...)
-    local x123 <close> = func2close(function () error("X") end)
+    local x123 <close> = func2close(function () error("@X") end)
   end
   end
 
 
   local st, msg = xpcall(foo, debug.traceback)
   local st, msg = xpcall(foo, debug.traceback)
-  assert(string.match(msg, "^[^ ]* X"))
+  assert(string.match(msg, "^[^ ]* @X"))
 
 
   assert(string.find(msg, "in metamethod 'close'"))
   assert(string.find(msg, "in metamethod 'close'"))
+  endwarn()
 end
 end
 
 
 
 
@@ -361,6 +453,8 @@ end
 
 
 if rawget(_G, "T") then
 if rawget(_G, "T") then
 
 
+  warn("@off")
+
   -- memory error inside closing function
   -- memory error inside closing function
   local function foo ()
   local function foo ()
     local y <close> = func2close(function () T.alloccount() end)
     local y <close> = func2close(function () T.alloccount() end)
@@ -437,7 +531,7 @@ if rawget(_G, "T") then
 
 
     local s = string.rep("a", lim)
     local s = string.rep("a", lim)
 
 
-    -- concat this table needs two buffer resizes (one for each 's') 
+    -- concat this table needs two buffer resizes (one for each 's')
     local a = {s, s}
     local a = {s, s}
 
 
     collectgarbage()
     collectgarbage()
@@ -472,6 +566,8 @@ if rawget(_G, "T") then
 
 
     print'+'
     print'+'
   end
   end
+
+  warn("@on")
 end
 end
 
 
 
 
@@ -501,17 +597,20 @@ end
 
 
 
 
 do
 do
+  prepwarn()
+
   -- error in a wrapped coroutine raising errors when closing a variable
   -- error in a wrapped coroutine raising errors when closing a variable
   local x = 0
   local x = 0
   local co = coroutine.wrap(function ()
   local co = coroutine.wrap(function ()
-    local xx <close> = func2close(function () x = x + 1; error("YYY") end)
-    local xv <close> = func2close(function () x = x + 1; error("XXX") end)
+    local xx <close> = func2close(function () x = x + 1; error("@YYY") end)
+    local xv <close> = func2close(function () x = x + 1; error("@XXX") end)
       coroutine.yield(100)
       coroutine.yield(100)
       error(200)
       error(200)
   end)
   end)
   assert(co() == 100); assert(x == 0)
   assert(co() == 100); assert(x == 0)
   local st, msg = pcall(co); assert(x == 2)
   local st, msg = pcall(co); assert(x == 2)
   assert(not st and msg == 200)   -- should get first error raised
   assert(not st and msg == 200)   -- should get first error raised
+  checkwarn("@YYY")
 
 
   local x = 0
   local x = 0
   local y = 0
   local y = 0
@@ -526,6 +625,9 @@ do
   assert(x == 2 and y == 1)   -- first close is called twice
   assert(x == 2 and y == 1)   -- first close is called twice
   -- should get first error raised
   -- should get first error raised
   assert(not st and string.find(msg, "%w+%.%w+:%d+: XXX"))
   assert(not st and string.find(msg, "%w+%.%w+:%d+: XXX"))
+  checkwarn("YYY")
+
+  endwarn()
 end
 end