hush: fix a case where "$@" must expand to no word at all
authorDenis Vlasenko <vda.linux@googlemail.com>
Sat, 5 Jul 2008 17:40:04 +0000 (17:40 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Sat, 5 Jul 2008 17:40:04 +0000 (17:40 -0000)
shell/hush.c
shell/hush_test/hush-parsing/starquoted2.right [new file with mode: 0644]
shell/hush_test/hush-parsing/starquoted2.tests [new file with mode: 0755]

index 5b5a5424137dab39bd7e8f2e351192f557bf82f3..2c1d31c6dd23a16c8e1a25328a14ceb3f094fa67 100644 (file)
@@ -2380,7 +2380,10 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
                p = strchr(p, SPECIAL_VAR_SYMBOL);
 
                first_ch = arg[0] | or_mask; /* forced to "quoted" if or_mask = 0x80 */
-               ored_ch |= first_ch;
+               /* "$@" is special. Even if quoted, it can still
+                * expand to nothing (not even an empty string) */
+               if ((first_ch & 0x7f) != '@')
+                       ored_ch |= first_ch;
                val = NULL;
                switch (first_ch & 0x7f) {
                /* Highest bit in first_ch indicates that var is double-quoted */
@@ -2401,6 +2404,7 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
                        i = 1;
                        if (!global_argv[i])
                                break;
+                       ored_ch |= first_ch; /* do it for "$@" _now_, when we know it's not empty */
                        if (!(first_ch & 0x80)) { /* unquoted $* or $@ */
                                smallint sv = output->o_quote;
                                /* unquoted var's contents should be globbed, so don't quote */
@@ -2932,15 +2936,25 @@ static int done_word(o_string *word, struct p_context *ctx)
                        }
                }
 #endif
-               if (word->nonnull /* we saw "xx" or 'xx' */
+               if (word->nonnull /* word had "xx" or 'xx' at least as part of it */
                 /* optimization: and if it's ("" or '') or ($v... or `cmd`...): */
                 && (word->data[0] == '\0' || word->data[0] == SPECIAL_VAR_SYMBOL)
                 /* (otherwise it's "abc".... and is already safe) */
                ) {
-                       /* 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);
+                       /* but exclude "$@"! it expands to no word despite "" */
+                       char *p = word->data;
+                       while (p[0] == SPECIAL_VAR_SYMBOL
+                           && (p[1] & 0x7f) == '@'
+                           && p[2] == SPECIAL_VAR_SYMBOL
+                       ) {
+                               p += 3;
+                       }
+                       if (p == word->data || p[0] != '\0') {
+                               /* 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);
+                       }
                }
                child->argv = add_malloced_string_to_strings(child->argv, xstrdup(word->data));
                debug_print_strings("word appended to argv", child->argv);
diff --git a/shell/hush_test/hush-parsing/starquoted2.right b/shell/hush_test/hush-parsing/starquoted2.right
new file mode 100644 (file)
index 0000000..46f2436
--- /dev/null
@@ -0,0 +1,2 @@
+Should be printed
+Should be printed
diff --git a/shell/hush_test/hush-parsing/starquoted2.tests b/shell/hush_test/hush-parsing/starquoted2.tests
new file mode 100755 (executable)
index 0000000..782d71b
--- /dev/null
@@ -0,0 +1,14 @@
+if test $# != 0; then
+    exec "$THIS_SH" "$0"
+fi
+
+# No params!
+for a in "$*"; do echo Should be printed; done
+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 Should be printed; done