hush: fix misparsing of "... do eval a= ...". Closes 3721
No binary (build failed)
—
(log yok)
| @@ -449,6 +449,15 @@ enum { | |
| /* Used for initialization: o_string foo = NULL_O_STRING; */ | |
| #define NULL_O_STRING { NULL } | |
| + | #ifndef debug_printf_parse |
| + | static const char *const assignment_flag[] = { |
| + | "MAYBE_ASSIGNMENT", |
| + | "DEFINITELY_ASSIGNMENT", |
| + | "NOT_ASSIGNMENT", |
| + | "WORD_IS_KEYWORD", |
| + | }; |
| + | #endif |
| + | |
| typedef struct in_str { | |
| const char *p; | |
| /* eof_flag=1: last char in ->p is really an EOF */ | |
| @@ -3033,24 +3042,24 @@ static const struct reserved_combo* match_reserved_word(o_string *word) | |
| */ | |
| static const struct reserved_combo reserved_list[] = { | |
| # if ENABLE_HUSH_IF | |
| - | { "!", RES_NONE, NOT_ASSIGNMENT , 0 }, |
| - | { "if", RES_IF, WORD_IS_KEYWORD, FLAG_THEN | FLAG_START }, |
| - | { "then", RES_THEN, WORD_IS_KEYWORD, FLAG_ELIF | FLAG_ELSE | FLAG_FI }, |
| - | { "elif", RES_ELIF, WORD_IS_KEYWORD, FLAG_THEN }, |
| - | { "else", RES_ELSE, WORD_IS_KEYWORD, FLAG_FI }, |
| - | { "fi", RES_FI, NOT_ASSIGNMENT , FLAG_END }, |
| + | { "!", RES_NONE, NOT_ASSIGNMENT , 0 }, |
| + | { "if", RES_IF, MAYBE_ASSIGNMENT, FLAG_THEN | FLAG_START }, |
| + | { "then", RES_THEN, MAYBE_ASSIGNMENT, FLAG_ELIF | FLAG_ELSE | FLAG_FI }, |
| + | { "elif", RES_ELIF, MAYBE_ASSIGNMENT, FLAG_THEN }, |
| + | { "else", RES_ELSE, MAYBE_ASSIGNMENT, FLAG_FI }, |
| + | { "fi", RES_FI, NOT_ASSIGNMENT , FLAG_END }, |
| # endif | |
| # if ENABLE_HUSH_LOOPS | |
| - | { "for", RES_FOR, NOT_ASSIGNMENT , FLAG_IN | FLAG_DO | FLAG_START }, |
| - | { "while", RES_WHILE, WORD_IS_KEYWORD, FLAG_DO | FLAG_START }, |
| - | { "until", RES_UNTIL, WORD_IS_KEYWORD, FLAG_DO | FLAG_START }, |
| - | { "in", RES_IN, NOT_ASSIGNMENT , FLAG_DO }, |
| - | { "do", RES_DO, WORD_IS_KEYWORD, FLAG_DONE }, |
| - | { "done", RES_DONE, NOT_ASSIGNMENT , FLAG_END }, |
| + | { "for", RES_FOR, NOT_ASSIGNMENT , FLAG_IN | FLAG_DO | FLAG_START }, |
| + | { "while", RES_WHILE, MAYBE_ASSIGNMENT, FLAG_DO | FLAG_START }, |
| + | { "until", RES_UNTIL, MAYBE_ASSIGNMENT, FLAG_DO | FLAG_START }, |
| + | { "in", RES_IN, NOT_ASSIGNMENT , FLAG_DO }, |
| + | { "do", RES_DO, MAYBE_ASSIGNMENT, FLAG_DONE }, |
| + | { "done", RES_DONE, NOT_ASSIGNMENT , FLAG_END }, |
| # endif | |
| # if ENABLE_HUSH_CASE | |
| - | { "case", RES_CASE, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_START }, |
| - | { "esac", RES_ESAC, NOT_ASSIGNMENT , FLAG_END }, |
| + | { "case", RES_CASE, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_START }, |
| + | { "esac", RES_ESAC, NOT_ASSIGNMENT , FLAG_END }, |
| # endif | |
| }; | |
| const struct reserved_combo *r; | |
| @@ -3116,6 +3125,7 @@ static int reserved_word(o_string *word, struct parse_context *ctx) | |
| ctx->ctx_res_w = r->res; | |
| ctx->old_flag = r->flag; | |
| word->o_assignment = r->assignment_flag; | |
| + | debug_printf_parse("word->o_assignment='%s'\n", assignment_flag[word->o_assignment]); |
| if (ctx->old_flag & FLAG_END) { | |
| struct parse_context *old; | |
| @@ -3182,18 +3192,6 @@ static int done_word(o_string *word, struct parse_context *ctx) | |
| debug_printf_parse("word stored in rd_filename: '%s'\n", word->data); | |
| ctx->pending_redirect = NULL; | |
| } else { | |
| - | /* If this word wasn't an assignment, next ones definitely |
| - | * can't be assignments. Even if they look like ones. */ |
| - | if (word->o_assignment != DEFINITELY_ASSIGNMENT |
| - | && word->o_assignment != WORD_IS_KEYWORD |
| - | ) { |
| - | word->o_assignment = NOT_ASSIGNMENT; |
| - | } else { |
| - | if (word->o_assignment == DEFINITELY_ASSIGNMENT) |
| - | command->assignment_cnt++; |
| - | word->o_assignment = MAYBE_ASSIGNMENT; |
| - | } |
| - | |
| #if HAS_KEYWORDS | |
| # if ENABLE_HUSH_CASE | |
| if (ctx->ctx_dsemicolon | |
| @@ -3213,8 +3211,9 @@ static int done_word(o_string *word, struct parse_context *ctx) | |
| && ctx->ctx_res_w != RES_CASE | |
| # endif | |
| ) { | |
| - | debug_printf_parse("checking '%s' for reserved-ness\n", word->data); |
| - | if (reserved_word(word, ctx)) { |
| + | int reserved = reserved_word(word, ctx); |
| + | debug_printf_parse("checking for reserved-ness: %d\n", reserved); |
| + | if (reserved) { |
| o_reset_to_empty_unquoted(word); | |
| debug_printf_parse("done_word return %d\n", | |
| (ctx->ctx_res_w == RES_SNTX)); | |
| @@ -3235,6 +3234,23 @@ static int done_word(o_string *word, struct parse_context *ctx) | |
| "groups and arglists don't mix\n"); | |
| return 1; | |
| } | |
| + | |
| + | /* If this word wasn't an assignment, next ones definitely |
| + | * can't be assignments. Even if they look like ones. */ |
| + | if (word->o_assignment != DEFINITELY_ASSIGNMENT |
| + | && word->o_assignment != WORD_IS_KEYWORD |
| + | ) { |
| + | word->o_assignment = NOT_ASSIGNMENT; |
| + | } else { |
| + | if (word->o_assignment == DEFINITELY_ASSIGNMENT) { |
| + | command->assignment_cnt++; |
| + | debug_printf_parse("++assignment_cnt=%d\n", command->assignment_cnt); |
| + | } |
| + | debug_printf_parse("word->o_assignment was:'%s'\n", assignment_flag[word->o_assignment]); |
| + | word->o_assignment = MAYBE_ASSIGNMENT; |
| + | } |
| + | debug_printf_parse("word->o_assignment='%s'\n", assignment_flag[word->o_assignment]); |
| + | |
| if (word->has_quoted_part | |
| /* optimization: and if it's ("" or '') or ($v... or `cmd`...): */ | |
| && (word->data[0] == '\0' || word->data[0] == SPECIAL_VAR_SYMBOL) | |
| @@ -4259,6 +4275,7 @@ static struct pipe *parse_stream(char **pstring, | |
| && is_well_formed_var_name(dest.data, '=') | |
| ) { | |
| dest.o_assignment = DEFINITELY_ASSIGNMENT; | |
| + | debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); |
| } | |
| continue; | |
| } | |
| @@ -4308,6 +4325,7 @@ static struct pipe *parse_stream(char **pstring, | |
| heredoc_cnt = 0; | |
| } | |
| dest.o_assignment = MAYBE_ASSIGNMENT; | |
| + | debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); |
| ch = ';'; | |
| /* note: if (is_blank) continue; | |
| * will still trigger for us */ | |
| @@ -4357,6 +4375,7 @@ static struct pipe *parse_stream(char **pstring, | |
| } | |
| done_pipe(&ctx, PIPE_SEQ); | |
| dest.o_assignment = MAYBE_ASSIGNMENT; | |
| + | debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); |
| /* Do we sit outside of any if's, loops or case's? */ | |
| if (!HAS_KEYWORDS | |
| IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0)) | |
| @@ -4463,6 +4482,7 @@ static struct pipe *parse_stream(char **pstring, | |
| /* ch is a special char and thus this word | |
| * cannot be an assignment */ | |
| dest.o_assignment = NOT_ASSIGNMENT; | |
| + | debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); |
| } | |
| /* Note: nommu_addchr(&ctx.as_string, ch) is already done */ | |
| @@ -4561,6 +4581,7 @@ static struct pipe *parse_stream(char **pstring, | |
| /* We just finished a cmd. New one may start | |
| * with an assignment */ | |
| dest.o_assignment = MAYBE_ASSIGNMENT; | |
| + | debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); |
| break; | |
| case '&': | |
| if (done_word(&dest, &ctx)) { | |
| @@ -0,0 +1 @@ | |
| + | Done:0 |
| @@ -0,0 +1,3 @@ | |
| + | # There was a bug where we misinterpreted assignments after 'do': |
| + | for i in 1; do eval b=; done |
| + | echo Done:$? |