hush: fix remaining known two bugs with IFS expansion. Closes 4027.
authorDenys Vlasenko <vda.linux@googlemail.com>
Mon, 1 Aug 2011 16:16:43 +0000 (18:16 +0200)
committerDenys Vlasenko <vda.linux@googlemail.com>
Mon, 1 Aug 2011 16:16:43 +0000 (18:16 +0200)
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>
shell/hush.c
shell/hush_test/hush-parsing/starquoted2.right
shell/hush_test/hush-parsing/starquoted2.tests

index 503cb770bedc8874f7624e1f4b0ebbe67c572d76..6b3027771aa8638bf8c7e8a6ed5237b6385830ca 100644 (file)
@@ -3265,14 +3265,6 @@ static int done_word(o_string *word, struct parse_context *ctx)
                        ) {
                                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));
                debug_print_strings("word appended to argv", command->argv);
@@ -4516,20 +4508,30 @@ static struct pipe *parse_stream(char **pstring,
                        break;
                case '\'':
                        dest.has_quoted_part = 1;
-                       while (1) {
-                               ch = i_getch(input);
-                               if (ch == EOF) {
-                                       syntax_error_unterm_ch('\'');
-                                       goto parse_error;
+                       if (next == '\'' && !ctx.pending_redirect) {
+ insert_empty_quoted_str_marker:
+                               nommu_addchr(&ctx.as_string, next);
+                               i_getch(input); /* eat second ' */
+                               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;
                case '"':
                        dest.has_quoted_part = 1;
+                       if (next == '"' && !ctx.pending_redirect)
+                               goto insert_empty_quoted_str_marker;
                        if (dest.o_assignment == NOT_ASSIGNMENT)
                                dest.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS;
                        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
  * we encounter IFS char(s). This is used for expanding variable values.
- * End-of-string does NOT finalize word: think about 'echo -$VAR-' */
-static int expand_on_ifs(o_string *output, int n, const char *str)
+ * End-of-string does NOT finalize word: think about 'echo -$VAR-'.
+ * 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) {
                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_addqblock(output, str, word_len); - WRONG: "v='*'; echo Z$v" prints "Z*" instead of Z* files */
                        }
+                       last_is_ifs = 0;
                        str += word_len;
                        if (!*str)  /* EOL - do not finalize word */
                                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
                /* Case "v=' a'; echo $v":
                 * here nothing precedes the space in $v expansion,
                 * 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');
                        debug_print_list("expand_on_ifs", 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);
        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.
         */
        char cant_be_null = 0; /* only bit 0x80 matters */
+       int ended_in_ifs = 0;  /* did last unquoted expansion end with IFS chars? */
        char *p;
 
        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
                char arith_buf[sizeof(arith_t)*3 + 2];
 #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);
                debug_print_list("expand_vars_to_list[1]", output, n);
                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 */
                        if (!(first_ch & 0x80)) { /* unquoted $* or $@ */
                                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);
                                        if (G.global_argv[i++][0] && G.global_argv[i]) {
                                                /* 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,
                                                !!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
                                if (val && val[0]) {
-                                       n = expand_on_ifs(output, n, val);
+                                       n = expand_on_ifs(&ended_in_ifs, output, n, val);
                                        val = NULL;
                                }
                        } 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) ..." */
 
        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);
                /* this part is literal, and it was already pre-quoted
                 * if needed (much earlier), do not use o_addQstr here! */
index e1562ed6d9208484d94e63bcf67f67dc538cb237..1bff408ca971303a2078fd76307b28faae646a2f 100644 (file)
@@ -1,4 +1,7 @@
 Should be printed
+Would not be printed by bash
+Would not be printed by bash
+Would not be printed by bash
 Should be printed
 Empty:
 Empty:
index f305c4cd99c4865bf60eea1f33923d1e475845df..7c5ff45b8d8511ec775ba968bacd7dc344ca3d9f 100755 (executable)
@@ -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
 # treating "" as "this word cannot 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 Should not be printed; 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 Would not be printed by bash; done
+for a in """$@"''"$@"''; do echo Would not be printed by bash; done
 for a in ""; do echo Should be printed; done
 
 # Bug 207: "$@" expands to nothing, and we erroneously glob "%s\n" twice: