hush: fix remaining known two bugs with IFS expansion. Closes 4027.

function                                             old     new   delta
expand_vars_to_list                                 1054    1140     +86
parse_stream                                        2425    2479     +54
expand_on_ifs                                        258     310     +52
builtin_umask                                        133     132      -1
done_word                                            820     779     -41
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 3/2 up/down: 192/-42)           Total: 150 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Denys Vlasenko 2011-08-01 18:16:43 +02:00
parent 4fb53fb08c
commit 6e42b89b8d
3 changed files with 61 additions and 30 deletions

View File

@ -3265,14 +3265,6 @@ static int done_word(o_string *word, struct parse_context *ctx)
) { ) {
p += 3; p += 3;
} }
if (p == word->data || p[0] != '\0') {
/* saw no "$@", or not only "$@" but some
* real text is there too */
/* insert "empty variable" reference, this makes
* e.g. "", $empty"" etc to not disappear */
o_addchr(word, SPECIAL_VAR_SYMBOL);
o_addchr(word, SPECIAL_VAR_SYMBOL);
}
} }
command->argv = add_string_to_strings(command->argv, xstrdup(word->data)); command->argv = add_string_to_strings(command->argv, xstrdup(word->data));
debug_print_strings("word appended to argv", command->argv); debug_print_strings("word appended to argv", command->argv);
@ -4516,20 +4508,30 @@ static struct pipe *parse_stream(char **pstring,
break; break;
case '\'': case '\'':
dest.has_quoted_part = 1; dest.has_quoted_part = 1;
while (1) { if (next == '\'' && !ctx.pending_redirect) {
ch = i_getch(input); insert_empty_quoted_str_marker:
if (ch == EOF) { nommu_addchr(&ctx.as_string, next);
syntax_error_unterm_ch('\''); i_getch(input); /* eat second ' */
goto parse_error; o_addchr(&dest, SPECIAL_VAR_SYMBOL);
o_addchr(&dest, SPECIAL_VAR_SYMBOL);
} else {
while (1) {
ch = i_getch(input);
if (ch == EOF) {
syntax_error_unterm_ch('\'');
goto parse_error;
}
nommu_addchr(&ctx.as_string, ch);
if (ch == '\'')
break;
o_addqchr(&dest, ch);
} }
nommu_addchr(&ctx.as_string, ch);
if (ch == '\'')
break;
o_addqchr(&dest, ch);
} }
break; break;
case '"': case '"':
dest.has_quoted_part = 1; dest.has_quoted_part = 1;
if (next == '"' && !ctx.pending_redirect)
goto insert_empty_quoted_str_marker;
if (dest.o_assignment == NOT_ASSIGNMENT) if (dest.o_assignment == NOT_ASSIGNMENT)
dest.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS; dest.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS;
if (!encode_string(&ctx.as_string, &dest, input, '"', /*process_bkslash:*/ 1)) if (!encode_string(&ctx.as_string, &dest, input, '"', /*process_bkslash:*/ 1))
@ -4751,9 +4753,14 @@ static void o_addblock_duplicate_backslash(o_string *o, const char *str, int len
/* Store given string, finalizing the word and starting new one whenever /* Store given string, finalizing the word and starting new one whenever
* we encounter IFS char(s). This is used for expanding variable values. * we encounter IFS char(s). This is used for expanding variable values.
* End-of-string does NOT finalize word: think about 'echo -$VAR-' */ * End-of-string does NOT finalize word: think about 'echo -$VAR-'.
static int expand_on_ifs(o_string *output, int n, const char *str) * Return in *ended_with_ifs:
* 1 - ended with IFS char, else 0 (this includes case of empty str).
*/
static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const char *str)
{ {
int last_is_ifs = 0;
while (1) { while (1) {
int word_len; int word_len;
@ -4774,27 +4781,36 @@ static int expand_on_ifs(o_string *output, int n, const char *str)
/*o_addblock(output, str, word_len); - WRONG: "v='\*'; echo Z$v" prints "Z*" instead of "Z\*" */ /*o_addblock(output, str, word_len); - WRONG: "v='\*'; echo Z$v" prints "Z*" instead of "Z\*" */
/*o_addqblock(output, str, word_len); - WRONG: "v='*'; echo Z$v" prints "Z*" instead of Z* files */ /*o_addqblock(output, str, word_len); - WRONG: "v='*'; echo Z$v" prints "Z*" instead of Z* files */
} }
last_is_ifs = 0;
str += word_len; str += word_len;
if (!*str) /* EOL - do not finalize word */ if (!*str) /* EOL - do not finalize word */
break; break;
goto finalize; /* optimization (can just fall thru) */
} }
/* Case "v=' a'; echo ''$v": we do need to finalize empty word */
/* We know str here points to at least one IFS char */
last_is_ifs = 1;
str += strspn(str, G.ifs); /* skip IFS chars */
if (!*str) /* EOL - do not finalize word */
break;
/* Start new word... but not always! */
/* Case "v=' a'; echo ''$v": we do need to finalize empty word: */
if (output->has_quoted_part if (output->has_quoted_part
/* Case "v=' a'; echo $v": /* Case "v=' a'; echo $v":
* here nothing precedes the space in $v expansion, * here nothing precedes the space in $v expansion,
* therefore we should not finish the word * therefore we should not finish the word
* (IOW: if there *is* word to finalize, only then do it) * (IOW: if there *is* word to finalize, only then do it):
*/ */
|| (output->length && output->data[output->length - 1]) || (n > 0 && output->data[output->length - 1])
) { ) {
finalize:
o_addchr(output, '\0'); o_addchr(output, '\0');
debug_print_list("expand_on_ifs", output, n); debug_print_list("expand_on_ifs", output, n);
n = o_save_ptr(output, n); n = o_save_ptr(output, n);
} }
str += strspn(str, G.ifs); /* skip IFS chars */
} }
if (ended_with_ifs)
*ended_with_ifs = last_is_ifs;
debug_print_list("expand_on_ifs[1]", output, n); debug_print_list("expand_on_ifs[1]", output, n);
return n; return n;
} }
@ -5209,6 +5225,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
* expansion of right-hand side of assignment == 1-element expand. * expansion of right-hand side of assignment == 1-element expand.
*/ */
char cant_be_null = 0; /* only bit 0x80 matters */ char cant_be_null = 0; /* only bit 0x80 matters */
int ended_in_ifs = 0; /* did last unquoted expansion end with IFS chars? */
char *p; char *p;
debug_printf_expand("expand_vars_to_list: arg:'%s' singleword:%x\n", arg, debug_printf_expand("expand_vars_to_list: arg:'%s' singleword:%x\n", arg,
@ -5227,6 +5244,13 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
#if ENABLE_SH_MATH_SUPPORT #if ENABLE_SH_MATH_SUPPORT
char arith_buf[sizeof(arith_t)*3 + 2]; char arith_buf[sizeof(arith_t)*3 + 2];
#endif #endif
if (ended_in_ifs) {
o_addchr(output, '\0');
n = o_save_ptr(output, n);
ended_in_ifs = 0;
}
o_addblock(output, arg, p - arg); o_addblock(output, arg, p - arg);
debug_print_list("expand_vars_to_list[1]", output, n); debug_print_list("expand_vars_to_list[1]", output, n);
arg = ++p; arg = ++p;
@ -5255,7 +5279,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
cant_be_null |= first_ch; /* do it for "$@" _now_, when we know it's not empty */ cant_be_null |= first_ch; /* do it for "$@" _now_, when we know it's not empty */
if (!(first_ch & 0x80)) { /* unquoted $* or $@ */ if (!(first_ch & 0x80)) { /* unquoted $* or $@ */
while (G.global_argv[i]) { while (G.global_argv[i]) {
n = expand_on_ifs(output, n, G.global_argv[i]); n = expand_on_ifs(NULL, output, n, G.global_argv[i]);
debug_printf_expand("expand_vars_to_list: argv %d (last %d)\n", i, G.global_argc - 1); debug_printf_expand("expand_vars_to_list: argv %d (last %d)\n", i, G.global_argc - 1);
if (G.global_argv[i++][0] && G.global_argv[i]) { if (G.global_argv[i++][0] && G.global_argv[i]) {
/* this argv[] is not empty and not last: /* this argv[] is not empty and not last:
@ -5332,7 +5356,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val, debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val,
!!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); !!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
if (val && val[0]) { if (val && val[0]) {
n = expand_on_ifs(output, n, val); n = expand_on_ifs(&ended_in_ifs, output, n, val);
val = NULL; val = NULL;
} }
} else { /* quoted $VAR, val will be appended below */ } else { /* quoted $VAR, val will be appended below */
@ -5361,6 +5385,10 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
} /* end of "while (SPECIAL_VAR_SYMBOL is found) ..." */ } /* end of "while (SPECIAL_VAR_SYMBOL is found) ..." */
if (arg[0]) { if (arg[0]) {
if (ended_in_ifs) {
o_addchr(output, '\0');
n = o_save_ptr(output, n);
}
debug_print_list("expand_vars_to_list[a]", output, n); debug_print_list("expand_vars_to_list[a]", output, n);
/* this part is literal, and it was already pre-quoted /* this part is literal, and it was already pre-quoted
* if needed (much earlier), do not use o_addQstr here! */ * if needed (much earlier), do not use o_addQstr here! */

View File

@ -1,4 +1,7 @@
Should be printed Should be printed
Would not be printed by bash
Would not be printed by bash
Would not be printed by bash
Should be printed Should be printed
Empty: Empty:
Empty: Empty:

View File

@ -8,9 +8,9 @@ for a in "$@"; do echo Should not be printed; done
# Yes, believe it or not, bash is mesmerized by "$@" and stops # Yes, believe it or not, bash is mesmerized by "$@" and stops
# treating "" as "this word cannot be expanded to nothing, # treating "" as "this word cannot be expanded to nothing,
# but must be at least null string". Now it can be expanded to nothing. # but must be at least null string". Now it can be expanded to nothing.
for a in "$@"""; do echo Should not be printed; done for a in "$@"""; do echo Would not be printed by bash; done
for a in """$@"; do echo Should not be printed; done for a in """$@"; do echo Would not be printed by bash; done
for a in """$@"''"$@"''; do echo Should not be printed; done for a in """$@"''"$@"''; do echo Would not be printed by bash; done
for a in ""; do echo Should be printed; done for a in ""; do echo Should be printed; done
# Bug 207: "$@" expands to nothing, and we erroneously glob "%s\n" twice: # Bug 207: "$@" expands to nothing, and we erroneously glob "%s\n" twice: