hush: fix memory leak. it was actually rather invloved problem.
Now finally glob/variable expansion is done IN THE RIGHT ORDER!
It opens up a possibility to cleanly fix remaining known bugs.

function                                             old     new   delta
o_save_ptr                                           115     286    +171
o_save_ptr_helper                                      -     115    +115
done_word                                            591     690     +99
o_get_last_ptr                                         -      31     +31
expand_on_ifs                                        125      97     -28
add_string_to_strings                                 28       -     -28
run_list                                            1895    1862     -33
debug_print_strings                                   42       -     -42
add_strings_to_strings                               126       -    -126
expand_variables                                    1550    1394    -156
o_debug_list                                         168       -    -168
expand_strvec_to_strvec                              388      10    -378
------------------------------------------------------------------------------
(add/remove: 2/4 grow/shrink: 2/4 up/down: 416/-959)         Total: -543 bytes

diff --git a/shell/hush.c b/shell/hush.c
index 0e19764..0b92c29 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -104,6 +104,8 @@
 #define debug_printf_exec(...)   do {} while (0)
 #define debug_printf_jobs(...)   do {} while (0)
 #define debug_printf_expand(...) do {} while (0)
+#define debug_printf_glob(...)   do {} while (0)
+#define debug_printf_list(...)   do {} while (0)
 #define debug_printf_clean(...)  do {} while (0)
 
 #ifndef debug_printf
@@ -120,12 +122,27 @@
 
 #ifndef debug_printf_jobs
 #define debug_printf_jobs(...) fprintf(stderr, __VA_ARGS__)
-#define DEBUG_SHELL_JOBS 1
+#define DEBUG_JOBS 1
+#else
+#define DEBUG_JOBS 0
 #endif
 
 #ifndef debug_printf_expand
 #define debug_printf_expand(...) fprintf(stderr, __VA_ARGS__)
 #define DEBUG_EXPAND 1
+#else
+#define DEBUG_EXPAND 0
+#endif
+
+#ifndef debug_printf_glob
+#define debug_printf_glob(...) fprintf(stderr, __VA_ARGS__)
+#define DEBUG_GLOB 1
+#else
+#define DEBUG_GLOB 0
+#endif
+
+#ifndef debug_printf_list
+#define debug_printf_list(...) fprintf(stderr, __VA_ARGS__)
 #endif
 
 /* Keep unconditionally on for now */
@@ -143,6 +160,17 @@
 #define DEBUG_CLEAN 1
 #endif
 
+#if DEBUG_EXPAND
+static void debug_print_strings(const char *prefix, char **vv)
+{
+	fprintf(stderr, "%s:\n", prefix);
+	while (*vv)
+		fprintf(stderr, " '%s'\n", *vv++);
+}
+#else
+#define debug_print_strings(prefix, vv) ((void)0)
+#endif
+
 
 /*
  * Leak hunting. Use hush_leaktool.sh for post-processing.
@@ -309,6 +337,7 @@
 	int length;
 	int maxlen;
 	smallint o_quote;
+	smallint o_glob;
 	smallint nonnull;
 	smallint has_empty_slot;
 } o_string;
@@ -531,6 +560,18 @@
 static void unset_local_var(const char *name);
 
 
+static int glob_needed(const char *s)
+{
+	while (*s) {
+		if (*s == '\\')
+			s++;
+		if (*s == '*' || *s == '[' || *s == '?')
+			return 1;
+		s++;
+	}
+	return 0;
+}
+
 static char **add_strings_to_strings(int need_xstrdup, char **strings, char **add)
 {
 	int i;
@@ -592,17 +633,6 @@
 }
 #endif
 
-#ifdef DEBUG_EXPAND
-static void debug_print_strings(const char *prefix, char **vv)
-{
-	fprintf(stderr, "%s:\n", prefix);
-	while (*vv)
-		fprintf(stderr, " '%s'\n", *vv++);
-}
-#else
-#define debug_print_strings(prefix, vv) ((void)0)
-#endif
-
 
 /* Function prototypes for builtins */
 static int builtin_cd(char **argv);
@@ -1260,7 +1290,33 @@
  * o_finalize_list() operation post-processes this structure - calculates
  * and stores actual char* ptrs in list[]. Oh, it NULL terminates it as well.
  */
-static int o_save_ptr(o_string *o, int n)
+#if DEBUG_EXPAND || DEBUG_GLOB
+static void debug_print_list(const char *prefix, o_string *o, int n)
+{
+	char **list = (char**)o->data;
+	int string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
+	int i = 0;
+	fprintf(stderr, "%s: list:%p n:%d string_start:%d length:%d maxlen:%d\n",
+			prefix, list, n, string_start, o->length, o->maxlen);
+	while (i < n) {
+		fprintf(stderr, " list[%d]=%d '%s' %p\n", i, (int)list[i],
+				o->data + (int)list[i] + string_start,
+				o->data + (int)list[i] + string_start);
+		i++;
+	}
+	if (n) {
+		const char *p = o->data + (int)list[n - 1] + string_start;
+		fprintf(stderr, " total_sz:%d\n", (p + strlen(p) + 1) - o->data);
+	}
+}
+#else
+#define debug_print_list(prefix, o, n) ((void)0)
+#endif
+
+/* n = o_save_ptr_helper(str, n) "starts new string" by storing an index value
+ * in list[n] so that it points past last stored byte so far.
+ * It returns n+1. */
+static int o_save_ptr_helper(o_string *o, int n)
 {
 	char **list = (char**)o->data;
 	int string_start;
@@ -1270,26 +1326,27 @@
 		string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
 		string_len = o->length - string_start;
 		if (!(n & 0xf)) { /* 0, 0x10, 0x20...? */
-			//bb_error_msg("list[%d]=%d string_start=%d (growing)", n, string_len, string_start);
+			debug_printf_list("list[%d]=%d string_start=%d (growing)", n, string_len, string_start);
 			/* list[n] points to string_start, make space for 16 more pointers */
 			o->maxlen += 0x10 * sizeof(list[0]);
 			o->data = xrealloc(o->data, o->maxlen + 1);
     			list = (char**)o->data;
 			memmove(list + n + 0x10, list + n, string_len);
 			o->length += 0x10 * sizeof(list[0]);
-		}
-		//else bb_error_msg("list[%d]=%d string_start=%d", n, string_len, string_start);
+		} else
+			debug_printf_list("list[%d]=%d string_start=%d", n, string_len, string_start);
 	} else {
 		/* We have empty slot at list[n], reuse without growth */
 		string_start = ((n+1 + 0xf) & ~0xf) * sizeof(list[0]); /* NB: n+1! */
 		string_len = o->length - string_start;
-		//bb_error_msg("list[%d]=%d string_start=%d (empty slot)", n, string_len, string_start);
+		debug_printf_list("list[%d]=%d string_start=%d (empty slot)", n, string_len, string_start);
 		o->has_empty_slot = 0;
 	}
 	list[n] = (char*)string_len;
 	return n + 1;
 }
 
+/* "What was our last o_save_ptr'ed position (byte offset relative o->data)?" */
 static int o_get_last_ptr(o_string *o, int n)
 {
 	char **list = (char**)o->data;
@@ -1298,14 +1355,89 @@
 	return ((int)list[n-1]) + string_start;
 }
 
+/* Convert every \x to x in-place, return ptr past NUL. */
+static char *unbackslash(char *src)
+{
+	char *dst = src;
+	while (1) {
+		if (*src == '\\')
+			src++;
+		if ((*dst++ = *src++) == '\0')
+			break;
+	}
+	return dst;
+}
+
+static int o_glob(o_string *o, int n)
+{
+	glob_t globdata;
+	int gr;
+	char *pattern;
+
+	debug_printf_glob("start o_glob: n:%d o->data:%p", n, o->data);
+	if (!o->data)
+		return o_save_ptr_helper(o, n);
+	pattern = o->data + o_get_last_ptr(o, n);
+	debug_printf_glob("glob pattern '%s'", pattern);
+	if (!glob_needed(pattern)) {
+ literal:
+		o->length = unbackslash(pattern) - o->data;
+		debug_printf_glob("glob pattern '%s' is literal", pattern);
+		return o_save_ptr_helper(o, n);
+	}
+
+	memset(&globdata, 0, sizeof(globdata));
+	gr = glob(pattern, 0, NULL, &globdata);
+	debug_printf_glob("glob('%s'):%d\n", pattern, gr);
+	if (gr == GLOB_NOSPACE)
+		bb_error_msg_and_die("out of memory during glob");
+	if (gr == GLOB_NOMATCH) {
+		globfree(&globdata);
+		goto literal;
+	}
+	if (gr != 0) { /* GLOB_ABORTED ? */
+//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]) {
+		char **argv = globdata.gl_pathv;
+		o->length = pattern - o->data; /* "forget" pattern */
+		while (1) {
+			o_addstr(o, *argv, strlen(*argv) + 1);
+			n = o_save_ptr_helper(o, n);
+			argv++;
+			if (!*argv)
+				break;
+		}
+	}
+	globfree(&globdata);
+	if (DEBUG_GLOB)
+		debug_print_list("o_glob returning", o, n);
+	return n;
+}
+
+/* o_save_ptr_helper + but glob the string so far remembered
+ * if o->o_glob == 1 */
+static int o_save_ptr(o_string *o, int n)
+{
+	if (o->o_glob)
+		return o_glob(o, n); /* o_save_ptr_helper is inside */
+	return o_save_ptr_helper(o, n);
+}
+
+/* "Please convert list[n] to real char* ptrs, and NULL terminate it." */
 static char **o_finalize_list(o_string *o, int n)
 {
-	char **list = (char**)o->data;
+	char **list;
 	int string_start;
 
-	o_save_ptr(o, n); /* force growth for list[n] if necessary */
-	string_start = ((n+1 + 0xf) & ~0xf) * sizeof(list[0]);
-	list[n] = NULL;
+	n = o_save_ptr(o, n); /* force growth for list[n] if necessary */
+	if (DEBUG_EXPAND)
+		debug_print_list("finalized", o, n);
+	debug_printf_expand("finalized n:%d", n);
+	list = (char**)o->data;
+	string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
+	list[--n] = NULL;
 	while (n) {
 		n--;
 		list[n] = o->data + (int)list[n] + string_start;
@@ -1313,28 +1445,6 @@
 	return list;
 }
 
-#ifdef DEBUG_EXPAND
-static void o_debug_list(const char *prefix, o_string *o, int n)
-{
-	char **list = (char**)o->data;
-	int string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
-	int i = 0;
-	fprintf(stderr, "%s: list:%p n:%d string_start:%d length:%d maxlen:%d\n",
-			prefix, list, n, string_start, o->length, o->maxlen);
-	while (i < n) {
-		fprintf(stderr, " list[%d]=%d '%s'\n", i, (int)list[i],
-				o->data + (int)list[i] + string_start);
-		i++;
-	}
-	if (n) {
-		const char *p = o->data + (int)list[n] + string_start;
-		fprintf(stderr, " total_sz:%d\n", (p + strlen(p) + 1) - o->data);
-	}
-}
-#else
-#define o_debug_list(prefix, o, n) ((void)0)
-#endif
-
 
 /*
  * in_str support
@@ -1782,7 +1892,7 @@
 	while ((childpid = waitpid(-1, &status, attributes)) > 0) {
 		const int dead = WIFEXITED(status) || WIFSIGNALED(status);
 
-#ifdef DEBUG_SHELL_JOBS
+#if DEBUG_JOBS
 		if (WIFSTOPPED(status))
 			debug_printf_jobs("pid %d stopped by sig %d (exitcode %d)\n",
 					childpid, WSTOPSIG(status), WEXITSTATUS(status));
@@ -2502,11 +2612,11 @@
 		if (!*str)  /* EOL - do not finalize word */
 			break;
 		o_addchr(output, '\0');
-		o_debug_list("expand_on_ifs", output, n);
+		debug_print_list("expand_on_ifs", output, n);
 		n = o_save_ptr(output, n);
 		str += strspn(str, ifs); /* skip ifs chars */
 	}
-	o_debug_list("expand_on_ifs[1]", output, n);
+	debug_print_list("expand_on_ifs[1]", output, n);
 	return n;
 }
 
@@ -2530,9 +2640,9 @@
 	ored_ch = 0;
 
 	debug_printf_expand("expand_vars_to_list: arg '%s'\n", arg);
-	o_debug_list("expand_vars_to_list", output, n);
+	debug_print_list("expand_vars_to_list", output, n);
 	n = o_save_ptr(output, n);
-	o_debug_list("expand_vars_to_list[0]", output, n);
+	debug_print_list("expand_vars_to_list[0]", output, n);
 
 	while ((p = strchr(arg, SPECIAL_VAR_SYMBOL)) != NULL) {
 #if ENABLE_HUSH_TICK
@@ -2540,7 +2650,7 @@
 #endif
 
 		o_addQstr(output, arg, p - arg);
-		o_debug_list("expand_vars_to_list[1]", output, n);
+		debug_print_list("expand_vars_to_list[1]", output, n);
 		arg = ++p;
 		p = strchr(p, SPECIAL_VAR_SYMBOL);
 
@@ -2575,9 +2685,9 @@
 						/* this argv[] is not empty and not last:
 						 * put terminating NUL, start new word */
 						o_addchr(output, '\0');
-						o_debug_list("expand_vars_to_list[2]", output, n);
+						debug_print_list("expand_vars_to_list[2]", output, n);
 						n = o_save_ptr(output, n);
-						o_debug_list("expand_vars_to_list[3]", output, n);
+						debug_print_list("expand_vars_to_list[3]", output, n);
 					}
 				}
 			} else
@@ -2589,7 +2699,7 @@
 					if (++i >= global_argc)
 						break;
 					o_addchr(output, '\0');
-					o_debug_list("expand_vars_to_list[4]", output, n);
+					debug_print_list("expand_vars_to_list[4]", output, n);
 					n = o_save_ptr(output, n);
 				}
 			} else { /* quoted $*: add as one word */
@@ -2647,9 +2757,9 @@
 	} /* end of "while (SPECIAL_VAR_SYMBOL is found) ..." */
 
 	if (arg[0]) {
-		o_debug_list("expand_vars_to_list[a]", output, n);
+		debug_print_list("expand_vars_to_list[a]", output, n);
 		o_addQstr(output, arg, strlen(arg) + 1);
-		o_debug_list("expand_vars_to_list[b]", output, n);
+		debug_print_list("expand_vars_to_list[b]", output, n);
 	} else if (output->length == o_get_last_ptr(output, n) /* expansion is empty */
 	 && !(ored_ch & 0x80) /* and all vars were not quoted. */
 	) {
@@ -2662,106 +2772,33 @@
 	return n;
 }
 
-static char **expand_variables(char **argv, char or_mask)
+static char **expand_variables(char **argv, int or_mask)
 {
 	int n;
 	char **list;
 	char **v;
 	o_string output = NULL_O_STRING;
 
+	if (or_mask & 0x100)
+		output.o_glob = 1;
+
 	n = 0;
 	v = argv;
-	while (*v)
-		n = expand_vars_to_list(&output, n, *v++, or_mask);
-	o_debug_list("expand_variables", &output, n);
+	while (*v) {
+		n = expand_vars_to_list(&output, n, *v, (char)or_mask);
+		v++;
+	}
+	debug_print_list("expand_variables", &output, n);
 
 	/* output.data (malloced in one block) gets returned in "list" */
 	list = o_finalize_list(&output, n);
+	debug_print_strings("expand_variables[1]", list);
 	return list;
 }
 
-/* Remove non-backslashed backslashes and add to "strings" vector.
- * XXX broken if the last character is '\\', check that before calling.
- */
-static char **add_unq_string_to_strings(char **strings, const char *src)
-{
-	int cnt;
-	const char *s;
-	char *v, *dest;
-
-	for (cnt = 1, s = src; s && *s; s++) {
-		if (*s == '\\') s++;
-		cnt++;
-	}
-	v = dest = xmalloc(cnt);
-	for (s = src; s && *s; s++, dest++) {
-		if (*s == '\\') s++;
-		*dest = *s;
-	}
-	*dest = '\0';
-
-	return add_string_to_strings(strings, v);
-}
-
-/* XXX broken if the last character is '\\', check that before calling */
-static int glob_needed(const char *s)
-{
-	for (; *s; s++) {
-		if (*s == '\\')
-			s++;
-		if (strchr("*[?", *s))
-			return 1;
-	}
-	return 0;
-}
-
-static void xglob(char ***pglob, const char *pattern)
-{
-	if (glob_needed(pattern)) {
-		glob_t globdata;
-		int gr;
-
-		memset(&globdata, 0, sizeof(globdata));
-		gr = glob(pattern, 0, NULL, &globdata);
-		debug_printf("glob returned %d\n", gr);
-		if (gr == GLOB_NOSPACE)
-			bb_error_msg_and_die("out of memory during glob");
-		if (gr == GLOB_NOMATCH) {
-			globfree(&globdata);
-			goto literal;
-		}
-		if (gr != 0) { /* GLOB_ABORTED ? */
-			bb_error_msg("glob(3) error %d on '%s'", gr, pattern);
-//TODO: testcase for bad glob pattern behavior
-		}
-		if (globdata.gl_pathv && globdata.gl_pathv[0])
-			*pglob = add_strings_to_strings(1, *pglob, globdata.gl_pathv);
-		globfree(&globdata);
-		return;
-	}
-
- literal:
-	/* quote removal, or more accurately, backslash removal */
-	*pglob = add_unq_string_to_strings(*pglob, pattern);
-	debug_print_strings("after xglob", *pglob);
-}
-
-//LEAK is here: callers expect result to be free()able, but we
-//actually require free_strings(). free() leaks strings.
 static char **expand_strvec_to_strvec(char **argv)
 {
-	char **exp;
-	char **res = xzalloc(sizeof(res[0]));
-
-	debug_print_strings("expand_strvec_to_strvec: pre expand", argv);
-	exp = argv = expand_variables(argv, 0);
-	debug_print_strings("expand_strvec_to_strvec: post expand", argv);
-	while (*argv) {
-		xglob(&res, *argv++);
-	}
-	free(exp);
-	debug_print_strings("expand_strvec_to_strvec: res", res);
-	return res;
+	return expand_variables(argv, 0x100);
 }
 
 /* used for expansion of right hand of assignments */