Added control messages to warnings

Added the concept of control messages to the warning system, plus the
implementation of the controls "@on"/"@off" to turn warnings on/off.
Moreover, the warning system in the test library adds some other
controls to ease the test of warnings.
This commit is contained in:
Roberto Ierusalimschy 2019-08-15 13:44:36 -03:00
parent f64a1b175a
commit a1d8eb2743
9 changed files with 160 additions and 48 deletions

View File

@ -1002,29 +1002,43 @@ static int panic (lua_State *L) {
/* /*
** Emit a warning. '*previoustocont' signals whether previous message ** Emit a warning. '*warnstate' means:
** was to be continued by the current one. ** 0 - warning system is off;
** 1 - ready to start a new message;
** 2 - previous message is to be continued.
*/ */
static void warnf (void *ud, const char *message, int tocont) { static void warnf (void *ud, const char *message, int tocont) {
int *previoustocont = (int *)ud; int *warnstate = (int *)ud;
if (!*previoustocont) /* previous message was the last? */ if (*warnstate != 2 && !tocont && *message == '@') { /* control message? */
if (strcmp(message + 1, "off") == 0)
*warnstate = 0;
else if (strcmp(message + 1, "on") == 0)
*warnstate = 1;
return;
}
else if (*warnstate == 0) /* warnings off? */
return;
if (*warnstate == 1) /* previous message was the last? */
lua_writestringerror("%s", "Lua warning: "); /* start a new warning */ lua_writestringerror("%s", "Lua warning: "); /* start a new warning */
lua_writestringerror("%s", message); /* write message */ lua_writestringerror("%s", message); /* write message */
if (!tocont) /* is this the last part? */ if (tocont) /* not the last part? */
*warnstate = 2; /* to be continued */
else { /* last part */
lua_writestringerror("%s", "\n"); /* finish message with end-of-line */ lua_writestringerror("%s", "\n"); /* finish message with end-of-line */
*previoustocont = tocont; *warnstate = 1; /* ready to start a new message */
}
} }
LUALIB_API lua_State *luaL_newstate (void) { LUALIB_API lua_State *luaL_newstate (void) {
lua_State *L = lua_newstate(l_alloc, NULL); lua_State *L = lua_newstate(l_alloc, NULL);
if (L) { if (L) {
int *previoustocont; /* space for warning state */ int *warnstate; /* space for warning state */
lua_atpanic(L, &panic); lua_atpanic(L, &panic);
previoustocont = (int *)lua_newuserdatauv(L, sizeof(int), 0); warnstate = (int *)lua_newuserdatauv(L, sizeof(int), 0);
luaL_ref(L, LUA_REGISTRYINDEX); /* make sure it won't be collected */ luaL_ref(L, LUA_REGISTRYINDEX); /* make sure it won't be collected */
*previoustocont = 0; /* next message starts a new warning */ *warnstate = 1; /* next message starts a new warning */
lua_setwarnf(L, warnf, previoustocont); lua_setwarnf(L, warnf, warnstate);
} }
return L; return L;
} }

View File

@ -48,9 +48,9 @@ static int luaB_warn (lua_State *L) {
luaL_checkstring(L, 1); /* at least one argument */ luaL_checkstring(L, 1); /* at least one argument */
for (i = 2; i <= n; i++) for (i = 2; i <= n; i++)
luaL_checkstring(L, i); /* make sure all arguments are strings */ luaL_checkstring(L, i); /* make sure all arguments are strings */
for (i = 1; i <= n; i++) /* compose warning */ for (i = 1; i < n; i++) /* compose warning */
lua_warning(L, lua_tostring(L, i), 1); lua_warning(L, lua_tostring(L, i), 1);
lua_warning(L, "", 0); /* close warning */ lua_warning(L, lua_tostring(L, n), 0); /* close warning */
return 0; return 0;
} }

View File

@ -79,32 +79,62 @@ static int tpanic (lua_State *L) {
/* /*
** Warning function for tests. Fist, it concatenates all parts of ** Warning function for tests. Fist, it concatenates all parts of
** a warning in buffer 'buff'. Then: ** a warning in buffer 'buff'. Then, it has three modes:
** - messages starting with '#' are shown on standard output (used to ** - 0.normal: messages starting with '#' are shown on standard output;
** test explicit warnings);
** - messages containing '@' are stored in global '_WARN' (used to test
** errors that generate warnings);
** - other messages abort the tests (they represent real warning ** - other messages abort the tests (they represent real warning
** conditions; the standard tests should not generate these conditions ** conditions; the standard tests should not generate these conditions
** unexpectedly). ** unexpectedly);
** - 1.allow: all messages are shown;
** - 2.store: all warnings go to the global '_WARN';
*/ */
static void warnf (void *ud, const char *msg, int tocont) { static void warnf (void *ud, const char *msg, int tocont) {
static char buff[200] = ""; /* should be enough for tests... */ static char buff[200] = ""; /* should be enough for tests... */
static int onoff = 1;
static int mode = 0; /* start in normal mode */
static int lasttocont = 0;
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)
onoff = 0;
else if (strcmp(msg + 1, "on") == 0)
onoff = 1;
else if (strcmp(msg + 1, "normal") == 0)
mode = 0;
else if (strcmp(msg + 1, "allow") == 0)
mode = 1;
else if (strcmp(msg + 1, "store") == 0)
mode = 2;
else
badexit("Invalid control warning in test mode: %s\naborting...\n", msg);
return;
}
lasttocont = tocont;
if (strlen(msg) >= sizeof(buff) - strlen(buff)) if (strlen(msg) >= sizeof(buff) - strlen(buff))
badexit("%s", "warnf-buffer overflow"); badexit("%s", "warnf-buffer overflow");
strcat(buff, msg); /* add new message to current warning */ strcat(buff, msg); /* add new message to current warning */
if (!tocont) { /* message finished? */ if (!tocont) { /* message finished? */
if (buff[0] == '#') /* expected warning? */ switch (mode) {
printf("Expected Lua warning: %s\n", buff); /* print it */ case 0: { /* normal */
else if (strchr(buff, '@') != NULL) { /* warning for test purposes? */ if (buff[0] != '#' && onoff) /* unexpected warning? */
badexit("Unexpected warning in test mode: %s\naborting...\n", buff);
/* else */ /* FALLTHROUGH */
}
case 1: { /* allow */
if (onoff)
fprintf(stderr, "Lua warning: %s\n", buff); /* print warning */
break;
}
case 2: { /* store */
lua_State *L = cast(lua_State *, ud); lua_State *L = cast(lua_State *, ud);
lua_unlock(L); lua_unlock(L);
lua_pushstring(L, buff); lua_pushstring(L, buff);
lua_setglobal(L, "_WARN"); /* assign message to global '_WARN' */ lua_setglobal(L, "_WARN"); /* assign message to global '_WARN' */
lua_lock(L); lua_lock(L);
buff[0] = '\0'; /* prepare buffer for next warning */
break;
}
} }
else /* a real warning; should not happen during tests */
badexit("Unexpected warning in test mode: %s\naborting...\n", buff);
buff[0] = '\0'; /* prepare buffer for next warning */ buff[0] = '\0'; /* prepare buffer for next warning */
} }
} }

20
lua.c
View File

@ -73,6 +73,7 @@ static void print_usage (const char *badoption) {
" -l name require library 'name' into global 'name'\n" " -l name require library 'name' into global 'name'\n"
" -v show version information\n" " -v show version information\n"
" -E ignore environment variables\n" " -E ignore environment variables\n"
" -q turn warnings off\n"
" -- stop handling options\n" " -- stop handling options\n"
" - stop handling options and execute stdin\n" " - stop handling options and execute stdin\n"
, ,
@ -259,14 +260,18 @@ static int collectargs (char **argv, int *first) {
case '\0': /* '-' */ case '\0': /* '-' */
return args; /* script "name" is '-' */ return args; /* script "name" is '-' */
case 'E': case 'E':
if (argv[i][2] != '\0') /* extra characters after 1st? */ if (argv[i][2] != '\0') /* extra characters? */
return has_error; /* invalid option */ return has_error; /* invalid option */
args |= has_E; args |= has_E;
break; break;
case 'q':
if (argv[i][2] != '\0') /* extra characters? */
return has_error; /* invalid option */
break;
case 'i': case 'i':
args |= has_i; /* (-i implies -v) *//* FALLTHROUGH */ args |= has_i; /* (-i implies -v) *//* FALLTHROUGH */
case 'v': case 'v':
if (argv[i][2] != '\0') /* extra characters after 1st? */ if (argv[i][2] != '\0') /* extra characters? */
return has_error; /* invalid option */ return has_error; /* invalid option */
args |= has_v; args |= has_v;
break; break;
@ -289,7 +294,8 @@ static int collectargs (char **argv, int *first) {
/* /*
** Processes options 'e' and 'l', which involve running Lua code. ** Processes options 'e' and 'l', which involve running Lua code, and
** 'q', which also affects the state.
** Returns 0 if some code raises an error. ** Returns 0 if some code raises an error.
*/ */
static int runargs (lua_State *L, char **argv, int n) { static int runargs (lua_State *L, char **argv, int n) {
@ -297,7 +303,8 @@ static int runargs (lua_State *L, char **argv, int n) {
for (i = 1; i < n; i++) { for (i = 1; i < n; i++) {
int option = argv[i][1]; int option = argv[i][1];
lua_assert(argv[i][0] == '-'); /* already checked */ lua_assert(argv[i][0] == '-'); /* already checked */
if (option == 'e' || option == 'l') { switch (option) {
case 'e': case 'l': {
int status; int status;
const char *extra = argv[i] + 2; /* both options need an argument */ const char *extra = argv[i] + 2; /* both options need an argument */
if (*extra == '\0') extra = argv[++i]; if (*extra == '\0') extra = argv[++i];
@ -306,6 +313,11 @@ static int runargs (lua_State *L, char **argv, int n) {
? dostring(L, extra, "=(command line)") ? dostring(L, extra, "=(command line)")
: dolibrary(L, extra); : dolibrary(L, extra);
if (status != LUA_OK) return 0; if (status != LUA_OK) return 0;
break;
}
case 'q':
lua_warning(L, "@off", 0); /* no warnings */
break;
} }
} }
return 1; return 1;

View File

@ -4370,6 +4370,8 @@ The third parameter is a boolean that
indicates whether the message is indicates whether the message is
to be continued by the message in the next call. to be continued by the message in the next call.
See @Lid{warn} for more details about warnings.
} }
@APIEntry{ @APIEntry{
@ -4380,6 +4382,8 @@ Emits a warning with the given message.
A message in a call with @id{tocont} true should be A message in a call with @id{tocont} true should be
continued in another call to this function. continued in another call to this function.
See @Lid{warn} for more details about warnings.
} }
@APIEntry{ @APIEntry{
@ -6355,6 +6359,16 @@ The current value of this variable is @St{Lua 5.4}.
Emits a warning with a message composed by the concatenation Emits a warning with a message composed by the concatenation
of all its arguments (which should be strings). of all its arguments (which should be strings).
By convention,
a one-piece message starting with @Char{@At}
is intended to be a @emph{control message},
which is a message to the warning system itself.
In particular, the standard warning function in Lua
recognizes the control messages @St{@At{}off},
to stop the emission of warnings,
and @St{@At{}on}, to (re)start the emission;
it ignores unknown control messages.
} }
@LibEntry{xpcall (f, msgh [, arg1, @Cdots])| @LibEntry{xpcall (f, msgh [, arg1, @Cdots])|
@ -7293,7 +7307,7 @@ stored as the first capture, and therefore has @N{number 1};
the character matching @St{.} is captured with @N{number 2}, the character matching @St{.} is captured with @N{number 2},
and the part matching @St{%s*} has @N{number 3}. and the part matching @St{%s*} has @N{number 3}.
As a special case, the empty capture @T{()} captures As a special case, the capture @T{()} captures
the current string position (a number). the current string position (a number).
For instance, if we apply the pattern @T{"()aa()"} on the For instance, if we apply the pattern @T{"()aa()"} on the
string @T{"flaaap"}, there will be two captures: @N{3 and 5}. string @T{"flaaap"}, there will be two captures: @N{3 and 5}.
@ -7858,7 +7872,6 @@ they are compared as @x{unsigned integers}.
} }
@sect2{iolib| @title{Input and Output Facilities} @sect2{iolib| @title{Input and Output Facilities}
The I/O library provides two different styles for file manipulation. The I/O library provides two different styles for file manipulation.
@ -8150,7 +8163,6 @@ There are three available modes:
@item{@St{line}| line buffering.} @item{@St{line}| line buffering.}
} }
}
For the last two cases, For the last two cases,
@id{size} is a hint for the size of the buffer, in bytes. @id{size} is a hint for the size of the buffer, in bytes.
The default is an appropriate size. The default is an appropriate size.
@ -8708,6 +8720,7 @@ The options are:
@item{@T{-i}| enters interactive mode after running @rep{script};} @item{@T{-i}| enters interactive mode after running @rep{script};}
@item{@T{-v}| prints version information;} @item{@T{-v}| prints version information;}
@item{@T{-E}| ignores environment variables;} @item{@T{-E}| ignores environment variables;}
@item{@T{-q}| turn warnings off;}
@item{@T{--}| stops handling options;} @item{@T{--}| stops handling options;}
@item{@T{-}| executes @id{stdin} as a file and stops handling options.} @item{@T{-}| executes @id{stdin} as a file and stops handling options.}
} }
@ -8733,12 +8746,13 @@ setting the values of
@Lid{package.path} and @Lid{package.cpath} @Lid{package.path} and @Lid{package.cpath}
with the default paths defined in @id{luaconf.h}. with the default paths defined in @id{luaconf.h}.
All options are handled in order, except @T{-i} and @T{-E}. The options @T{-e}, @T{-l}, and @T{-q} are handled in
the order they appear.
For instance, an invocation like For instance, an invocation like
@verbatim{ @verbatim{
$ lua -e'a=1' -e 'print(a)' script.lua $ lua -e 'a=1' -llib1 script.lua
} }
will first set @id{a} to 1, then print the value of @id{a}, will first set @id{a} to 1, then require the library @id{lib1},
and finally run the file @id{script.lua} with no arguments. and finally run the file @id{script.lua} with no arguments.
(Here @T{$} is the shell prompt. Your prompt may be different.) (Here @T{$} is the shell prompt. Your prompt may be different.)
@ -8798,7 +8812,8 @@ has a metamethod @idx{__tostring},
the interpreter calls this metamethod to produce the final message. the interpreter calls this metamethod to produce the final message.
Otherwise, the interpreter converts the error object to a string Otherwise, the interpreter converts the error object to a string
and adds a stack traceback to it. and adds a stack traceback to it.
Warnings are simply printed in the standard error output. When warnings are on,
they are simply printed in the standard error output.
When finishing normally, When finishing normally,
the interpreter closes its main Lua state the interpreter closes its main Lua state

View File

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

View File

@ -977,6 +977,7 @@ assert(t[7] == nil)
------------------------------------------------------------------------- -------------------------------------------------------------------------
do -- testing errors during GC do -- testing errors during GC
warn("@off")
collectgarbage("stop") collectgarbage("stop")
local a = {} local a = {}
for i=1,20 do for i=1,20 do
@ -994,6 +995,7 @@ do -- testing errors during GC
collectgarbage() collectgarbage()
assert(A == 10) -- number of normal collections assert(A == 10) -- number of normal collections
collectgarbage("restart") collectgarbage("restart")
warn("@on")
end end
------------------------------------------------------------------------- -------------------------------------------------------------------------
-- test for userdata vals -- test for userdata vals

View File

@ -369,6 +369,7 @@ if T then
s[n] = i s[n] = i
end end
warn("@store")
collectgarbage() collectgarbage()
assert(string.find(_WARN, "error in __gc metamethod")) assert(string.find(_WARN, "error in __gc metamethod"))
assert(string.match(_WARN, "@(.-)@") == "expected") assert(string.match(_WARN, "@(.-)@") == "expected")
@ -383,6 +384,7 @@ if T then
for i = 1, 10 do assert(s[i]) end for i = 1, 10 do assert(s[i]) end
getmetatable(u).__gc = nil getmetatable(u).__gc = nil
warn("@normal")
end end
print '+' print '+'
@ -475,9 +477,11 @@ end
-- errors during collection -- errors during collection
if T then if T then
warn("@store")
u = setmetatable({}, {__gc = function () error "@expected error" end}) u = setmetatable({}, {__gc = function () error "@expected error" end})
u = nil u = nil
collectgarbage() collectgarbage()
warn("@normal")
end end
@ -645,7 +649,7 @@ end
-- create several objects to raise errors when collected while closing state -- create several objects to raise errors when collected while closing state
if T then if T then
local error, assert, find = error, assert, string.find local error, assert, find, warn = error, assert, string.find, warn
local n = 0 local n = 0
local lastmsg local lastmsg
local mt = {__gc = function (o) local mt = {__gc = function (o)
@ -659,7 +663,9 @@ if T then
else else
assert(lastmsg == _WARN) -- subsequent error messages are equal assert(lastmsg == _WARN) -- subsequent error messages are equal
end end
warn("@store")
error"@expected warning" error"@expected warning"
warn("@normal")
end} end}
for i = 10, 1, -1 do for i = 10, 1, -1 do
-- create object and preserve it until the end -- create object and preserve it until the end

View File

@ -221,6 +221,28 @@ assert(string.find(getoutput(), "error calling 'print'"))
RUN('echo "io.stderr:write(1000)\ncont" | lua -e "require\'debug\'.debug()" 2> %s', out) RUN('echo "io.stderr:write(1000)\ncont" | lua -e "require\'debug\'.debug()" 2> %s', out)
checkout("lua_debug> 1000lua_debug> ") checkout("lua_debug> 1000lua_debug> ")
-- test warnings
RUN('echo "io.stderr:write(1); warn[[XXX]]" | lua -q 2> %s', out)
checkout("1")
prepfile[[
warn("@allow") -- unknown control, ignored
warn("@off", "XXX", "@off") -- these are not control messages
warn("@off") -- this one is
warn("@on", "YYY", "@on") -- not control, but warn is off
warn("@off") -- keep it off
warn("@on") -- restart warnings
warn("", "@on") -- again, no control, real warning
warn("@on") -- keep it "started"
warn("Z", "Z", "Z") -- common warning
]]
RUN('lua %s 2> %s', prog, out)
checkout[[
Lua warning: @offXXX@off
Lua warning: @on
Lua warning: ZZZ
]]
-- test many arguments -- test many arguments
prepfile[[print(({...})[30])]] prepfile[[print(({...})[30])]]
RUN('lua %s %s > %s', prog, string.rep(" a", 30), out) RUN('lua %s %s > %s', prog, string.rep(" a", 30), out)
@ -355,8 +377,15 @@ if T then -- test library?
NoRun("not enough memory", "env MEMLIMIT=100 lua") NoRun("not enough memory", "env MEMLIMIT=100 lua")
-- testing 'warn' -- testing 'warn'
warn("@store")
warn("@123", "456", "789") warn("@123", "456", "789")
assert(_WARN == "@123456789") assert(_WARN == "@123456789")
warn("zip", "", " ", "zap")
assert(_WARN == "zip zap")
warn("ZIP", "", " ", "ZAP")
assert(_WARN == "ZIP ZAP")
warn("@normal")
end end
do do