From 2bdec03def3054bbd80d5fd23e9bb0c026f85fc4 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 18 Aug 2025 13:14:26 +0200 Subject: hush: disentangle keyword detection, no logic changes function old new delta done_word 790 766 -24 Signed-off-by: Denys Vlasenko --- shell/hush.c | 52 +++++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index 00887d45f..f776a6468 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -3947,7 +3947,7 @@ static void debug_print_tree(struct pipe *pi, int lvl) # endif # if ENABLE_HUSH_CASE [RES_CASE ] = "CASE" , - [RES_CASE_IN ] = "CASE_IN" , + [RES_CASE_IN] = "CASE_IN" , [RES_MATCH] = "MATCH", [RES_CASE_BODY] = "CASE_BODY", [RES_ESAC ] = "ESAC" , @@ -4289,11 +4289,22 @@ static const struct reserved_combo* reserved_word(struct parse_context *ctx) # endif const struct reserved_combo *r; - if (ctx->word.has_quoted_part) - return 0; r = match_reserved_word(ctx->word.data); if (!r) return r; /* NULL */ +# if ENABLE_HUSH_CASE /* "case" syntax has a curveball */ + if (ctx->ctx_res_w == RES_MATCH + && r->res != RES_ESAC + ) { + /* We are at WORD in ";; WORD" or "case .. in WORD". + * Here, only "esac" is a keyword. + * Else WORD is a case pattern, can be keyword-like: + * if) echo got_if;; + * is allowed. + */ + return NULL; + } +# endif debug_printf("found reserved word %s, res %d\n", r->literal, r->res); # if ENABLE_HUSH_CASE @@ -4309,7 +4320,7 @@ static const struct reserved_combo* reserved_word(struct parse_context *ctx) return r; } # endif - if (ctx->pipe->cmds != ctx->command /* bash disallows: nice | ! cat */ + if (ctx->pipe->num_cmds != 0 /* bash disallows: nice | ! cat */ /* || ctx->pipe->pi_inverted - bash used to disallow "! ! true" bash 5.2.15 allows it */ ) { syntax_error_unexpected_ch('!'); @@ -4443,36 +4454,26 @@ static int done_word(struct parse_context *ctx) } #if HAS_KEYWORDS -# if ENABLE_HUSH_CASE - if (ctx->ctx_res_w == RES_MATCH - && (ctx->word.has_quoted_part - || strcmp(ctx->word.data, "esac") != 0 - ) - ) { /* ";; WORD" but not ";; esac" */ - /* Do not match WORD as keyword: - * the WORD is a case match, can be keyword-like: - * if) echo got_if;; - * is allowed. - */ - } else -# endif # if defined(CMD_TEST2_SINGLEWORD_NOGLOB) if (command->cmd_type == CMD_TEST2_SINGLEWORD_NOGLOB && !ctx->word.has_quoted_part && strcmp(ctx->word.data, "]]") == 0 ) { - /* allow "[[ ]] >file" etc */ + /* End test2-specific parsing rules */ + /* Allow "[[ ]] >file" etc (> is a redirect symbol again) */ command->cmd_type = CMD_SINGLEWORD_NOGLOB; } else # endif - if (!command->argv /* if it's the first word of command... */ - && !command->redirects /* no redirects yet... disallows: argv /* if it's the first word of command... */ + && !ctx->word.has_quoted_part /* ""WORD never matches any keywords */ + && !command->redirects /* no redirects yet... disallows: ctx_res_w != RES_FOR /* not after FOR or IN */ + && ctx->ctx_res_w != RES_FOR /* not after "for" or "in" */ && ctx->ctx_res_w != RES_IN # endif # if ENABLE_HUSH_CASE - && ctx->ctx_res_w != RES_CASE /* not after CASE */ + && ctx->ctx_res_w != RES_CASE /* not after "case" */ # endif ) { const struct reserved_combo *reserved; @@ -4481,10 +4482,10 @@ static int done_word(struct parse_context *ctx) if (reserved) { # if ENABLE_HUSH_LINENO_VAR /* Case: - * "while ...; do - * cmd ..." + * while ...; do + * CMD * If we don't close the pipe _now_, immediately after "do", lineno logic - * sees "cmd" as starting at "do" - i.e., at the previous line. + * sees CMD as starting at "do" - i.e., at the previous line. */ if (0 IF_HUSH_IF(|| reserved->res == RES_THEN) @@ -4502,6 +4503,7 @@ static int done_word(struct parse_context *ctx) } # if defined(CMD_TEST2_SINGLEWORD_NOGLOB) if (strcmp(ctx->word.data, "[[") == 0) { + /* Inside [[ ]], parsing rules are different */ command->cmd_type = CMD_TEST2_SINGLEWORD_NOGLOB; } else # endif -- cgit v1.2.3-55-g6feb