hush: fix handling of } which is not a closing one in { cmd; }

function                                             old     new   delta
parse_stream                                        2176    2302    +126
builtin_unset                                        381     387      +6

diff --git a/shell/hush.c b/shell/hush.c
index ecacd96..62b1d48 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -1822,7 +1822,7 @@
 		goto literal;
 	}
 	if (gr != 0) { /* GLOB_ABORTED ? */
-//TODO: testcase for bad glob pattern behavior
+		/* TODO: testcase for bad glob pattern behavior */
 		bb_error_msg("glob(3) error %d on '%s'", gr, pattern);
 	}
 	if (globdata.gl_pathv && globdata.gl_pathv[0]) {
@@ -2339,7 +2339,8 @@
 			if (HUSH_DEBUG)
 				if (list[n-1] + strlen(list[n-1]) + 1 != list[n])
 					bb_error_msg_and_die("BUG in varexp3");
-			list[n][-1] = ' '; /* TODO: or to G.ifs[0]? */
+			/* bash uses ' ' regardless of $IFS contents */
+			list[n][-1] = ' ';
 			n++;
 		}
 	}
@@ -5128,14 +5129,16 @@
 		while (1) {
 			ch = i_getch(input);
 			nommu_addchr(as_string, ch);
-			if (ch == '}')
+			if (ch == '}') {
 				break;
+			}
 
 			if (first_char) {
-				if (ch == '#')
+				if (ch == '#') {
 					/* ${#var}: length of var contents */
 					goto char_ok;
-				else if (isdigit(ch)) {
+				}
+				if (isdigit(ch)) {
 					all_digits = true;
 					goto char_ok;
 				}
@@ -5186,7 +5189,7 @@
 			o_addchr(dest, ch | quote_mask);
 			quote_mask = 0;
 			first_char = false;
-		}
+		} /* while (1) */
 		o_addchr(dest, SPECIAL_VAR_SYMBOL);
 		break;
 	}
@@ -5432,6 +5435,7 @@
 				, ch);
 
 		if (!is_special && !is_ifs) { /* ordinary char */
+ ordinary_char:
 			o_addQchr(&dest, ch);
 			if ((dest.o_assignment == MAYBE_ASSIGNMENT
 			    || dest.o_assignment == WORD_IS_KEYWORD)
@@ -5475,6 +5479,19 @@
 		if (end_trigger && end_trigger == ch
 		 && (heredoc_cnt == 0 || end_trigger != ';')
 		) {
+			/* "{ cmd}" or "{ cmd }..." without semicolon or &:
+			 * } is an ordinary char in this case.
+			 * Pathological example: { ""}; } should exec "}" cmd
+			 */
+			if (ch == '}'
+			 && !(IS_NULL_PIPE(ctx.pipe)
+			     && IS_NULL_CMD(ctx.command)
+			     && dest.length == 0
+			     && !dest.o_quoted
+			    )
+			) {
+				goto ordinary_char;
+			}
 			if (heredoc_cnt) {
 				/* This is technically valid:
 				 * { cat <<HERE; }; echo Ok
@@ -5491,15 +5508,6 @@
 			if (done_word(&dest, &ctx)) {
 				goto parse_error;
 			}
-			/* Disallow "{ cmd }" without semicolon or & */
-			//debug_printf_parse("null pi %d\n", IS_NULL_PIPE(ctx.pipe))
-			//debug_printf_parse("null cmd %d\n", IS_NULL_CMD(ctx.command))
-			if (ch == '}'
-			 && !(IS_NULL_PIPE(ctx.pipe) && IS_NULL_CMD(ctx.command))
-			) {
-				syntax_error_unexpected_ch(ch);
-				goto parse_error;
-			}
 			done_pipe(&ctx, PIPE_SEQ);
 			dest.o_assignment = MAYBE_ASSIGNMENT;
 			/* Do we sit outside of any if's, loops or case's? */
@@ -5749,6 +5757,16 @@
 				goto case_semi;
 #endif
 		case '}':
+			if (!(IS_NULL_PIPE(ctx.pipe)
+			     && IS_NULL_CMD(ctx.command)
+			     && dest.length == 0
+			     && !dest.o_quoted
+			    )
+			) {
+				/* } not preceded by ; or & is an ordinary
+				 * char, example: "echo }" */
+				goto ordinary_char;
+			}
 			/* proper use of this character is caught by end_trigger:
 			 * if we see {, we call parse_group(..., end_trigger='}')
 			 * and it will match } earlier (not here). */
@@ -6858,7 +6876,8 @@
 
 	var = 0;
 	while ((arg = *argv) != NULL && arg[0] == '-') {
-		while (*++arg) {
+		arg++;
+		do {
 			switch (*arg) {
 			case 'v':
 			case 'f':
@@ -6872,7 +6891,7 @@
 				bb_error_msg("unset: %s: invalid option", *argv);
 				return EXIT_FAILURE;
 			}
-		}
+		} while (*++arg);
 		argv++;
 	}
 
diff --git a/shell/hush_test/hush-misc/export.right b/shell/hush_test/hush-misc/export.right
index 4df2e38..b93e503 100644
--- a/shell/hush_test/hush-misc/export.right
+++ b/shell/hush_test/hush-misc/export.right
@@ -1,5 +1,5 @@
 export aaa1="'''"
-export aaa2=
+export aaa2=''
 export aaa3="'''"'abc'
 export aaa4='def'"'''"
 export aaa5="'''"'abc'"'''"'def'"'''"
diff --git a/shell/hush_test/hush-parsing/group1.right b/shell/hush_test/hush-parsing/group1.right
new file mode 100644
index 0000000..6a7c4be
--- /dev/null
+++ b/shell/hush_test/hush-parsing/group1.right
@@ -0,0 +1 @@
+word} }
diff --git a/shell/hush_test/hush-parsing/group1.tests b/shell/hush_test/hush-parsing/group1.tests
new file mode 100644
index 0000000..f063fbc
--- /dev/null
+++ b/shell/hush_test/hush-parsing/group1.tests
@@ -0,0 +1 @@
+{ echo word} }; }
diff --git a/shell/hush_test/hush-vars/var_posix1.right b/shell/hush_test/hush-vars/var_posix1.right
index 373b16c..47d52a6 100644
--- a/shell/hush_test/hush-vars/var_posix1.right
+++ b/shell/hush_test/hush-vars/var_posix1.right
@@ -22,6 +22,7 @@
 babcdcd
 ababcdcd
 Empty:
+ababcdcd}_tail
 ababcd
 ababcd
 ababcd
@@ -30,4 +31,5 @@
 ababcdc
 ababcdcd
 Empty:
+ababcdcd}_tail
 end
diff --git a/shell/hush_test/hush-vars/var_posix1.tests b/shell/hush_test/hush-vars/var_posix1.tests
index 0ce531d..3069360 100755
--- a/shell/hush_test/hush-vars/var_posix1.tests
+++ b/shell/hush_test/hush-vars/var_posix1.tests
@@ -30,6 +30,8 @@
 echo ${var##?}
 echo ${var#*}
 echo Empty:${var##*}
+echo ${var#}}_tail
+# UNFIXED BUG: echo ${var#\}}_tail
 
 echo ${var%cd}
 echo ${var%%cd}
@@ -39,5 +41,7 @@
 echo ${var%%?}
 echo ${var%*}
 echo Empty:${var%%*}
+echo ${var#}}_tail
+# UNFIXED BUG: echo ${var#\}}_tail
 
 echo end