[git commit] hush: fix word splitting in ${v:+ARG} - dollar_altvalue1 test

Denys Vlasenko vda.linux at googlemail.com
Fri Jul 20 14:29:43 UTC 2018


commit: https://git.busybox.net/busybox/commit/?id=294eb4612cd668521faa48711297196f00af61d9
branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master

ash might be a bit buggy, need to investigate dollar_altvalue9 test

function                                             old     new   delta
expand_one_var                                      1639    2236    +597
expand_variables                                     112     128     +16
expand_vars_to_list                                 1117    1097     -20
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 2/1 up/down: 613/-20)           Total: 593 bytes

Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
 shell/ash_test/ash-quoting/dollar_altvalue9.right  |  24 ++
 shell/ash_test/ash-quoting/dollar_altvalue9.tests  |  17 ++
 shell/hush.c                                       | 263 ++++++++++++++++-----
 .../hush_test/hush-quoting/dollar_altvalue9.right  |  24 ++
 .../hush_test/hush-quoting/dollar_altvalue9.tests  |  17 ++
 5 files changed, 284 insertions(+), 61 deletions(-)

diff --git a/shell/ash_test/ash-quoting/dollar_altvalue9.right b/shell/ash_test/ash-quoting/dollar_altvalue9.right
new file mode 100644
index 000000000..fc6c2697c
--- /dev/null
+++ b/shell/ash_test/ash-quoting/dollar_altvalue9.right
@@ -0,0 +1,24 @@
+Unquoted 1:
+|a|
+|x y|
+|1|
+|2|
+|1 2|
+|A|
+|B|
+|C D|
+|zb|
+Quoted 1:
+|a 'x y' 1 2 ''  1 2 A B C D zb|
+Unquoted 2:
+|ax y|
+|1|
+|2|
+|1 2|
+|A|
+|B|
+|C D|
+|z|
+|b|
+Quoted 2:
+|a 'x y' 1 2 ''  1 2 A B C D z b|
diff --git a/shell/ash_test/ash-quoting/dollar_altvalue9.tests b/shell/ash_test/ash-quoting/dollar_altvalue9.tests
new file mode 100755
index 000000000..27a6f4f3c
--- /dev/null
+++ b/shell/ash_test/ash-quoting/dollar_altvalue9.tests
@@ -0,0 +1,17 @@
+f() { for i; do echo "|$i|"; done; }
+
+echo Unquoted 1:
+x='1 2'; f a${x:+ 'x y' $x ''  "$x" `echo A B` "`echo C D`" z}b
+echo Quoted 1:
+x='1 2'; f "a${x:+ 'x y' $x ''  "$x" `echo A B` "`echo C D`" z}b"
+
+echo Unquoted 2:
+x='1 2'; f a${x:+'x y' $x ''  "$x" `echo A B` "`echo C D`" z }b
+echo Quoted 2:
+x='1 2'; f "a${x:+ 'x y' $x ''  "$x" `echo A B` "`echo C D`" z }b"
+
+#echo Unquoted 3:
+#e=
+#x='1 2'; f a${x:+'x y' $x ''  "$x" $e $e "$e" $e `echo A B` "`echo C D`" z }b
+#echo Quoted 3:
+#x='1 2'; f "a${x:+ 'x y' $x ''  "$x" $e $e "$e" $e `echo A B` "`echo C D`" z }b"
diff --git a/shell/hush.c b/shell/hush.c
index b8af1b088..fc77b89fc 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -5873,6 +5873,138 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int
 	return exp_str;
 }
 
+static int expand_vars_to_list(o_string *output, int n, char *arg);
+
+static int encode_then_append_var_plusminus(o_string *output, int n,
+		const char *str, int dquoted)
+{
+	struct in_str input;
+	o_string dest = NULL_O_STRING;
+
+#if 0 //todo?
+	const char *cp;
+	cp = str;
+	for (;;) {
+		if (!*cp) return NULL; /* string has no special chars */
+		if (*cp == '$') break;
+		if (*cp == '\\') break;
+		if (*cp == '\'') break;
+		if (*cp == '"') break;
+#if ENABLE_HUSH_TICK
+		if (*cp == '`') break;
+#endif
+		cp++;
+	}
+#endif
+
+	/* Expanding ARG in ${var+ARG}, ${var-ARG} */
+
+	setup_string_in_str(&input, str);
+
+	for (;;) {
+		int ch;
+
+		ch = i_getch(&input);
+		debug_printf_parse("%s: ch=%c (%d) escape=%x\n",
+				__func__, ch, ch, dest.o_expflags);
+
+		if (!dest.o_expflags) {
+			if (ch == EOF)
+				break;
+			if (!dquoted && strchr(G.ifs, ch)) {
+				/* PREFIX${x:d${e}f ...} and we met space: expand "d${e}f" and start new word.
+				 * do not assume we are at the start of the word (PREFIX above).
+				 */
+				if (dest.data) {
+					n = expand_vars_to_list(output, n, dest.data);
+					o_free(&dest);
+					o_addchr(output, '\0');
+					n = o_save_ptr(output, n); /* create next word */
+				} else
+				if (output->length != o_get_last_ptr(output, n)
+				 || output->has_quoted_part
+				) {
+					/* For these cases:
+					 * f() { for i; do echo "|$i|"; done; }; x=x
+					 * f a${x:+ }b  # 1st condition
+					 * |a|
+					 * |b|
+					 * f ""${x:+ }b  # 2nd condition
+					 * ||
+					 * |b|
+					 */
+					o_addchr(output, '\0');
+					n = o_save_ptr(output, n); /* create next word */
+				}
+				continue;
+			}
+			if (!dquoted && ch == '\'') {
+//quoting version of add_till_single_quote() (try to merge?):
+				for (;;) {
+					ch = i_getch(&input);
+					if (ch == EOF) {
+						syntax_error_unterm_ch('\'');
+						goto ret; /* error */
+					}
+					if (ch == '\'')
+						break;
+					o_addqchr(&dest, ch);
+				}
+				continue;
+			}
+		}
+		if (ch == EOF) {
+			syntax_error_unterm_ch('"');
+			goto ret; /* error */
+		}
+		if (ch == '"') {
+			dest.o_expflags ^= EXP_FLAG_ESC_GLOB_CHARS;
+			continue;
+		}
+		if (ch == '\\') {
+			ch = i_getch(&input);
+			if (ch == EOF) {
+//example? error message?	syntax_error_unterm_ch('"');
+				debug_printf_parse("%s: error: \\<eof>\n", __func__);
+				goto ret;
+			}
+			o_addqchr(&dest, ch);
+			continue;
+		}
+		if (ch == '$') {
+			if (!parse_dollar(NULL, &dest, &input, /*quote_mask:*/ (dest.o_expflags || dquoted) ? 0x80 : 0)) {
+				debug_printf_parse("%s: error: parse_dollar returned 0 (error)\n", __func__);
+				goto ret;
+			}
+			continue;
+		}
+#if ENABLE_HUSH_TICK
+		if (ch == '`') {
+			//unsigned pos = dest->length;
+			o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+			o_addchr(&dest, (dest.o_expflags || dquoted) ? 0x80 | '`' : '`');
+			if (!add_till_backquote(&dest, &input,
+					/*in_dquote:*/ dest.o_expflags /* nonzero if EXP_FLAG_ESC_GLOB_CHARS set */
+				)
+			) {
+				goto ret; /* error */
+			}
+			o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+			//debug_printf_subst("SUBST RES3 '%s'\n", dest->data + pos);
+			continue;
+		}
+#endif
+		o_addQchr(&dest, ch);
+	} /* for (;;) */
+
+	if (dest.data) {
+		n = expand_vars_to_list(output, n, dest.data);
+	}
+ ret:
+	o_free_unsafe(&dest);
+	return n;
+}
+
 #if ENABLE_FEATURE_SH_MATH
 static arith_t expand_and_evaluate_arith(const char *arg, const char **errmsg_p)
 {
@@ -6231,78 +6363,86 @@ static NOINLINE int expand_one_var(o_string *output,
 			 *
 			 * Colon forms (${var:-word}, ${var:=word} etc) do the same,
 			 * but also treat null var as if it is unset.
+			 *
+			 * Word-splitting and single quote behavior:
+			 *
+			 * $ f() { for i; do echo "|$i|"; done; };
+			 *
+			 * $ x=; f ${x:?'x y' z}
+			 * bash: x: x y z              #BUG: does not abort, ${} results in empty expansion
+			 * $ x=; f "${x:?'x y' z}"
+			 * bash: x: x y z       # dash prints: dash: x: 'x y' z   #BUG: does not abort, ${} results in ""
+			 *
+			 * $ x=; f ${x:='x y' z}
+			 * |x|
+			 * |y|
+			 * |z|
+			 * $ x=; f "${x:='x y' z}"
+			 * |'x y' z|
+			 *
+			 * $ x=x; f ${x:+'x y' z}|
+			 * |x y|
+			 * |z|
+			 * $ x=x; f "${x:+'x y' z}"
+			 * |'x y' z|
+			 *
+			 * $ x=; f ${x:-'x y' z}
+			 * |x y|
+			 * |z|
+			 * $ x=; f "${x:-'x y' z}"
+			 * |'x y' z|
 			 */
-/*
- * Word-splitting and squote behavior of bash:
- * $ f() { for i; do echo "|$i|"; done; };
- *
- * $ x=; f ${x:?'x y' z}
- * bash: x: x y z              #BUG: does not abort, ${} results in empty expansion
- * $ x=; f "${x:?'x y' z}"
- * bash: x: x y z       # dash prints: dash: x: 'x y' z   #BUG: does not abort, ${} results in ""
- *
- * $ x=; f ${x:='x y' z}
- * |x|
- * |y|
- * |z|
- * $ x=; f "${x:='x y' z}"
- * |'x y' z|
- *
- * $ x=x; f ${x:+'x y' z}
- * |x y|
- * |z|
- * $ x=x; f "${x:+'x y' z}"
- * |'x y' z|
- *
- * $ x=; f ${x:-'x y' z}
- * |x y|
- * |z|
- * $ x=; f "${x:-'x y' z}"
- * |'x y' z|
- */
 			int use_word = (!val || ((exp_save == ':') && !val[0]));
 			if (exp_op == '+')
 				use_word = !use_word;
 			debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op,
 					(exp_save == ':') ? "true" : "false", use_word);
 			if (use_word) {
-//FIXME: unquoted ${x:+"b c" d} and ${x:+'b c' d} should expand to two words
-//currently it expands to three.
-				to_be_freed = encode_then_expand_vararg(exp_word,
-						/*handle_squotes:*/ !(arg0 & 0x80),
-						/*unbackslash:*/ 0
-				);
-				if (to_be_freed)
-					exp_word = to_be_freed;
-				if (exp_op == '?') {
-					/* mimic bash message */
-					msg_and_die_if_script("%s: %s",
-						var,
-						exp_word[0]
-						? exp_word
-						: "parameter null or not set"
-						/* ash has more specific messages, a-la: */
-						/*: (exp_save == ':' ? "parameter null or not set" : "parameter not set")*/
+				if (exp_op == '+' || exp_op == '-') {
+					/* ${var+word} - use alternative value */
+					/* ${var-word} - use default value */
+					n = encode_then_append_var_plusminus(output, n, exp_word,
+							/*dquoted:*/ (arg0 & 0x80)
 					);
+					val = NULL;
+				} else {
+					/* ${var?word} - indicate error if unset */
+					/* ${var=word} - assign and use default value */
+					to_be_freed = encode_then_expand_vararg(exp_word,
+							/*handle_squotes:*/ !(arg0 & 0x80),
+							/*unbackslash:*/ 0
+					);
+					if (to_be_freed)
+						exp_word = to_be_freed;
+					if (exp_op == '?') {
+						/* mimic bash message */
+						msg_and_die_if_script("%s: %s",
+							var,
+							exp_word[0]
+							? exp_word
+							: "parameter null or not set"
+							/* ash has more specific messages, a-la: */
+							/*: (exp_save == ':' ? "parameter null or not set" : "parameter not set")*/
+						);
 //TODO: how interactive bash aborts expansion mid-command?
 //It aborts the entire line, returns to prompt:
 // $ f() { for i; do echo "|$i|"; done; }; x=; f "${x:?'x y' z}"; echo YO
 // bash: x: x y z
 // $
 // ("echo YO" is not executed, neither the f function call)
-				} else {
-					val = exp_word;
-				}
-
-				if (exp_op == '=') {
-					/* ${var=[word]} or ${var:=[word]} */
-					if (isdigit(var[0]) || var[0] == '#') {
-						/* mimic bash message */
-						msg_and_die_if_script("$%s: cannot assign in this way", var);
-						val = NULL;
 					} else {
-						char *new_var = xasprintf("%s=%s", var, val);
-						set_local_var(new_var, /*flag:*/ 0);
+						val = exp_word;
+					}
+					if (exp_op == '=') {
+						/* ${var=[word]} or ${var:=[word]} */
+						if (isdigit(var[0]) || var[0] == '#') {
+							/* mimic bash message */
+							msg_and_die_if_script("$%s: cannot assign in this way", var);
+							val = NULL;
+						} else {
+							char *new_var = xasprintf("%s=%s", var, val);
+							set_local_var(new_var, /*flag:*/ 0);
+						}
 					}
 				}
 			}
@@ -6482,8 +6622,9 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
 		}
 		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! */
-		o_addstr_with_NUL(output, arg);
+		 * if needed (much earlier), do not use o_addQstr here!
+		 */
+		o_addstr(output, arg);
 		debug_print_list("expand_vars_to_list[b]", output, n);
 	} else if (output->length == o_get_last_ptr(output, n) /* expansion is empty */
 	 && !(cant_be_null & 0x80) /* and all vars were not quoted. */
@@ -6491,8 +6632,6 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
 		n--;
 		/* allow to reuse list[n] later without re-growth */
 		output->has_empty_slot = 1;
-	} else {
-		o_addchr(output, '\0');
 	}
 
 	return n;
@@ -6517,6 +6656,8 @@ static char **expand_variables(char **argv, unsigned expflags)
 
 		/* expand argv[i] */
 		n = expand_vars_to_list(&output, n, *argv++);
+		/* if (!output->has_empty_slot) -- need this?? */
+			o_addchr(&output, '\0');
 	}
 	debug_print_list("expand_variables", &output, n);
 
diff --git a/shell/hush_test/hush-quoting/dollar_altvalue9.right b/shell/hush_test/hush-quoting/dollar_altvalue9.right
new file mode 100644
index 000000000..fc6c2697c
--- /dev/null
+++ b/shell/hush_test/hush-quoting/dollar_altvalue9.right
@@ -0,0 +1,24 @@
+Unquoted 1:
+|a|
+|x y|
+|1|
+|2|
+|1 2|
+|A|
+|B|
+|C D|
+|zb|
+Quoted 1:
+|a 'x y' 1 2 ''  1 2 A B C D zb|
+Unquoted 2:
+|ax y|
+|1|
+|2|
+|1 2|
+|A|
+|B|
+|C D|
+|z|
+|b|
+Quoted 2:
+|a 'x y' 1 2 ''  1 2 A B C D z b|
diff --git a/shell/hush_test/hush-quoting/dollar_altvalue9.tests b/shell/hush_test/hush-quoting/dollar_altvalue9.tests
new file mode 100755
index 000000000..27a6f4f3c
--- /dev/null
+++ b/shell/hush_test/hush-quoting/dollar_altvalue9.tests
@@ -0,0 +1,17 @@
+f() { for i; do echo "|$i|"; done; }
+
+echo Unquoted 1:
+x='1 2'; f a${x:+ 'x y' $x ''  "$x" `echo A B` "`echo C D`" z}b
+echo Quoted 1:
+x='1 2'; f "a${x:+ 'x y' $x ''  "$x" `echo A B` "`echo C D`" z}b"
+
+echo Unquoted 2:
+x='1 2'; f a${x:+'x y' $x ''  "$x" `echo A B` "`echo C D`" z }b
+echo Quoted 2:
+x='1 2'; f "a${x:+ 'x y' $x ''  "$x" `echo A B` "`echo C D`" z }b"
+
+#echo Unquoted 3:
+#e=
+#x='1 2'; f a${x:+'x y' $x ''  "$x" $e $e "$e" $e `echo A B` "`echo C D`" z }b
+#echo Quoted 3:
+#x='1 2'; f "a${x:+ 'x y' $x ''  "$x" $e $e "$e" $e `echo A B` "`echo C D`" z }b"


More information about the busybox-cvs mailing list