diff options
| author | Denys Vlasenko <vda.linux@googlemail.com> | 2025-08-14 02:30:32 +0200 |
|---|---|---|
| committer | Denys Vlasenko <vda.linux@googlemail.com> | 2025-08-14 02:30:32 +0200 |
| commit | 2bb8b9b2a4eb057208ae3a131aa626901d9f905a (patch) | |
| tree | 0be59353c7973ddba8604ce092e8061fdce44db6 /shell | |
| parent | 438b92efdd73d0552ecaa938aa33de7f445bb87f (diff) | |
| download | busybox-w32-2bb8b9b2a4eb057208ae3a131aa626901d9f905a.tar.gz busybox-w32-2bb8b9b2a4eb057208ae3a131aa626901d9f905a.tar.bz2 busybox-w32-2bb8b9b2a4eb057208ae3a131aa626901d9f905a.zip | |
hush: remove two strchr's from the hottest parsing loop
function old new delta
parse_stream 2572 2566 -6
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
Diffstat (limited to 'shell')
| -rw-r--r-- | shell/hush.c | 173 |
1 files changed, 85 insertions, 88 deletions
diff --git a/shell/hush.c b/shell/hush.c index 808adf7b1..2155dda8e 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
| @@ -5119,7 +5119,6 @@ static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsign | |||
| 5119 | # if BB_MMU | 5119 | # if BB_MMU |
| 5120 | #define parse_dollar_squote(as_string, dest, input) \ | 5120 | #define parse_dollar_squote(as_string, dest, input) \ |
| 5121 | parse_dollar_squote(dest, input) | 5121 | parse_dollar_squote(dest, input) |
| 5122 | #define as_string NULL | ||
| 5123 | # endif | 5122 | # endif |
| 5124 | static int parse_dollar_squote(o_string *as_string, o_string *dest, struct in_str *input) | 5123 | static int parse_dollar_squote(o_string *as_string, o_string *dest, struct in_str *input) |
| 5125 | { | 5124 | { |
| @@ -5204,7 +5203,6 @@ static int parse_dollar_squote(o_string *as_string, o_string *dest, struct in_st | |||
| 5204 | } | 5203 | } |
| 5205 | 5204 | ||
| 5206 | return 1; | 5205 | return 1; |
| 5207 | # undef as_string | ||
| 5208 | } | 5206 | } |
| 5209 | #else | 5207 | #else |
| 5210 | # define parse_dollar_squote(as_string, dest, input) 0 | 5208 | # define parse_dollar_squote(as_string, dest, input) 0 |
| @@ -5482,7 +5480,6 @@ static int parse_dollar(o_string *as_string, | |||
| 5482 | #if BB_MMU | 5480 | #if BB_MMU |
| 5483 | #define encode_string(as_string, dest, input, dquote_end) \ | 5481 | #define encode_string(as_string, dest, input, dquote_end) \ |
| 5484 | encode_string(dest, input, dquote_end) | 5482 | encode_string(dest, input, dquote_end) |
| 5485 | #define as_string NULL | ||
| 5486 | #endif | 5483 | #endif |
| 5487 | static int encode_string(o_string *as_string, | 5484 | static int encode_string(o_string *as_string, |
| 5488 | o_string *dest, | 5485 | o_string *dest, |
| @@ -5564,7 +5561,6 @@ static int encode_string(o_string *as_string, | |||
| 5564 | o_addchr(dest, SPECIAL_VAR_SYMBOL); | 5561 | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| 5565 | } | 5562 | } |
| 5566 | goto again; | 5563 | goto again; |
| 5567 | #undef as_string | ||
| 5568 | } | 5564 | } |
| 5569 | 5565 | ||
| 5570 | /* | 5566 | /* |
| @@ -5605,8 +5601,7 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5605 | 5601 | ||
| 5606 | heredoc_cnt = 0; | 5602 | heredoc_cnt = 0; |
| 5607 | while (1) { | 5603 | while (1) { |
| 5608 | const char *is_blank; | 5604 | int is_blank; |
| 5609 | const char *is_special; | ||
| 5610 | int ch; | 5605 | int ch; |
| 5611 | int next; | 5606 | int next; |
| 5612 | int redir_fd; | 5607 | int redir_fd; |
| @@ -5623,11 +5618,8 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5623 | continue; | 5618 | continue; |
| 5624 | } | 5619 | } |
| 5625 | #endif | 5620 | #endif |
| 5626 | if (ch == EOF) | ||
| 5627 | break; | ||
| 5628 | |||
| 5629 | /* Handle "'" and "\" first, as they won't play nice with | 5621 | /* Handle "'" and "\" first, as they won't play nice with |
| 5630 | * i_peek_and_eat_bkslash_nl() anyway: | 5622 | * i_peek_and_eat_bkslash_nl(): |
| 5631 | * echo z\\ | 5623 | * echo z\\ |
| 5632 | * and | 5624 | * and |
| 5633 | * echo '\ | 5625 | * echo '\ |
| @@ -5660,13 +5652,14 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5660 | o_addchr(&ctx.word, ch); | 5652 | o_addchr(&ctx.word, ch); |
| 5661 | continue; /* get next char */ | 5653 | continue; /* get next char */ |
| 5662 | } | 5654 | } |
| 5655 | if (ch == EOF) | ||
| 5656 | break; | ||
| 5663 | nommu_addchr(&ctx.as_string, ch); | 5657 | nommu_addchr(&ctx.as_string, ch); |
| 5664 | if (ch == '\'') { | 5658 | if (ch == '\'') { |
| 5665 | ctx.word.has_quoted_part = 1; | 5659 | ctx.word.has_quoted_part = 1; |
| 5666 | next = i_getch(input); | 5660 | next = i_getch(input); |
| 5667 | if (next == '\'' && !ctx.pending_redirect/*why?*/) | 5661 | if (next == '\'' && !ctx.pending_redirect/*why?*/) |
| 5668 | goto insert_empty_quoted_str_marker; | 5662 | goto insert_empty_quoted_str_marker; |
| 5669 | |||
| 5670 | ch = next; | 5663 | ch = next; |
| 5671 | while (1) { | 5664 | while (1) { |
| 5672 | if (ch == EOF) { | 5665 | if (ch == EOF) { |
| @@ -5688,73 +5681,38 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5688 | } | 5681 | } |
| 5689 | 5682 | ||
| 5690 | next = '\0'; | 5683 | next = '\0'; |
| 5691 | if (ch != '\n') | 5684 | is_blank = 1; |
| 5692 | next = i_peek_and_eat_bkslash_nl(input); | 5685 | /* If '\n', must not peek (peeking past '\n' provokes line editing) */ |
| 5693 | 5686 | if (ch == '\n') | |
| 5694 | is_special = "{}<>&|();#" /* special outside of "str" */ | 5687 | /* had to test for '\n' anyway, can also jump directly to newline handling */ |
| 5695 | "$\"" IF_HUSH_TICK("`") /* always special */ | 5688 | goto ch_is_newline; |
| 5696 | SPECIAL_VAR_SYMBOL_STR; | 5689 | next = i_peek_and_eat_bkslash_nl(input); |
| 5697 | #if defined(CMD_TEST2_SINGLEWORD_NOGLOB) | 5690 | |
| 5698 | if (ctx.command->cmd_type == CMD_TEST2_SINGLEWORD_NOGLOB) { | 5691 | is_blank = (ch == ' ' || ch == '\t'); |
| 5699 | /* In [[ ]], {}<>&|() are not special */ | ||
| 5700 | is_special += 8; | ||
| 5701 | } else | ||
| 5702 | #endif | ||
| 5703 | /* Are { and } special here? */ | ||
| 5704 | if (ctx.command->argv /* word [word]{... - non-special */ | ||
| 5705 | || !IS_NULL_WORD(ctx.word) /* word{... ""{... - non-special */ | ||
| 5706 | || (next != ';' /* }; - special */ | ||
| 5707 | && next != ')' /* }) - special */ | ||
| 5708 | && next != '(' /* {( - special */ | ||
| 5709 | && next != '&' /* }& and }&& ... - special */ | ||
| 5710 | && next != '|' /* }|| ... - special */ | ||
| 5711 | && !strchr(defifs, next) /* {word - non-special */ | ||
| 5712 | ) | ||
| 5713 | ) { | ||
| 5714 | /* They are not special, skip "{}" */ | ||
| 5715 | is_special += 2; | ||
| 5716 | } | ||
| 5717 | is_special = strchr(is_special, ch); | ||
| 5718 | is_blank = strchr(defifs, ch); | ||
| 5719 | |||
| 5720 | if (!is_special && !is_blank) { /* ordinary char */ | ||
| 5721 | ordinary_char: | ||
| 5722 | o_addQchr(&ctx.word, ch); | ||
| 5723 | if ((ctx.is_assignment == MAYBE_ASSIGNMENT | ||
| 5724 | || ctx.is_assignment == WORD_IS_KEYWORD) | ||
| 5725 | && ch == '=' | ||
| 5726 | && endofname(ctx.word.data)[0] == '=' | ||
| 5727 | ) { | ||
| 5728 | ctx.is_assignment = DEFINITELY_ASSIGNMENT; | ||
| 5729 | debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); | ||
| 5730 | } | ||
| 5731 | continue; | ||
| 5732 | } | ||
| 5733 | |||
| 5734 | if (is_blank) { | 5692 | if (is_blank) { |
| 5735 | #if ENABLE_HUSH_LINENO_VAR | 5693 | #if ENABLE_HUSH_LINENO_VAR |
| 5736 | /* Case: | 5694 | /* "while ...; do<whitespace><newline> |
| 5737 | * "while ...; do<whitespace><newline> | 5695 | * CMD" |
| 5738 | * cmd ..." | 5696 | * would think that CMD starts in <whitespace> - |
| 5739 | * would think that "cmd" starts in <whitespace> - | ||
| 5740 | * i.e., at the previous line. | 5697 | * i.e., at the previous line. |
| 5741 | * We need to skip all whitespace before newlines. | 5698 | * Need to skip whitespace up to next newline (and eat it) |
| 5699 | * or not-whitespace (and do not eat it). | ||
| 5742 | */ | 5700 | */ |
| 5743 | while (ch != '\n') { | 5701 | do { |
| 5744 | next = i_peek(input); | 5702 | next = i_peek(input); |
| 5745 | if (next != ' ' && next != '\t' && next != '\n') | 5703 | if (next != ' ' && next != '\t' && next != '\n') |
| 5746 | break; /* next char is not ws */ | 5704 | break; /* next char is not ws */ |
| 5747 | ch = i_getch(input); | 5705 | ch = i_getch(input); |
| 5748 | } | 5706 | } while (ch != '\n'); |
| 5749 | /* ch == last eaten whitespace char */ | ||
| 5750 | #endif | 5707 | #endif |
| 5708 | ch_is_newline: | ||
| 5751 | if (done_word(&ctx)) | 5709 | if (done_word(&ctx)) |
| 5752 | goto parse_error_exitcode1; | 5710 | goto parse_error_exitcode1; |
| 5753 | if (ch == '\n') { | 5711 | if (ch == '\n') { |
| 5754 | /* Is this a case when newline is simply ignored? | 5712 | /* Is this a case when newline is simply ignored? |
| 5755 | * Some examples: | 5713 | * Some examples: |
| 5756 | * "cmd | <newline> cmd ..." | 5714 | * "CMD | <newline> CMD ..." |
| 5757 | * "case ... in <newline> word) ..." | 5715 | * "case ... in <newline> PATTERN) ..." |
| 5758 | */ | 5716 | */ |
| 5759 | if (IS_NULL_CMD(ctx.command) | 5717 | if (IS_NULL_CMD(ctx.command) |
| 5760 | && IS_NULL_WORD(ctx.word) | 5718 | && IS_NULL_WORD(ctx.word) |
| @@ -5764,18 +5722,18 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5764 | * Without check #1, interactive shell | 5722 | * Without check #1, interactive shell |
| 5765 | * ignores even bare <newline>, | 5723 | * ignores even bare <newline>, |
| 5766 | * and shows the continuation prompt: | 5724 | * and shows the continuation prompt: |
| 5767 | * ps1_prompt$ <enter> | 5725 | * ps1$ <enter> |
| 5768 | * ps2> _ <=== wrong, should be ps1 | 5726 | * ps2> _ <=== wrong, should be ps1 |
| 5769 | * Without check #2, "cmd & <newline>" | 5727 | * Without check #2, "CMD & <newline>" |
| 5770 | * is similarly mistreated. | 5728 | * is similarly mistreated. |
| 5771 | * (BTW, this makes "cmd & cmd" | 5729 | * (BTW, this makes "CMD & CMD" |
| 5772 | * and "cmd && cmd" non-orthogonal. | 5730 | * and "CMD && CMD" non-orthogonal. |
| 5773 | * Really, ask yourself, why | 5731 | * Really, ask yourself, why |
| 5774 | * "cmd && <newline>" doesn't start | 5732 | * "CMD && <newline>" doesn't start |
| 5775 | * cmd but waits for more input? | 5733 | * CMD but waits for more input? |
| 5776 | * The only reason is that it might be | 5734 | * The only reason is that it might be |
| 5777 | * a "cmd1 && <nl> cmd2 &" construct, | 5735 | * a "CMD1 && <nl> CMD2 &" construct: |
| 5778 | * cmd1 may need to run in BG). | 5736 | * CMD1 may need to run in BG). |
| 5779 | */ | 5737 | */ |
| 5780 | pi = ctx.list_head; | 5738 | pi = ctx.list_head; |
| 5781 | if (pi->num_cmds != 0 /* check #1 */ | 5739 | if (pi->num_cmds != 0 /* check #1 */ |
| @@ -5797,11 +5755,51 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5797 | ch = ';'; | 5755 | ch = ';'; |
| 5798 | /* note: if (is_blank) continue; | 5756 | /* note: if (is_blank) continue; |
| 5799 | * will still trigger for us */ | 5757 | * will still trigger for us */ |
| 5758 | } /* if ch == '\n */ | ||
| 5759 | } else { | ||
| 5760 | const char *is_special; | ||
| 5761 | |||
| 5762 | is_special = "{}<>&|();#" /* special outside of "str" */ | ||
| 5763 | "$\"" IF_HUSH_TICK("`") /* always special */ | ||
| 5764 | SPECIAL_VAR_SYMBOL_STR; | ||
| 5765 | #if defined(CMD_TEST2_SINGLEWORD_NOGLOB) | ||
| 5766 | if (ctx.command->cmd_type == CMD_TEST2_SINGLEWORD_NOGLOB) { | ||
| 5767 | /* In [[ ]], {}<>&|() are not special */ | ||
| 5768 | is_special += 8; | ||
| 5769 | } else | ||
| 5770 | #endif | ||
| 5771 | /* Are { and } special here? */ | ||
| 5772 | if (ctx.command->argv /* WORD [WORD]{... - non-special */ | ||
| 5773 | || !IS_NULL_WORD(ctx.word) /* WORD{... ""{... - non-special */ | ||
| 5774 | || (next != ';' /* }; - special */ | ||
| 5775 | && next != ')' /* }) - special */ | ||
| 5776 | && next != '(' /* {( - special */ | ||
| 5777 | && next != '&' /* }& and }&& ... - special */ | ||
| 5778 | && next != '|' /* }|| ... - special */ | ||
| 5779 | && !strchr(defifs, next) /* {WORD - non-special */ | ||
| 5780 | ) | ||
| 5781 | ) { | ||
| 5782 | /* They are not special, skip "{}" */ | ||
| 5783 | is_special += 2; | ||
| 5784 | } | ||
| 5785 | is_special = strchr(is_special, ch); | ||
| 5786 | if (!is_special) { /* ordinary char */ | ||
| 5787 | ordinary_char: | ||
| 5788 | o_addQchr(&ctx.word, ch); | ||
| 5789 | if ((ctx.is_assignment == MAYBE_ASSIGNMENT | ||
| 5790 | || ctx.is_assignment == WORD_IS_KEYWORD) | ||
| 5791 | && ch == '=' | ||
| 5792 | && endofname(ctx.word.data)[0] == '=' | ||
| 5793 | ) { | ||
| 5794 | ctx.is_assignment = DEFINITELY_ASSIGNMENT; | ||
| 5795 | debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); | ||
| 5796 | } | ||
| 5797 | continue; | ||
| 5800 | } | 5798 | } |
| 5801 | } | 5799 | } |
| 5802 | 5800 | ||
| 5803 | /* "cmd}" or "cmd }..." without semicolon or &: | 5801 | /* "CMD}" or "CMD }..." without semicolon or &: |
| 5804 | * } is an ordinary char in this case, even inside { cmd; } | 5802 | * } is an ordinary char in this case, even inside { CMD; } |
| 5805 | * Pathological example: { ""}; } should run "}" command. | 5803 | * Pathological example: { ""}; } should run "}" command. |
| 5806 | */ | 5804 | */ |
| 5807 | if (ch == '}') { | 5805 | if (ch == '}') { |
| @@ -5809,9 +5807,9 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5809 | /* word} or ""} */ | 5807 | /* word} or ""} */ |
| 5810 | goto ordinary_char; | 5808 | goto ordinary_char; |
| 5811 | } | 5809 | } |
| 5812 | if (!IS_NULL_CMD(ctx.command)) { /* cmd } */ | 5810 | if (!IS_NULL_CMD(ctx.command)) { /* CMD } */ |
| 5813 | /* Generally, there should be semicolon: "cmd; }" | 5811 | /* Generally, there should be semicolon: "CMD; }" |
| 5814 | * However, bash allows to omit it if "cmd" is | 5812 | * However, bash allows to omit it if "CMD" is |
| 5815 | * a group. Examples: | 5813 | * a group. Examples: |
| 5816 | * { { echo 1; } } | 5814 | * { { echo 1; } } |
| 5817 | * {(echo 1)} | 5815 | * {(echo 1)} |
| @@ -5823,8 +5821,8 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5823 | goto term_group; | 5821 | goto term_group; |
| 5824 | goto ordinary_char; | 5822 | goto ordinary_char; |
| 5825 | } | 5823 | } |
| 5826 | if (!IS_NULL_PIPE(ctx.pipe)) /* cmd | } */ | 5824 | if (!IS_NULL_PIPE(ctx.pipe)) /* CMD | } */ |
| 5827 | /* Can't be an end of {cmd}, skip the check */ | 5825 | /* Can't be an end of {CMD}, skip the check */ |
| 5828 | goto skip_end_trigger; | 5826 | goto skip_end_trigger; |
| 5829 | /* else: } does terminate a group */ | 5827 | /* else: } does terminate a group */ |
| 5830 | } | 5828 | } |
| @@ -5876,7 +5874,7 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5876 | } | 5874 | } |
| 5877 | } | 5875 | } |
| 5878 | 5876 | ||
| 5879 | if (is_blank) | 5877 | if (is_blank) /* space, tab or newline? */ |
| 5880 | continue; | 5878 | continue; |
| 5881 | 5879 | ||
| 5882 | /* Catch <, > before deciding whether this word is | 5880 | /* Catch <, > before deciding whether this word is |
| @@ -5928,7 +5926,7 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5928 | continue; /* get next char */ | 5926 | continue; /* get next char */ |
| 5929 | case '#': | 5927 | case '#': |
| 5930 | if (IS_NULL_WORD(ctx.word)) { | 5928 | if (IS_NULL_WORD(ctx.word)) { |
| 5931 | /* skip "#comment" */ | 5929 | /* skip "#COMMENT" */ |
| 5932 | /* note: we do not add it to &ctx.as_string */ | 5930 | /* note: we do not add it to &ctx.as_string */ |
| 5933 | /* TODO: in bash: | 5931 | /* TODO: in bash: |
| 5934 | * comment inside $() goes to the next \n, even inside quoted string (!): | 5932 | * comment inside $() goes to the next \n, even inside quoted string (!): |
| @@ -6068,7 +6066,7 @@ static struct pipe *parse_stream(char **pstring, | |||
| 6068 | goto parse_error_exitcode1; | 6066 | goto parse_error_exitcode1; |
| 6069 | #if ENABLE_HUSH_CASE | 6067 | #if ENABLE_HUSH_CASE |
| 6070 | if (ctx.ctx_res_w == RES_MATCH) | 6068 | if (ctx.ctx_res_w == RES_MATCH) |
| 6071 | break; /* we are in case's "word | word)" */ | 6069 | break; /* we are in case's "WORD | WORD)" */ |
| 6072 | #endif | 6070 | #endif |
| 6073 | if (next == '|') { /* || */ | 6071 | if (next == '|') { /* || */ |
| 6074 | ch = i_getch(input); | 6072 | ch = i_getch(input); |
| @@ -6095,10 +6093,10 @@ static struct pipe *parse_stream(char **pstring, | |||
| 6095 | goto new_cmd; | 6093 | goto new_cmd; |
| 6096 | case '(': | 6094 | case '(': |
| 6097 | #if ENABLE_HUSH_CASE | 6095 | #if ENABLE_HUSH_CASE |
| 6098 | /* "case... in [(]word)..." - skip '(' */ | 6096 | /* "case... in [(]PATTERN)..." - skip '(' */ |
| 6099 | if (ctx.ctx_res_w == RES_MATCH | 6097 | if (ctx.ctx_res_w == RES_MATCH |
| 6100 | && ctx.command->argv == NULL /* not (word|(... */ | 6098 | && ctx.command->argv == NULL /* not (PATTERN|(... */ |
| 6101 | && IS_NULL_WORD(ctx.word) /* not word(... or ""(... */ | 6099 | && IS_NULL_WORD(ctx.word) /* not PATTERN(... or ""(... */ |
| 6102 | ) { | 6100 | ) { |
| 6103 | continue; /* get next char */ | 6101 | continue; /* get next char */ |
| 6104 | } | 6102 | } |
| @@ -6106,9 +6104,8 @@ static struct pipe *parse_stream(char **pstring, | |||
| 6106 | /* fall through */ | 6104 | /* fall through */ |
| 6107 | case '{': { | 6105 | case '{': { |
| 6108 | int n = parse_group(&ctx, input, ch); | 6106 | int n = parse_group(&ctx, input, ch); |
| 6109 | if (n < 0) { | 6107 | if (n < 0) |
| 6110 | goto parse_error_exitcode1; | 6108 | goto parse_error_exitcode1; |
| 6111 | } | ||
| 6112 | debug_printf_heredoc("parse_group done, needs heredocs:%d\n", n); | 6109 | debug_printf_heredoc("parse_group done, needs heredocs:%d\n", n); |
| 6113 | heredoc_cnt += n; | 6110 | heredoc_cnt += n; |
| 6114 | goto new_cmd; | 6111 | goto new_cmd; |
| @@ -6123,7 +6120,7 @@ static struct pipe *parse_stream(char **pstring, | |||
| 6123 | } | 6120 | } |
| 6124 | #endif | 6121 | #endif |
| 6125 | case '}': | 6122 | case '}': |
| 6126 | /* proper use of this character is caught by end_trigger: | 6123 | /* Proper use of this character is caught by end_trigger: |
| 6127 | * if we see {, we call parse_group(..., end_trigger='}') | 6124 | * if we see {, we call parse_group(..., end_trigger='}') |
| 6128 | * and it will match } earlier (not here). */ | 6125 | * and it will match } earlier (not here). */ |
| 6129 | G.last_exitcode = 2; | 6126 | G.last_exitcode = 2; |
