aboutsummaryrefslogtreecommitdiff
path: root/shell
diff options
context:
space:
mode:
authorDenys Vlasenko <vda.linux@googlemail.com>2025-08-12 20:16:07 +0200
committerDenys Vlasenko <vda.linux@googlemail.com>2025-08-12 20:29:26 +0200
commitf161bc628fc58ae9708e8499c93da657998d49f5 (patch)
treeee5fc365573d1449d810d0dfe4a716cbe5651c54 /shell
parent2b0b74e8b468496e903f8ed62d2c37fcef165170 (diff)
downloadbusybox-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.c135
-rw-r--r--shell/hush_test/hush-misc/syntax_err_negate.right4
-rwxr-xr-xshell/hush_test/hush-misc/syntax_err_negate.tests5
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 @@
1bash 3.2 fails this 10:0
2hush: syntax error: unexpected ! 21: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 @@
1echo 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:$?