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) {
   int *warnstate = (int *)ud;
   if (*warnstate != 2 && !tocont && *message == '@') {  /* control message? */
-    if (strcmp(message + 1, "off") == 0)
+    if (strcmp(message, "@off") == 0)
       *warnstate = 0;
-    else if (strcmp(message + 1, "on") == 0)
+    else if (strcmp(message, "@on") == 0)
       *warnstate = 1;
     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);
       if (newstatus != LUA_OK && status == CLOSEPROTECT)  /* first 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);
+      }
     }
     /* 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 */
     g->gcrunning = running;  /* restore state */
     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 */
     }
   }

+ 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_enterCcall (lua_State *L);
 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++)

+ 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 (buff[0] != '\0')
       badexit("Control warning during warning: %s\naborting...\n", msg);
-    if (strcmp(msg + 1, "off") == 0)
+    if (strcmp(msg, "@off") == 0)
       onoff = 0;
-    else if (strcmp(msg + 1, "on") == 0)
+    else if (strcmp(msg, "@on") == 0)
       onoff = 1;
-    else if (strcmp(msg + 1, "normal") == 0)
+    else if (strcmp(msg, "@normal") == 0)
       mode = 0;
-    else if (strcmp(msg + 1, "allow") == 0)
+    else if (strcmp(msg, "@allow") == 0)
       mode = 1;
-    else if (strcmp(msg + 1, "store") == 0)
+    else if (strcmp(msg, "@store") == 0)
       mode = 2;
     else
       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,
 the other pending closing methods will still be called.
 Errors in these methods
-interrupt the respective method,
+interrupt the respective method and generate a warning,
 but are otherwise ignored;
 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")
 end
 
+print("(there should be two warnings now)")
+warn("#This is ", "an expected", " warning")
 warn("@off")
 warn("******** THIS WARNING SHOULD NOT APPEAR **********")
 warn("******** THIS WARNING ALSO SHOULD NOT APPEAR **********")
 warn("@on")
-print("(there should be two warnings now)")
-warn("#This is ", "an expected", " warning")
 warn("#This is", " another one")
 
 -- no test module should define 'debug'

+ 4 - 1
testes/coroutine.lua

@@ -168,7 +168,7 @@ do
     local y <close> = func2close(function (self,err)
       if (err ~= 111) then os.exit(false) end   -- should not happen
       x = 200
-      error(200)
+      error("200")
     end)
     local x <close> = func2close(function (self, err)
       assert(err == nil); error(111)
@@ -177,7 +177,10 @@ do
   end)
   coroutine.resume(co)
   assert(x == 0)
+  _WARN = nil; warn("@off"); warn("@store")
   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(x == 200)
 

+ 127 - 25
testes/locals.lua

@@ -286,57 +286,149 @@ do
 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> =
-       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> =
-       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 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> =
+      -- 'z' close is called twice
       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)
-    if err then error(4) end
+
+    return 200
   end
+
   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)
   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
   local function foo (...)
     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
+    os.exit(false)    -- should not run
   end
 
   local st, msg = xpcall(foo, debug.traceback)
-  assert(string.match(msg, "^[^ ]* X"))
+  assert(string.match(msg, "^[^ ]* @X"))
   assert(string.find(msg, "in metamethod 'close'"))
 
   -- error in toclose in vararg function
   local function foo (...)
-    local x123 <close> = func2close(function () error("X") end)
+    local x123 <close> = func2close(function () error("@X") end)
   end
 
   local st, msg = xpcall(foo, debug.traceback)
-  assert(string.match(msg, "^[^ ]* X"))
+  assert(string.match(msg, "^[^ ]* @X"))
 
   assert(string.find(msg, "in metamethod 'close'"))
+  endwarn()
 end
 
 
@@ -361,6 +453,8 @@ end
 
 if rawget(_G, "T") then
 
+  warn("@off")
+
   -- memory error inside closing function
   local function foo ()
     local y <close> = func2close(function () T.alloccount() end)
@@ -437,7 +531,7 @@ if rawget(_G, "T") then
 
     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}
 
     collectgarbage()
@@ -472,6 +566,8 @@ if rawget(_G, "T") then
 
     print'+'
   end
+
+  warn("@on")
 end
 
 
@@ -501,17 +597,20 @@ end
 
 
 do
+  prepwarn()
+
   -- error in a wrapped coroutine raising errors when closing a variable
   local x = 0
   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)
       error(200)
   end)
   assert(co() == 100); assert(x == 0)
   local st, msg = pcall(co); assert(x == 2)
   assert(not st and msg == 200)   -- should get first error raised
+  checkwarn("@YYY")
 
   local x = 0
   local y = 0
@@ -526,6 +625,9 @@ do
   assert(x == 2 and y == 1)   -- first close is called twice
   -- should get first error raised
   assert(not st and string.find(msg, "%w+%.%w+:%d+: XXX"))
+  checkwarn("YYY")
+
+  endwarn()
 end