diff options
| author | Denys Vlasenko <vda.linux@googlemail.com> | 2025-08-12 20:16:07 +0200 |
|---|---|---|
| committer | Denys Vlasenko <vda.linux@googlemail.com> | 2025-08-12 20:29:26 +0200 |
| commit | f161bc628fc58ae9708e8499c93da657998d49f5 (patch) | |
| tree | ee5fc365573d1449d810d0dfe4a716cbe5651c54 /shell | |
| parent | 2b0b74e8b468496e903f8ed62d2c37fcef165170 (diff) | |
| download | busybox-w32-f161bc628fc58ae9708e8499c93da657998d49f5.tar.gz busybox-w32-f161bc628fc58ae9708e8499c93da657998d49f5.tar.bz2 busybox-w32-f161bc628fc58ae9708e8499c93da657998d49f5.zip | |
hush: allow nested negation "! ! ! CMD" - bash 5.2.15 allows it
Also, deindent "ch == EOF" code branch in parse_stream()
function old new delta
done_word 799 797 -2
parse_stream 2453 2446 -7
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 0/2 up/down: 0/-9) Total: -9 bytes
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
Diffstat (limited to 'shell')
| -rw-r--r-- | shell/hush.c | 135 | ||||
| -rw-r--r-- | shell/hush_test/hush-misc/syntax_err_negate.right | 4 | ||||
| -rwxr-xr-x | shell/hush_test/hush-misc/syntax_err_negate.tests | 5 |
3 files changed, 71 insertions, 73 deletions
diff --git a/shell/hush.c b/shell/hush.c index bca27e038..b32d903ae 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
| @@ -4190,12 +4190,12 @@ static const struct reserved_combo* reserved_word(struct parse_context *ctx) | |||
| 4190 | # endif | 4190 | # endif |
| 4191 | if (r->flag == 0) { /* '!' */ | 4191 | if (r->flag == 0) { /* '!' */ |
| 4192 | if (ctx->pipe->cmds != ctx->command /* bash disallows: nice | ! cat */ | 4192 | if (ctx->pipe->cmds != ctx->command /* bash disallows: nice | ! cat */ |
| 4193 | || ctx->pipe->pi_inverted /* bash disallows: ! ! true */ | 4193 | /* || ctx->pipe->pi_inverted - bash used to disallow "! ! true" bash 5.2.15 allows it */ |
| 4194 | ) { | 4194 | ) { |
| 4195 | syntax_error_unexpected_ch('!'); | 4195 | syntax_error_unexpected_ch('!'); |
| 4196 | ctx->ctx_res_w = RES_SNTX; | 4196 | ctx->ctx_res_w = RES_SNTX; |
| 4197 | } | 4197 | } |
| 4198 | ctx->pipe->pi_inverted = 1; | 4198 | ctx->pipe->pi_inverted = 1 - ctx->pipe->pi_inverted; |
| 4199 | return r; | 4199 | return r; |
| 4200 | } | 4200 | } |
| 4201 | if (r->flag & FLAG_START) { | 4201 | if (r->flag & FLAG_START) { |
| @@ -5588,6 +5588,7 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5588 | struct in_str *input, | 5588 | struct in_str *input, |
| 5589 | int end_trigger) | 5589 | int end_trigger) |
| 5590 | { | 5590 | { |
| 5591 | struct pipe *pi; | ||
| 5591 | struct parse_context ctx; | 5592 | struct parse_context ctx; |
| 5592 | int heredoc_cnt; | 5593 | int heredoc_cnt; |
| 5593 | 5594 | ||
| @@ -5622,64 +5623,8 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5622 | ch = i_getch(input); | 5623 | ch = i_getch(input); |
| 5623 | debug_printf_parse(": ch:%c (%d) globprotect:%d\n", | 5624 | debug_printf_parse(": ch:%c (%d) globprotect:%d\n", |
| 5624 | ch, ch, !!(ctx.word.o_expflags & EXP_FLAG_GLOBPROTECT_CHARS)); | 5625 | ch, ch, !!(ctx.word.o_expflags & EXP_FLAG_GLOBPROTECT_CHARS)); |
| 5625 | if (ch == EOF) { | 5626 | if (ch == EOF) |
| 5626 | struct pipe *pi; | 5627 | break; |
| 5627 | |||
| 5628 | if (heredoc_cnt) { | ||
| 5629 | syntax_error_unterm_str("here document"); | ||
| 5630 | goto parse_error_exitcode1; | ||
| 5631 | } | ||
| 5632 | if (end_trigger == ')') { | ||
| 5633 | syntax_error_unterm_ch('('); | ||
| 5634 | goto parse_error_exitcode1; | ||
| 5635 | } | ||
| 5636 | if (end_trigger == '}') { | ||
| 5637 | syntax_error_unterm_ch('{'); | ||
| 5638 | goto parse_error_exitcode1; | ||
| 5639 | } | ||
| 5640 | |||
| 5641 | if (done_word(&ctx)) { | ||
| 5642 | goto parse_error_exitcode1; | ||
| 5643 | } | ||
| 5644 | o_free_and_set_NULL(&ctx.word); | ||
| 5645 | if (done_pipe(&ctx, PIPE_SEQ)) { | ||
| 5646 | /* Testcase: sh -c 'date |' */ | ||
| 5647 | syntax_error_unterm_ch('|'); | ||
| 5648 | goto parse_error_exitcode1; | ||
| 5649 | } | ||
| 5650 | // TODO: catch 'date &&<whitespace><EOF>' and 'date ||<whitespace><EOF>' too | ||
| 5651 | |||
| 5652 | /* Do we sit inside of any if's, loops or case's? */ | ||
| 5653 | if (HAS_KEYWORDS | ||
| 5654 | IF_HAS_KEYWORDS(&& (ctx.ctx_res_w != RES_NONE || ctx.old_flag != 0)) | ||
| 5655 | ) { | ||
| 5656 | syntax_error_unterm_str("compound statement"); | ||
| 5657 | goto parse_error_exitcode1; | ||
| 5658 | } | ||
| 5659 | |||
| 5660 | pi = ctx.list_head; | ||
| 5661 | /* If we got nothing... */ | ||
| 5662 | if (pi->num_cmds == 0 | ||
| 5663 | IF_HAS_KEYWORDS(&& pi->res_word == RES_NONE) | ||
| 5664 | ) { | ||
| 5665 | free_pipe_list(pi); | ||
| 5666 | pi = NULL; | ||
| 5667 | } | ||
| 5668 | #if !BB_MMU | ||
| 5669 | debug_printf_parse("as_string1 '%s'\n", ctx.as_string.data); | ||
| 5670 | if (pstring) | ||
| 5671 | *pstring = ctx.as_string.data; | ||
| 5672 | else | ||
| 5673 | o_free(&ctx.as_string); | ||
| 5674 | #endif | ||
| 5675 | // heredoc_cnt must be 0 here anyway | ||
| 5676 | //if (heredoc_cnt_ptr) | ||
| 5677 | // *heredoc_cnt_ptr = heredoc_cnt; | ||
| 5678 | debug_leave(); | ||
| 5679 | debug_printf_heredoc("parse_stream return heredoc_cnt:%d\n", heredoc_cnt); | ||
| 5680 | debug_printf_parse("parse_stream return %p: EOF\n", pi); | ||
| 5681 | return pi; | ||
| 5682 | } | ||
| 5683 | 5628 | ||
| 5684 | /* Handle "'" and "\" first, as they won't play nice with | 5629 | /* Handle "'" and "\" first, as they won't play nice with |
| 5685 | * i_peek_and_eat_bkslash_nl() anyway: | 5630 | * i_peek_and_eat_bkslash_nl() anyway: |
| @@ -5705,7 +5650,7 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5705 | /* bash-4.3.43 was removing backslash, | 5650 | /* bash-4.3.43 was removing backslash, |
| 5706 | * but 4.4.19 retains it, most other shells too | 5651 | * but 4.4.19 retains it, most other shells too |
| 5707 | */ | 5652 | */ |
| 5708 | continue; /* get next char */ | 5653 | break; |
| 5709 | } | 5654 | } |
| 5710 | /* Example: echo Hello \2>file | 5655 | /* Example: echo Hello \2>file |
| 5711 | * we need to know that word 2 is quoted | 5656 | * we need to know that word 2 is quoted |
| @@ -5719,7 +5664,7 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5719 | if (ch == '\'') { | 5664 | if (ch == '\'') { |
| 5720 | ctx.word.has_quoted_part = 1; | 5665 | ctx.word.has_quoted_part = 1; |
| 5721 | next = i_getch(input); | 5666 | next = i_getch(input); |
| 5722 | if (next == '\'' && !ctx.pending_redirect) | 5667 | if (next == '\'' && !ctx.pending_redirect/*why?*/) |
| 5723 | goto insert_empty_quoted_str_marker; | 5668 | goto insert_empty_quoted_str_marker; |
| 5724 | 5669 | ||
| 5725 | ch = next; | 5670 | ch = next; |
| @@ -5835,7 +5780,7 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5835 | * a "cmd1 && <nl> cmd2 &" construct, | 5780 | * a "cmd1 && <nl> cmd2 &" construct, |
| 5836 | * cmd1 may need to run in BG). | 5781 | * cmd1 may need to run in BG). |
| 5837 | */ | 5782 | */ |
| 5838 | struct pipe *pi = ctx.list_head; | 5783 | pi = ctx.list_head; |
| 5839 | if (pi->num_cmds != 0 /* check #1 */ | 5784 | if (pi->num_cmds != 0 /* check #1 */ |
| 5840 | && pi->followup != PIPE_BG /* check #2 */ | 5785 | && pi->followup != PIPE_BG /* check #2 */ |
| 5841 | ) { | 5786 | ) { |
| @@ -6206,12 +6151,66 @@ static struct pipe *parse_stream(char **pstring, | |||
| 6206 | } | 6151 | } |
| 6207 | } /* while (1) */ | 6152 | } /* while (1) */ |
| 6208 | 6153 | ||
| 6154 | /* Reached EOF */ | ||
| 6155 | if (heredoc_cnt) { | ||
| 6156 | syntax_error_unterm_str("here document"); | ||
| 6157 | goto parse_error_exitcode1; | ||
| 6158 | } | ||
| 6159 | if (end_trigger == ')') { | ||
| 6160 | syntax_error_unterm_ch('('); | ||
| 6161 | goto parse_error_exitcode1; | ||
| 6162 | } | ||
| 6163 | if (end_trigger == '}') { | ||
| 6164 | syntax_error_unterm_ch('{'); | ||
| 6165 | goto parse_error_exitcode1; | ||
| 6166 | } | ||
| 6167 | |||
| 6168 | if (done_word(&ctx)) { | ||
| 6169 | goto parse_error_exitcode1; | ||
| 6170 | } | ||
| 6171 | o_free_and_set_NULL(&ctx.word); | ||
| 6172 | if (done_pipe(&ctx, PIPE_SEQ)) { | ||
| 6173 | /* Testcase: sh -c 'date |' */ | ||
| 6174 | syntax_error_unterm_ch('|'); | ||
| 6175 | goto parse_error_exitcode1; | ||
| 6176 | } | ||
| 6177 | // TODO: catch 'date &&<whitespace><EOF>' and 'date ||<whitespace><EOF>' too | ||
| 6178 | |||
| 6179 | #if HAS_KEYWORDS | ||
| 6180 | /* Do we sit inside of any if's, loops or case's? */ | ||
| 6181 | if (ctx.ctx_res_w != RES_NONE || ctx.old_flag != 0) { | ||
| 6182 | syntax_error_unterm_str("compound statement"); | ||
| 6183 | goto parse_error_exitcode1; | ||
| 6184 | } | ||
| 6185 | #endif | ||
| 6186 | pi = ctx.list_head; | ||
| 6187 | /* If we got nothing... */ | ||
| 6188 | if (pi->num_cmds == 0 | ||
| 6189 | IF_HAS_KEYWORDS(&& pi->res_word == RES_NONE) | ||
| 6190 | ) { | ||
| 6191 | free_pipe_list(pi); | ||
| 6192 | pi = NULL; | ||
| 6193 | } | ||
| 6194 | #if !BB_MMU | ||
| 6195 | debug_printf_parse("as_string1 '%s'\n", ctx.as_string.data); | ||
| 6196 | if (pstring) | ||
| 6197 | *pstring = ctx.as_string.data; | ||
| 6198 | else | ||
| 6199 | o_free(&ctx.as_string); | ||
| 6200 | #endif | ||
| 6201 | // heredoc_cnt must be 0 here anyway | ||
| 6202 | //if (heredoc_cnt_ptr) | ||
| 6203 | // *heredoc_cnt_ptr = heredoc_cnt; | ||
| 6204 | debug_leave(); | ||
| 6205 | debug_printf_heredoc("parse_stream return heredoc_cnt:%d\n", heredoc_cnt); | ||
| 6206 | debug_printf_parse("parse_stream return %p: EOF\n", pi); | ||
| 6207 | return pi; | ||
| 6208 | |||
| 6209 | parse_error_exitcode1: | 6209 | parse_error_exitcode1: |
| 6210 | G.last_exitcode = 1; | 6210 | G.last_exitcode = 1; |
| 6211 | parse_error: | 6211 | parse_error: |
| 6212 | { | 6212 | { |
| 6213 | struct parse_context *pctx; | 6213 | struct parse_context *pctx IF_HAS_KEYWORDS(, *p2;); |
| 6214 | IF_HAS_KEYWORDS(struct parse_context *p2;) | ||
| 6215 | 6214 | ||
| 6216 | /* Clean up allocated tree. | 6215 | /* Clean up allocated tree. |
| 6217 | * Sample for finding leaks on syntax error recovery path. | 6216 | * Sample for finding leaks on syntax error recovery path. |
| @@ -6226,8 +6225,7 @@ static struct pipe *parse_stream(char **pstring, | |||
| 6226 | /* Update pipe/command counts, | 6225 | /* Update pipe/command counts, |
| 6227 | * otherwise freeing may miss some */ | 6226 | * otherwise freeing may miss some */ |
| 6228 | done_pipe(pctx, PIPE_SEQ); | 6227 | done_pipe(pctx, PIPE_SEQ); |
| 6229 | debug_printf_clean("freeing list %p from ctx %p\n", | 6228 | debug_printf_clean("freeing list %p from ctx %p\n", pctx->list_head, pctx); |
| 6230 | pctx->list_head, pctx); | ||
| 6231 | debug_print_tree(pctx->list_head, 0); | 6229 | debug_print_tree(pctx->list_head, 0); |
| 6232 | free_pipe_list(pctx->list_head); | 6230 | free_pipe_list(pctx->list_head); |
| 6233 | debug_printf_clean("freed list %p\n", pctx->list_head); | 6231 | debug_printf_clean("freed list %p\n", pctx->list_head); |
| @@ -6235,9 +6233,8 @@ static struct pipe *parse_stream(char **pstring, | |||
| 6235 | o_free(&pctx->as_string); | 6233 | o_free(&pctx->as_string); |
| 6236 | #endif | 6234 | #endif |
| 6237 | IF_HAS_KEYWORDS(p2 = pctx->stack;) | 6235 | IF_HAS_KEYWORDS(p2 = pctx->stack;) |
| 6238 | if (pctx != &ctx) { | 6236 | if (pctx != &ctx) |
| 6239 | free(pctx); | 6237 | free(pctx); |
| 6240 | } | ||
| 6241 | IF_HAS_KEYWORDS(pctx = p2;) | 6238 | IF_HAS_KEYWORDS(pctx = p2;) |
| 6242 | } while (HAS_KEYWORDS && pctx); | 6239 | } while (HAS_KEYWORDS && pctx); |
| 6243 | 6240 | ||
diff --git a/shell/hush_test/hush-misc/syntax_err_negate.right b/shell/hush_test/hush-misc/syntax_err_negate.right index 0b8d211c7..39db799ea 100644 --- a/shell/hush_test/hush-misc/syntax_err_negate.right +++ b/shell/hush_test/hush-misc/syntax_err_negate.right | |||
| @@ -1,2 +1,2 @@ | |||
| 1 | bash 3.2 fails this | 1 | 0:0 |
| 2 | hush: syntax error: unexpected ! | 2 | 1:1 |
diff --git a/shell/hush_test/hush-misc/syntax_err_negate.tests b/shell/hush_test/hush-misc/syntax_err_negate.tests index d61b1b09f..64f0f8a75 100755 --- a/shell/hush_test/hush-misc/syntax_err_negate.tests +++ b/shell/hush_test/hush-misc/syntax_err_negate.tests | |||
| @@ -1,2 +1,3 @@ | |||
| 1 | echo bash 3.2 fails this | 1 | # bash 3.2 fails this, 5.2.15 allows |
| 2 | ! ! true | 2 | ! ! true; echo 0:$? |
| 3 | ! ! ! true; echo 1:$? | ||
