aboutsummaryrefslogtreecommitdiff
path: root/shell
diff options
context:
space:
mode:
authorDenys Vlasenko <vda.linux@googlemail.com>2025-08-13 00:47:25 +0200
committerDenys Vlasenko <vda.linux@googlemail.com>2025-08-13 00:51:44 +0200
commit1847fee2d4ee7ce13edd676d3e69b8e11c3d1e7d (patch)
tree1ccd6426fe2a1e44b08f83d8da3a05059e6be265 /shell
parentf161bc628fc58ae9708e8499c93da657998d49f5 (diff)
downloadbusybox-w32-1847fee2d4ee7ce13edd676d3e69b8e11c3d1e7d.tar.gz
busybox-w32-1847fee2d4ee7ce13edd676d3e69b8e11c3d1e7d.tar.bz2
busybox-w32-1847fee2d4ee7ce13edd676d3e69b8e11c3d1e7d.zip
hush: fix a corner case in "case" stmt, ctx_dsemicolon is in fact unused
function old new delta parse_stream 2446 2476 +30 done_word 797 800 +3 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/0 up/down: 33/0) Total: 33 bytes Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
Diffstat (limited to 'shell')
-rw-r--r--shell/hush.c91
-rw-r--r--shell/hush_test/hush-misc/case2.right1
-rwxr-xr-xshell/hush_test/hush-misc/case2.tests6
-rw-r--r--shell/hush_test/hush-misc/case3.right2
-rwxr-xr-xshell/hush_test/hush-misc/case3.tests8
5 files changed, 55 insertions, 53 deletions
diff --git a/shell/hush.c b/shell/hush.c
index b32d903ae..9ec813df2 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -748,9 +748,6 @@ struct parse_context {
748 smallint is_assignment; /* 0:maybe, 1:yes, 2:no, 3:keyword */ 748 smallint is_assignment; /* 0:maybe, 1:yes, 2:no, 3:keyword */
749#if HAS_KEYWORDS 749#if HAS_KEYWORDS
750 smallint ctx_res_w; 750 smallint ctx_res_w;
751#if ENABLE_HUSH_CASE
752 smallint ctx_dsemicolon; /* ";;" seen */
753#endif
754 /* bitmask of FLAG_xxx, for figuring out valid reserved words */ 751 /* bitmask of FLAG_xxx, for figuring out valid reserved words */
755 int old_flag; 752 int old_flag;
756 /* group we are enclosed in: 753 /* group we are enclosed in:
@@ -4170,7 +4167,7 @@ static const struct reserved_combo* reserved_word(struct parse_context *ctx)
4170{ 4167{
4171# if ENABLE_HUSH_CASE 4168# if ENABLE_HUSH_CASE
4172 static const struct reserved_combo reserved_match = { 4169 static const struct reserved_combo reserved_match = {
4173 "", RES_MATCH, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_ESAC 4170 "", RES_MATCH, NOT_ASSIGNMENT, FLAG_MATCH | FLAG_ESAC
4174 }; 4171 };
4175# endif 4172# endif
4176 const struct reserved_combo *r; 4173 const struct reserved_combo *r;
@@ -4266,7 +4263,7 @@ static const struct reserved_combo* reserved_word(struct parse_context *ctx)
4266 * Look at it and update current command: 4263 * Look at it and update current command:
4267 * update current command's argv/cmd_type/etc, fill in redirect name and type, 4264 * update current command's argv/cmd_type/etc, fill in redirect name and type,
4268 * check reserved-ness and assignment-ness, etc... 4265 * check reserved-ness and assignment-ness, etc...
4269 * Normal return is 0. Syntax errors return 1. 4266 * Normal return is 0. Syntax errors print error message and return 1.
4270 * Note: on return, word is reset, but not o_free'd! 4267 * Note: on return, word is reset, but not o_free'd!
4271 */ 4268 */
4272static int done_word(struct parse_context *ctx) 4269static int done_word(struct parse_context *ctx)
@@ -4324,15 +4321,16 @@ static int done_word(struct parse_context *ctx)
4324 4321
4325#if HAS_KEYWORDS 4322#if HAS_KEYWORDS
4326# if ENABLE_HUSH_CASE 4323# if ENABLE_HUSH_CASE
4327 4324 if (ctx->ctx_res_w == RES_MATCH
4328 if (ctx->ctx_dsemicolon
4329 && (ctx->word.has_quoted_part 4325 && (ctx->word.has_quoted_part
4330 || strcmp(ctx->word.data, "esac") != 0 4326 || strcmp(ctx->word.data, "esac") != 0
4331 ) 4327 )
4332 ) { /* ";; WORD" but not "... PATTERN) CMD;; esac" */ 4328 ) { /* ";; WORD" but not ";; esac" */
4333 /* already done when ctx_dsemicolon was set to 1: */ 4329 /* Do not match WORD as keyword:
4334 /* ctx->ctx_res_w = RES_MATCH; */ 4330 * the WORD is a case match, can be keyword-like:
4335 ctx->ctx_dsemicolon = 0; 4331 * if) echo got_if;;
4332 * is allowed.
4333 */
4336 } else 4334 } else
4337# endif 4335# endif
4338# if defined(CMD_TEST2_SINGLEWORD_NOGLOB) 4336# if defined(CMD_TEST2_SINGLEWORD_NOGLOB)
@@ -4345,13 +4343,13 @@ static int done_word(struct parse_context *ctx)
4345 } else 4343 } else
4346# endif 4344# endif
4347 if (!command->argv /* if it's the first word of command... */ 4345 if (!command->argv /* if it's the first word of command... */
4348 && !command->redirects /* no redirects yet... disallows: </dev/null ! true; echo $? */ 4346 && !command->redirects /* no redirects yet... disallows: </dev/null if true; then... */
4349# if ENABLE_HUSH_LOOPS 4347# if ENABLE_HUSH_LOOPS
4350 && ctx->ctx_res_w != RES_FOR /* not after FOR or IN */ 4348 && ctx->ctx_res_w != RES_FOR /* not after FOR or IN */
4351 && ctx->ctx_res_w != RES_IN 4349 && ctx->ctx_res_w != RES_IN
4352# endif 4350# endif
4353# if ENABLE_HUSH_CASE 4351# if ENABLE_HUSH_CASE
4354 && ctx->ctx_res_w != RES_CASE /* not after CASE */ 4352 && ctx->ctx_res_w != RES_CASE /* not after CASE */
4355# endif 4353# endif
4356 ) { 4354 ) {
4357 const struct reserved_combo *reserved; 4355 const struct reserved_combo *reserved;
@@ -5648,7 +5646,7 @@ static struct pipe *parse_stream(char **pstring,
5648 if (ch == EOF) { 5646 if (ch == EOF) {
5649 /* Testcase: eval 'echo Ok\' */ 5647 /* Testcase: eval 'echo Ok\' */
5650 /* bash-4.3.43 was removing backslash, 5648 /* bash-4.3.43 was removing backslash,
5651 * but 4.4.19 retains it, most other shells too 5649 * but 4.4.19 retains it, most other shells retain too
5652 */ 5650 */
5653 break; 5651 break;
5654 } 5652 }
@@ -5749,9 +5747,8 @@ static struct pipe *parse_stream(char **pstring,
5749 } 5747 }
5750 /* ch == last eaten whitespace char */ 5748 /* ch == last eaten whitespace char */
5751#endif 5749#endif
5752 if (done_word(&ctx)) { 5750 if (done_word(&ctx))
5753 goto parse_error_exitcode1; 5751 goto parse_error_exitcode1;
5754 }
5755 if (ch == '\n') { 5752 if (ch == '\n') {
5756 /* Is this a case when newline is simply ignored? 5753 /* Is this a case when newline is simply ignored?
5757 * Some examples: 5754 * Some examples:
@@ -5784,10 +5781,10 @@ static struct pipe *parse_stream(char **pstring,
5784 if (pi->num_cmds != 0 /* check #1 */ 5781 if (pi->num_cmds != 0 /* check #1 */
5785 && pi->followup != PIPE_BG /* check #2 */ 5782 && pi->followup != PIPE_BG /* check #2 */
5786 ) { 5783 ) {
5787 continue; 5784 continue; /* ignore newline */
5788 } 5785 }
5789 } 5786 }
5790 /* Treat newline as a command separator. */ 5787 /* Treat newline as a command separator */
5791 done_pipe(&ctx, PIPE_SEQ); 5788 done_pipe(&ctx, PIPE_SEQ);
5792 debug_printf_heredoc("heredoc_cnt:%d\n", heredoc_cnt); 5789 debug_printf_heredoc("heredoc_cnt:%d\n", heredoc_cnt);
5793 if (heredoc_cnt) { 5790 if (heredoc_cnt) {
@@ -5805,11 +5802,11 @@ static struct pipe *parse_stream(char **pstring,
5805 5802
5806 /* "cmd}" or "cmd }..." without semicolon or &: 5803 /* "cmd}" or "cmd }..." without semicolon or &:
5807 * } is an ordinary char in this case, even inside { cmd; } 5804 * } is an ordinary char in this case, even inside { cmd; }
5808 * Pathological example: { ""}; } should exec "}" cmd 5805 * Pathological example: { ""}; } should run "}" command.
5809 */ 5806 */
5810 if (ch == '}') { 5807 if (ch == '}') {
5811 if (ctx.word.length != 0 /* word} */ 5808 if (ctx.word.length != 0 /* word} */
5812 || ctx.word.has_quoted_part /* ""} */ 5809 || ctx.word.has_quoted_part /* ""} */
5813 ) { 5810 ) {
5814 goto ordinary_char; 5811 goto ordinary_char;
5815 } 5812 }
@@ -5842,9 +5839,8 @@ static struct pipe *parse_stream(char **pstring,
5842 ) 5839 )
5843#endif 5840#endif
5844 ) { 5841 ) {
5845 if (done_word(&ctx)) { 5842 if (done_word(&ctx))
5846 goto parse_error_exitcode1; 5843 goto parse_error_exitcode1;
5847 }
5848 if (done_pipe(&ctx, PIPE_SEQ)) { 5844 if (done_pipe(&ctx, PIPE_SEQ)) {
5849 /* Testcase: sh -c 'date|;not_reached' */ 5845 /* Testcase: sh -c 'date|;not_reached' */
5850 syntax_error_unterm_ch('|'); 5846 syntax_error_unterm_ch('|');
@@ -5889,9 +5885,8 @@ static struct pipe *parse_stream(char **pstring,
5889 switch (ch) { 5885 switch (ch) {
5890 case '>': 5886 case '>':
5891 redir_fd = redirect_opt_num(&ctx.word); 5887 redir_fd = redirect_opt_num(&ctx.word);
5892 if (done_word(&ctx)) { 5888 if (done_word(&ctx))
5893 goto parse_error_exitcode1; 5889 goto parse_error_exitcode1;
5894 }
5895 redir_style = REDIRECT_OVERWRITE; 5890 redir_style = REDIRECT_OVERWRITE;
5896 if (next == '>') { 5891 if (next == '>') {
5897 redir_style = REDIRECT_APPEND; 5892 redir_style = REDIRECT_APPEND;
@@ -5909,9 +5904,8 @@ static struct pipe *parse_stream(char **pstring,
5909 continue; /* get next char */ 5904 continue; /* get next char */
5910 case '<': 5905 case '<':
5911 redir_fd = redirect_opt_num(&ctx.word); 5906 redir_fd = redirect_opt_num(&ctx.word);
5912 if (done_word(&ctx)) { 5907 if (done_word(&ctx))
5913 goto parse_error_exitcode1; 5908 goto parse_error_exitcode1;
5914 }
5915 redir_style = REDIRECT_INPUT; 5909 redir_style = REDIRECT_INPUT;
5916 if (next == '<') { 5910 if (next == '<') {
5917 redir_style = REDIRECT_HEREDOC; 5911 redir_style = REDIRECT_HEREDOC;
@@ -5951,7 +5945,7 @@ static struct pipe *parse_stream(char **pstring,
5951 } 5945 }
5952 ch = i_getch(input); 5946 ch = i_getch(input);
5953 if (ch == EOF) 5947 if (ch == EOF)
5954 break; 5948 goto eof;
5955 } 5949 }
5956 continue; /* get next char */ 5950 continue; /* get next char */
5957 } 5951 }
@@ -6026,27 +6020,17 @@ static struct pipe *parse_stream(char **pstring,
6026 } 6020 }
6027#endif 6021#endif
6028 case ';': 6022 case ';':
6029#if ENABLE_HUSH_CASE 6023 if (done_word(&ctx))
6030 case_semi:
6031#endif
6032 if (done_word(&ctx)) {
6033 goto parse_error_exitcode1; 6024 goto parse_error_exitcode1;
6034 }
6035 done_pipe(&ctx, PIPE_SEQ); 6025 done_pipe(&ctx, PIPE_SEQ);
6036#if ENABLE_HUSH_CASE 6026#if ENABLE_HUSH_CASE
6037 /* Eat multiple semicolons, detect 6027 if (ctx.ctx_res_w == RES_CASE_BODY
6038 * whether it means something special */ 6028 && !is_blank /* is it really actual semicolon? */
6039 while (1) { 6029 && i_peek_and_eat_bkslash_nl(input) == ';' /* and next char is ';' too? */
6040 ch = i_peek_and_eat_bkslash_nl(input); 6030 ) {
6041 if (ch != ';')
6042 break;
6043 ch = i_getch(input); 6031 ch = i_getch(input);
6044 nommu_addchr(&ctx.as_string, ch); 6032 nommu_addchr(&ctx.as_string, ch);
6045 if (ctx.ctx_res_w == RES_CASE_BODY) { 6033 ctx.ctx_res_w = RES_MATCH; /* "we are in PATTERN)" */
6046 ctx.ctx_dsemicolon = 1;
6047 ctx.ctx_res_w = RES_MATCH;
6048 break;
6049 }
6050 } 6034 }
6051#endif 6035#endif
6052 new_cmd: 6036 new_cmd:
@@ -6056,9 +6040,8 @@ static struct pipe *parse_stream(char **pstring,
6056 debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); 6040 debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]);
6057 continue; /* get next char */ 6041 continue; /* get next char */
6058 case '&': 6042 case '&':
6059 if (done_word(&ctx)) { 6043 if (done_word(&ctx))
6060 goto parse_error_exitcode1; 6044 goto parse_error_exitcode1;
6061 }
6062 if (ctx.pipe->num_cmds == 0 && IS_NULL_CMD(ctx.command)) { 6045 if (ctx.pipe->num_cmds == 0 && IS_NULL_CMD(ctx.command)) {
6063 /* Testcase: sh -c '&& date' */ 6046 /* Testcase: sh -c '&& date' */
6064 /* Testcase: sh -c '&' */ 6047 /* Testcase: sh -c '&' */
@@ -6082,9 +6065,8 @@ static struct pipe *parse_stream(char **pstring,
6082 } 6065 }
6083 goto new_cmd; 6066 goto new_cmd;
6084 case '|': 6067 case '|':
6085 if (done_word(&ctx)) { 6068 if (done_word(&ctx))
6086 goto parse_error_exitcode1; 6069 goto parse_error_exitcode1;
6087 }
6088#if ENABLE_HUSH_CASE 6070#if ENABLE_HUSH_CASE
6089 if (ctx.ctx_res_w == RES_MATCH) 6071 if (ctx.ctx_res_w == RES_MATCH)
6090 break; /* we are in case's "word | word)" */ 6072 break; /* we are in case's "word | word)" */
@@ -6135,8 +6117,12 @@ static struct pipe *parse_stream(char **pstring,
6135 } 6117 }
6136 case ')': 6118 case ')':
6137#if ENABLE_HUSH_CASE 6119#if ENABLE_HUSH_CASE
6138 if (ctx.ctx_res_w == RES_MATCH) 6120 if (ctx.ctx_res_w == RES_MATCH) {
6139 goto case_semi; 6121 if (done_word(&ctx))
6122 goto parse_error_exitcode1;
6123 done_pipe(&ctx, PIPE_SEQ);
6124 goto new_cmd;
6125 }
6140#endif 6126#endif
6141 case '}': 6127 case '}':
6142 /* proper use of this character is caught by end_trigger: 6128 /* proper use of this character is caught by end_trigger:
@@ -6150,7 +6136,7 @@ static struct pipe *parse_stream(char **pstring,
6150 bb_error_msg_and_die("BUG: unexpected %c", ch); 6136 bb_error_msg_and_die("BUG: unexpected %c", ch);
6151 } 6137 }
6152 } /* while (1) */ 6138 } /* while (1) */
6153 6139 eof:
6154 /* Reached EOF */ 6140 /* Reached EOF */
6155 if (heredoc_cnt) { 6141 if (heredoc_cnt) {
6156 syntax_error_unterm_str("here document"); 6142 syntax_error_unterm_str("here document");
@@ -6165,9 +6151,8 @@ static struct pipe *parse_stream(char **pstring,
6165 goto parse_error_exitcode1; 6151 goto parse_error_exitcode1;
6166 } 6152 }
6167 6153
6168 if (done_word(&ctx)) { 6154 if (done_word(&ctx))
6169 goto parse_error_exitcode1; 6155 goto parse_error_exitcode1;
6170 }
6171 o_free_and_set_NULL(&ctx.word); 6156 o_free_and_set_NULL(&ctx.word);
6172 if (done_pipe(&ctx, PIPE_SEQ)) { 6157 if (done_pipe(&ctx, PIPE_SEQ)) {
6173 /* Testcase: sh -c 'date |' */ 6158 /* Testcase: sh -c 'date |' */
diff --git a/shell/hush_test/hush-misc/case2.right b/shell/hush_test/hush-misc/case2.right
new file mode 100644
index 000000000..4780199de
--- /dev/null
+++ b/shell/hush_test/hush-misc/case2.right
@@ -0,0 +1 @@
hush: syntax error: unexpected )
diff --git a/shell/hush_test/hush-misc/case2.tests b/shell/hush_test/hush-misc/case2.tests
new file mode 100755
index 000000000..b9475392e
--- /dev/null
+++ b/shell/hush_test/hush-misc/case2.tests
@@ -0,0 +1,6 @@
1# had a parser bug which incorrectly detected ";;"
2case w in
3a);
4b);;
5esac
6echo Should not be reached
diff --git a/shell/hush_test/hush-misc/case3.right b/shell/hush_test/hush-misc/case3.right
new file mode 100644
index 000000000..02a29ccbc
--- /dev/null
+++ b/shell/hush_test/hush-misc/case3.right
@@ -0,0 +1,2 @@
1Keyword-like word may be in pattern
2Ok:0
diff --git a/shell/hush_test/hush-misc/case3.tests b/shell/hush_test/hush-misc/case3.tests
new file mode 100755
index 000000000..63b9dfd99
--- /dev/null
+++ b/shell/hush_test/hush-misc/case3.tests
@@ -0,0 +1,8 @@
1if=if
2case if in
3if) echo "Keyword-like word may be in pattern";;
4fi) echo "Not reached";;
5# esac) echo "Not reached";; # not accepted by bash 5.2.15
6do) echo "Not reached"
7esac
8echo Ok:$?