hush: partially fix wrong expansion on $IFS (bug 4027).
authorDenys Vlasenko <vda.linux@googlemail.com>
Mon, 1 Aug 2011 12:06:20 +0000 (14:06 +0200)
committerDenys Vlasenko <vda.linux@googlemail.com>
Mon, 1 Aug 2011 12:06:20 +0000 (14:06 +0200)
In the added testcase, before patch we failed 8 out of 9 tests,
now we fail only 2 (4th and 5th).

function                                             old     new   delta
expand_on_ifs                                        225     258     +33
expand_vars_to_list                                 1038    1054     +16
o_save_ptr_helper                                    115     119      +4
builtin_umask                                        132     133      +1
o_addQstr                                            165     161      -4
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 4/1 up/down: 54/-4)              Total: 50 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
shell/hush.c
shell/hush_test/hush-vars/var_expand_on_ifs.right [new file with mode: 0644]
shell/hush_test/hush-vars/var_expand_on_ifs.tests [new file with mode: 0755]

index 1082738a2ddcb43f843203dd83a1fe07afbfefc2..503cb770bedc8874f7624e1f4b0ebbe67c572d76 100644 (file)
@@ -2281,7 +2281,7 @@ static void o_addqblock(o_string *o, const char *str, int len)
                        ordinary_cnt = len;
                o_addblock(o, str, ordinary_cnt);
                if (ordinary_cnt == len)
-                       return;
+                       return; /* NUL is already added by o_addblock */
                str += ordinary_cnt;
                len -= ordinary_cnt + 1; /* we are processing + 1 char below */
 
@@ -2295,8 +2295,8 @@ static void o_addqblock(o_string *o, const char *str, int len)
                o_grow_by(o, sz);
                o->data[o->length] = ch;
                o->length++;
-               o->data[o->length] = '\0';
        }
+       o->data[o->length] = '\0';
 }
 
 static void o_addQblock(o_string *o, const char *str, int len)
@@ -2385,6 +2385,7 @@ static int o_save_ptr_helper(o_string *o, int n)
                                n, string_len, string_start);
                o->has_empty_slot = 0;
        }
+       o->has_quoted_part = 0;
        list[n] = (char*)(uintptr_t)string_len;
        return n + 1;
 }
@@ -4754,8 +4755,13 @@ static void o_addblock_duplicate_backslash(o_string *o, const char *str, int len
 static int expand_on_ifs(o_string *output, int n, const char *str)
 {
        while (1) {
-               int word_len = strcspn(str, G.ifs);
+               int word_len;
+
+               if (!*str)  /* EOL - do not finalize word */
+                       break;
+               word_len = strcspn(str, G.ifs);
                if (word_len) {
+                       /* We have WORD_LEN leading non-IFS chars */
                        if (!(output->o_expflags & EXP_FLAG_GLOB)) {
                                o_addblock(output, str, word_len);
                        } else {
@@ -4769,13 +4775,25 @@ static int expand_on_ifs(o_string *output, int n, const char *str)
                                /*o_addqblock(output, str, word_len); - WRONG: "v='*'; echo Z$v" prints "Z*" instead of Z* files */
                        }
                        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 */
+               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)
+                */
+                || (output->length && output->data[output->length - 1])
+               ) {
+ finalize:
+                       o_addchr(output, '\0');
+                       debug_print_list("expand_on_ifs", output, n);
+                       n = o_save_ptr(output, n);
                }
-               if (!*str)  /* EOL - do not finalize word */
-                       break;
-               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 */
+               str += strspn(str, G.ifs); /* skip IFS chars */
        }
        debug_print_list("expand_on_ifs[1]", output, n);
        return n;
@@ -5270,11 +5288,13 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
                                        if (G.ifs[0])
                                                o_addchr(output, G.ifs[0]);
                                }
+                               output->has_quoted_part = 1;
                        }
                        break;
                }
                case SPECIAL_VAR_SYMBOL: /* <SPECIAL_VAR_SYMBOL><SPECIAL_VAR_SYMBOL> */
                        /* "Empty variable", used to make "" etc to not disappear */
+                       output->has_quoted_part = 1;
                        arg++;
                        cant_be_null = 0x80;
                        break;
@@ -5316,6 +5336,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
                                        val = NULL;
                                }
                        } else { /* quoted $VAR, val will be appended below */
+                               output->has_quoted_part = 1;
                                debug_printf_expand("quoted '%s', output->o_escape:%d\n", val,
                                                !!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
                        }
diff --git a/shell/hush_test/hush-vars/var_expand_on_ifs.right b/shell/hush_test/hush-vars/var_expand_on_ifs.right
new file mode 100644 (file)
index 0000000..2ed2069
--- /dev/null
@@ -0,0 +1,9 @@
+1 a b c
+2 a + b c
+3 a b c
+4 a  b c
+5 a  b c
+6 a b + c
+7 a b c
+8 a b  c
+9 a b  c
diff --git a/shell/hush_test/hush-vars/var_expand_on_ifs.tests b/shell/hush_test/hush-vars/var_expand_on_ifs.tests
new file mode 100755 (executable)
index 0000000..a12ff8e
--- /dev/null
@@ -0,0 +1,11 @@
+b=' b '
+e=''
+echo 1 a $b c
+echo 2 a +$b c
+echo 3 a $e$b c
+echo 4 a "$e"$b c
+echo 5 a ""$b c
+echo 6 a $b+ c
+echo 7 a $b$e c
+echo 8 a $b"$e" c
+echo 9 a $b"" c