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
** was to be continued by the current one.
** Emit a warning. '*warnstate' means:
** 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) {
int *previoustocont = (int *)ud;
if (!*previoustocont) /* previous message was the last? */
int *warnstate = (int *)ud;
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", 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 */
*previoustocont = tocont;
*warnstate = 1; /* ready to start a new message */
}
}
LUALIB_API lua_State *luaL_newstate (void) {
lua_State *L = lua_newstate(l_alloc, NULL);
if (L) {
int *previoustocont; /* space for warning state */
int *warnstate; /* space for warning state */
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 */
*previoustocont = 0; /* next message starts a new warning */
lua_setwarnf(L, warnf, previoustocont);
*warnstate = 1; /* next message starts a new warning */
lua_setwarnf(L, warnf, warnstate);
}
return L;
}

View File

@ -48,9 +48,9 @@ static int luaB_warn (lua_State *L) {
luaL_checkstring(L, 1); /* at least one argument */
for (i = 2; i <= n; i++)
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, "", 0); /* close warning */
lua_warning(L, lua_tostring(L, n), 0); /* close warning */
return 0;
}

View File

@ -79,32 +79,62 @@ static int tpanic (lua_State *L) {
/*
** Warning function for tests. Fist, it concatenates all parts of
** a warning in buffer 'buff'. Then:
** - messages starting with '#' are shown on standard output (used to
** test explicit warnings);
** - messages containing '@' are stored in global '_WARN' (used to test
** errors that generate warnings);
** a warning in buffer 'buff'. Then, it has three modes:
** - 0.normal: messages starting with '#' are shown on standard output;
** - other messages abort the tests (they represent real warning
** 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 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))
badexit("%s", "warnf-buffer overflow");
strcat(buff, msg); /* add new message to current warning */
if (!tocont) { /* message finished? */
if (buff[0] == '#') /* expected warning? */
printf("Expected Lua warning: %s\n", buff); /* print it */
else if (strchr(buff, '@') != NULL) { /* warning for test purposes? */
lua_State *L = cast(lua_State *, ud);
lua_unlock(L);
lua_pushstring(L, buff);
lua_setglobal(L, "_WARN"); /* assign message to global '_WARN' */
lua_lock(L);
switch (mode) {
case 0: { /* normal */
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_unlock(L);
lua_pushstring(L, buff);
lua_setglobal(L, "_WARN"); /* assign message to global '_WARN' */
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 */
}
}

36
lua.c
View File

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

View File

@ -4370,6 +4370,8 @@ The third parameter is a boolean that
indicates whether the message is
to be continued by the message in the next call.
See @Lid{warn} for more details about warnings.
}
@APIEntry{
@ -4380,6 +4382,8 @@ Emits a warning with the given message.
A message in a call with @id{tocont} true should be
continued in another call to this function.
See @Lid{warn} for more details about warnings.
}
@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
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])|
@ -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},
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).
For instance, if we apply the pattern @T{"()aa()"} on the
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}
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.}
}
}
For the last two cases,
@id{size} is a hint for the size of the buffer, in bytes.
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{-v}| prints version information;}
@item{@T{-E}| ignores environment variables;}
@item{@T{-q}| turn warnings off;}
@item{@T{--}| 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}
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
@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.
(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.
Otherwise, the interpreter converts the error object to a string
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,
the interpreter closes its main Lua state

View File

@ -209,6 +209,10 @@ if #msgs > 0 then
warn("#tests not performed:\n ", m, "\n")
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)")
warn("#This is ", "an expected", " warning")
warn("#This is", " another one")

View File

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

View File

@ -369,6 +369,7 @@ if T then
s[n] = i
end
warn("@store")
collectgarbage()
assert(string.find(_WARN, "error in __gc metamethod"))
assert(string.match(_WARN, "@(.-)@") == "expected")
@ -383,6 +384,7 @@ if T then
for i = 1, 10 do assert(s[i]) end
getmetatable(u).__gc = nil
warn("@normal")
end
print '+'
@ -475,9 +477,11 @@ end
-- errors during collection
if T then
warn("@store")
u = setmetatable({}, {__gc = function () error "@expected error" end})
u = nil
collectgarbage()
warn("@normal")
end
@ -645,7 +649,7 @@ end
-- create several objects to raise errors when collected while closing state
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 lastmsg
local mt = {__gc = function (o)
@ -659,7 +663,9 @@ if T then
else
assert(lastmsg == _WARN) -- subsequent error messages are equal
end
warn("@store")
error"@expected warning"
warn("@normal")
end}
for i = 10, 1, -1 do
-- 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)
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
prepfile[[print(({...})[30])]]
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")
-- testing 'warn'
warn("@store")
warn("@123", "456", "789")
assert(_WARN == "@123456789")
warn("zip", "", " ", "zap")
assert(_WARN == "zip zap")
warn("ZIP", "", " ", "ZAP")
assert(_WARN == "ZIP ZAP")
warn("@normal")
end
do