hush: disallow "{echo hi; }" (require whitespace)

and "{ echo hi }" (require semicolon or &)

function                                             old     new   delta
parse_stream                                        2098    2176     +78
done_command                                          98      84     -14
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 1/1 up/down: 78/-14)             Total: 64 bytes
This commit is contained in:
Denis Vlasenko 2009-04-17 11:55:42 +00:00
parent 5ff9629b8f
commit f8c1f02d2f

View File

@ -319,6 +319,10 @@ struct command {
*/ */
struct redir_struct *redirects; /* I/O redirections */ struct redir_struct *redirects; /* I/O redirections */
}; };
/* Is there anything in this command at all? */
#define IS_NULL_CMD(cmd) \
(!(cmd)->group && !(cmd)->argv && !(cmd)->redirects)
struct pipe { struct pipe {
struct pipe *next; struct pipe *next;
@ -341,6 +345,9 @@ typedef enum pipe_style {
PIPE_OR = 3, PIPE_OR = 3,
PIPE_BG = 4, PIPE_BG = 4,
} pipe_style; } pipe_style;
/* Is there anything in this pipe at all? */
#define IS_NULL_PIPE(pi) \
((pi)->num_cmds == 0 IF_HAS_KEYWORDS( && (pi)->res_word == RES_NONE))
/* This holds pointers to the various results of parsing */ /* This holds pointers to the various results of parsing */
struct parse_context { struct parse_context {
@ -3971,8 +3978,10 @@ static struct pipe *new_pipe(void)
return pi; return pi;
} }
/* Command (member of a pipe) is complete. The only possible error here /* Command (member of a pipe) is complete, or we start a new pipe
* is out of memory, in which case xmalloc exits. */ * if ctx->command is NULL.
* No errors possible here.
*/
static int done_command(struct parse_context *ctx) static int done_command(struct parse_context *ctx)
{ {
/* The command is really already in the pipe structure, so /* The command is really already in the pipe structure, so
@ -3981,13 +3990,9 @@ static int done_command(struct parse_context *ctx)
struct command *command = ctx->command; struct command *command = ctx->command;
if (command) { if (command) {
if (command->group == NULL if (IS_NULL_CMD(command)) {
&& command->argv == NULL
&& command->redirects == NULL
) {
debug_printf_parse("done_command: skipping null cmd, num_cmds=%d\n", pi->num_cmds); debug_printf_parse("done_command: skipping null cmd, num_cmds=%d\n", pi->num_cmds);
memset(command, 0, sizeof(*command)); /* paranoia */ goto clear_and_ret;
return pi->num_cmds;
} }
pi->num_cmds++; pi->num_cmds++;
debug_printf_parse("done_command: ++num_cmds=%d\n", pi->num_cmds); debug_printf_parse("done_command: ++num_cmds=%d\n", pi->num_cmds);
@ -3999,12 +4004,9 @@ static int done_command(struct parse_context *ctx)
/* Only real trickiness here is that the uncommitted /* Only real trickiness here is that the uncommitted
* command structure is not counted in pi->num_cmds. */ * command structure is not counted in pi->num_cmds. */
pi->cmds = xrealloc(pi->cmds, sizeof(*pi->cmds) * (pi->num_cmds+1)); pi->cmds = xrealloc(pi->cmds, sizeof(*pi->cmds) * (pi->num_cmds+1));
command = &pi->cmds[pi->num_cmds]; ctx->command = command = &pi->cmds[pi->num_cmds];
clear_and_ret:
memset(command, 0, sizeof(*command)); memset(command, 0, sizeof(*command));
ctx->command = command;
/* but ctx->pipe and ctx->list_head remain unchanged */
return pi->num_cmds; /* used only for 0/nonzero check */ return pi->num_cmds; /* used only for 0/nonzero check */
} }
@ -4024,9 +4026,7 @@ static void done_pipe(struct parse_context *ctx, pipe_style type)
/* Without this check, even just <enter> on command line generates /* Without this check, even just <enter> on command line generates
* tree of three NOPs (!). Which is harmless but annoying. * tree of three NOPs (!). Which is harmless but annoying.
* IOW: it is safe to do it unconditionally. * IOW: it is safe to do it unconditionally. */
* RES_NONE case is for "for a in; do ..." (empty IN set)
* and other cases to work. */
if (not_null if (not_null
#if ENABLE_HUSH_IF #if ENABLE_HUSH_IF
|| ctx->ctx_res_w == RES_FI || ctx->ctx_res_w == RES_FI
@ -4048,7 +4048,7 @@ static void done_pipe(struct parse_context *ctx, pipe_style type)
ctx->pipe->next = new_p; ctx->pipe->next = new_p;
ctx->pipe = new_p; ctx->pipe = new_p;
/* RES_THEN, RES_DO etc are "sticky" - /* RES_THEN, RES_DO etc are "sticky" -
* they remain set for commands inside if/while. * they remain set for pipes inside if/while.
* This is used to control execution. * This is used to control execution.
* RES_FOR and RES_IN are NOT sticky (needed to support * RES_FOR and RES_IN are NOT sticky (needed to support
* cases where variable or value happens to match a keyword): * cases where variable or value happens to match a keyword):
@ -4304,7 +4304,7 @@ static int done_word(o_string *word, struct parse_context *ctx)
&& ctx->ctx_res_w != RES_IN && ctx->ctx_res_w != RES_IN
# endif # endif
) { ) {
debug_printf_parse(": checking '%s' for reserved-ness\n", word->data); debug_printf_parse("checking '%s' for reserved-ness\n", word->data);
if (reserved_word(word, ctx)) { if (reserved_word(word, ctx)) {
o_reset_to_empty_unquoted(word); o_reset_to_empty_unquoted(word);
debug_printf_parse("done_word return %d\n", debug_printf_parse("done_word return %d\n",
@ -4775,6 +4775,14 @@ static int parse_group(o_string *dest, struct parse_context *ctx,
if (ch == '(') { if (ch == '(') {
endch = ')'; endch = ')';
command->grp_type = GRP_SUBSHELL; command->grp_type = GRP_SUBSHELL;
} else {
/* bash does not allow "{echo...", requires whitespace */
ch = i_getch(input);
if (ch != ' ' && ch != '\t' && ch != '\n') {
syntax_error_unexpected_ch(ch);
return 1;
}
nommu_addchr(&ctx->as_string, ch);
} }
{ {
@ -5352,13 +5360,11 @@ static struct pipe *parse_stream(char **pstring,
if (end_trigger && end_trigger == ch if (end_trigger && end_trigger == ch
&& (heredoc_cnt == 0 || end_trigger != ';') && (heredoc_cnt == 0 || end_trigger != ';')
) { ) {
//TODO: disallow "{ cmd }" without semicolon
if (heredoc_cnt) { if (heredoc_cnt) {
/* This is technically valid: /* This is technically valid:
* { cat <<HERE; }; echo Ok * { cat <<HERE; }; echo Ok
* heredoc * heredoc
* heredoc * heredoc
* heredoc
* HERE * HERE
* but we don't support this. * but we don't support this.
* We require heredoc to be in enclosing {}/(), * We require heredoc to be in enclosing {}/(),
@ -5370,6 +5376,15 @@ static struct pipe *parse_stream(char **pstring,
if (done_word(&dest, &ctx)) { if (done_word(&dest, &ctx)) {
goto parse_error; goto parse_error;
} }
/* Disallow "{ cmd }" without semicolon or & */
//debug_printf_parse("null pi %d\n", IS_NULL_PIPE(ctx.pipe))
//debug_printf_parse("null cmd %d\n", IS_NULL_CMD(ctx.command))
if (ch == '}'
&& !(IS_NULL_PIPE(ctx.pipe) && IS_NULL_CMD(ctx.command))
) {
syntax_error_unexpected_ch(ch);
goto parse_error;
}
done_pipe(&ctx, PIPE_SEQ); done_pipe(&ctx, PIPE_SEQ);
dest.o_assignment = MAYBE_ASSIGNMENT; dest.o_assignment = MAYBE_ASSIGNMENT;
/* Do we sit outside of any if's, loops or case's? */ /* Do we sit outside of any if's, loops or case's? */