aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenys Vlasenko <vda.linux@googlemail.com>2018-04-05 00:51:55 +0200
committerDenys Vlasenko <vda.linux@googlemail.com>2018-04-05 00:51:55 +0200
commitd358b0b65dae83d52e511a126757e2aa7b1881b2 (patch)
tree6ff6f7c14a399c0e5ff659b69080822a0e84cc83
parent332e4115c978094b3630cde62a7154a4fcd564d8 (diff)
downloadbusybox-w32-d358b0b65dae83d52e511a126757e2aa7b1881b2.tar.gz
busybox-w32-d358b0b65dae83d52e511a126757e2aa7b1881b2.tar.bz2
busybox-w32-d358b0b65dae83d52e511a126757e2aa7b1881b2.zip
hush: fix a bug where we don't properly handle f() { a=A; b=B; }; a= f
function old new delta unset_local_var 20 274 +254 leave_var_nest_level - 98 +98 set_vars_and_save_old 128 164 +36 enter_var_nest_level - 32 +32 builtin_local 46 50 +4 pseudo_exec_argv 554 544 -10 redirect_and_varexp_helper 77 64 -13 run_pipe 1890 1641 -249 unset_local_var_len 267 - -267 ------------------------------------------------------------------------------ (add/remove: 2/1 grow/shrink: 3/3 up/down: 424/-539) Total: -115 bytes Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r--shell/hush.c153
-rw-r--r--shell/hush_test/hush-vars/var_nested1.right3
-rwxr-xr-xshell/hush_test/hush-vars/var_nested1.tests16
-rw-r--r--shell/hush_test/hush-vars/var_nested2.right1
-rwxr-xr-xshell/hush_test/hush-vars/var_nested2.tests2
5 files changed, 93 insertions, 82 deletions
diff --git a/shell/hush.c b/shell/hush.c
index 9fb9b5f32..00d86b4e4 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -475,7 +475,6 @@ static const char hush_version_str[] ALIGN1 = "HUSH_VERSION="BB_VER;
475 */ 475 */
476#if !BB_MMU 476#if !BB_MMU
477typedef struct nommu_save_t { 477typedef struct nommu_save_t {
478 char **new_env;
479 struct variable *old_vars; 478 struct variable *old_vars;
480 char **argv; 479 char **argv;
481 char **argv_from_re_execing; 480 char **argv_from_re_execing;
@@ -2176,10 +2175,16 @@ static int set_local_var(char *str, unsigned flags)
2176 *eq_sign = '='; 2175 *eq_sign = '=';
2177 } 2176 }
2178 if (cur->var_nest_level < local_lvl) { 2177 if (cur->var_nest_level < local_lvl) {
2179 /* New variable is declared as local, 2178 /* New variable is local ("local VAR=VAL" or
2179 * "VAR=VAL cmd")
2180 * and existing one is global, or local 2180 * and existing one is global, or local
2181 * from enclosing function. 2181 * on a lower level that new one.
2182 * Remove and save old one: */ 2182 * Remove and save old one:
2183 */
2184 debug_printf_env("shadowing %s'%s'/%u by '%s'/%u\n",
2185 cur->flg_export ? "exported " : "",
2186 cur->varstr, cur->var_nest_level, str, local_lvl
2187 );
2183 *cur_pp = cur->next; 2188 *cur_pp = cur->next;
2184 cur->next = *G.shadowed_vars_pp; 2189 cur->next = *G.shadowed_vars_pp;
2185 *G.shadowed_vars_pp = cur; 2190 *G.shadowed_vars_pp = cur;
@@ -2197,6 +2202,7 @@ static int set_local_var(char *str, unsigned flags)
2197 } 2202 }
2198 2203
2199 if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) { 2204 if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) {
2205 debug_printf_env("assignement '%s' does not change anything\n", str);
2200 free_and_exp: 2206 free_and_exp:
2201 free(str); 2207 free(str);
2202 goto exp; 2208 goto exp;
@@ -2204,6 +2210,7 @@ static int set_local_var(char *str, unsigned flags)
2204 if (cur->max_len != 0) { 2210 if (cur->max_len != 0) {
2205 if (cur->max_len >= strlen(str)) { 2211 if (cur->max_len >= strlen(str)) {
2206 /* This one is from startup env, reuse space */ 2212 /* This one is from startup env, reuse space */
2213 debug_printf_env("reusing startup env for '%s'\n", str);
2207 strcpy(cur->varstr, str); 2214 strcpy(cur->varstr, str);
2208 goto free_and_exp; 2215 goto free_and_exp;
2209 } 2216 }
@@ -2221,7 +2228,7 @@ static int set_local_var(char *str, unsigned flags)
2221 goto set_str_and_exp; 2228 goto set_str_and_exp;
2222 } 2229 }
2223 2230
2224 /* Not found - create new variable struct */ 2231 /* Not found or shadowed - create new variable struct */
2225 cur = xzalloc(sizeof(*cur)); 2232 cur = xzalloc(sizeof(*cur));
2226 cur->var_nest_level = local_lvl; 2233 cur->var_nest_level = local_lvl;
2227 cur->next = *cur_pp; 2234 cur->next = *cur_pp;
@@ -2250,7 +2257,7 @@ static int set_local_var(char *str, unsigned flags)
2250 /* unsetenv was already done */ 2257 /* unsetenv was already done */
2251 } else { 2258 } else {
2252 int i; 2259 int i;
2253 debug_printf_env("%s: putenv '%s'\n", __func__, cur->varstr); 2260 debug_printf_env("%s: putenv '%s'/%u\n", __func__, cur->varstr, cur->var_nest_level);
2254 i = putenv(cur->varstr); 2261 i = putenv(cur->varstr);
2255 /* only now we can free old exported malloced string */ 2262 /* only now we can free old exported malloced string */
2256 free(free_me); 2263 free(free_me);
@@ -2313,21 +2320,6 @@ static int unset_local_var(const char *name)
2313} 2320}
2314#endif 2321#endif
2315 2322
2316static void unset_vars(char **strings)
2317{
2318 char **v;
2319
2320 if (!strings)
2321 return;
2322 v = strings;
2323 while (*v) {
2324 const char *eq = strchrnul(*v, '=');
2325 unset_local_var_len(*v, (int)(eq - *v));
2326 v++;
2327 }
2328 free(strings);
2329}
2330
2331#if BASH_HOSTNAME_VAR || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_READ || ENABLE_HUSH_GETOPTS 2323#if BASH_HOSTNAME_VAR || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_READ || ENABLE_HUSH_GETOPTS
2332static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val) 2324static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val)
2333{ 2325{
@@ -2349,15 +2341,20 @@ static void add_vars(struct variable *var)
2349 var->next = G.top_var; 2341 var->next = G.top_var;
2350 G.top_var = var; 2342 G.top_var = var;
2351 if (var->flg_export) { 2343 if (var->flg_export) {
2352 debug_printf_env("%s: restoring exported '%s'\n", __func__, var->varstr); 2344 debug_printf_env("%s: restoring exported '%s'/%u\n", __func__, var->varstr, var->var_nest_level);
2353 putenv(var->varstr); 2345 putenv(var->varstr);
2354 } else { 2346 } else {
2355 debug_printf_env("%s: restoring variable '%s'\n", __func__, var->varstr); 2347 debug_printf_env("%s: restoring variable '%s'/%u\n", __func__, var->varstr, var->var_nest_level);
2356 } 2348 }
2357 var = next; 2349 var = next;
2358 } 2350 }
2359} 2351}
2360 2352
2353/* We put strings[i] into variable table and possibly putenv them.
2354 * If variable is read only, we can free the strings[i]
2355 * which attempts to overwrite it.
2356 * The strings[] vector itself is freed.
2357 */
2361static struct variable *set_vars_and_save_old(char **strings) 2358static struct variable *set_vars_and_save_old(char **strings)
2362{ 2359{
2363 char **s; 2360 char **s;
@@ -2365,6 +2362,7 @@ static struct variable *set_vars_and_save_old(char **strings)
2365 2362
2366 if (!strings) 2363 if (!strings)
2367 return old; 2364 return old;
2365
2368 s = strings; 2366 s = strings;
2369 while (*s) { 2367 while (*s) {
2370 struct variable *var_p; 2368 struct variable *var_p;
@@ -2398,11 +2396,15 @@ static struct variable *set_vars_and_save_old(char **strings)
2398 var_p->next = old; 2396 var_p->next = old;
2399 old = var_p; 2397 old = var_p;
2400 } 2398 }
2401 set_local_var(*s, SETFLAG_EXPORT); 2399 //bb_error_msg("G.var_nest_level:%d", G.var_nest_level);
2400 set_local_var(*s, (G.var_nest_level << SETFLAG_VARLVL_SHIFT) | SETFLAG_EXPORT);
2401 } else if (HUSH_DEBUG) {
2402 bb_error_msg_and_die("BUG in varexp4");
2402 } 2403 }
2403 s++; 2404 s++;
2404 next: ; 2405 next: ;
2405 } 2406 }
2407 free(strings);
2406 return old; 2408 return old;
2407} 2409}
2408 2410
@@ -6339,7 +6341,7 @@ static char **expand_strvec_to_strvec_singleword_noglob(char **argv)
6339#endif 6341#endif
6340 6342
6341/* Used for expansion of right hand of assignments, 6343/* Used for expansion of right hand of assignments,
6342 * $((...)), heredocs, variable espansion parts. 6344 * $((...)), heredocs, variable expansion parts.
6343 * 6345 *
6344 * NB: should NOT do globbing! 6346 * NB: should NOT do globbing!
6345 * "export v=/bin/c*; env | grep ^v=" outputs "v=/bin/c*" 6347 * "export v=/bin/c*; env | grep ^v=" outputs "v=/bin/c*"
@@ -7385,6 +7387,7 @@ static void exec_function(char ***to_free,
7385static void enter_var_nest_level(void) 7387static void enter_var_nest_level(void)
7386{ 7388{
7387 G.var_nest_level++; 7389 G.var_nest_level++;
7390 debug_printf_env("var_nest_level++ %u\n", G.var_nest_level);
7388 7391
7389 /* Try: f() { echo -n .; f; }; f 7392 /* Try: f() { echo -n .; f; }; f
7390 * struct variable::var_nest_level is uint16_t, 7393 * struct variable::var_nest_level is uint16_t,
@@ -7408,17 +7411,22 @@ static void leave_var_nest_level(void)
7408 continue; 7411 continue;
7409 } 7412 }
7410 /* Unexport */ 7413 /* Unexport */
7411 if (cur->flg_export) 7414 if (cur->flg_export) {
7415 debug_printf_env("unexporting nested '%s'/%u\n", cur->varstr, cur->var_nest_level);
7412 bb_unsetenv(cur->varstr); 7416 bb_unsetenv(cur->varstr);
7417 }
7413 /* Remove from global list */ 7418 /* Remove from global list */
7414 *cur_pp = cur->next; 7419 *cur_pp = cur->next;
7415 /* Free */ 7420 /* Free */
7416 if (!cur->max_len) 7421 if (!cur->max_len) {
7422 debug_printf_env("freeing nested '%s'/%u\n", cur->varstr, cur->var_nest_level);
7417 free(cur->varstr); 7423 free(cur->varstr);
7424 }
7418 free(cur); 7425 free(cur);
7419 } 7426 }
7420 7427
7421 G.var_nest_level--; 7428 G.var_nest_level--;
7429 debug_printf_env("var_nest_level-- %u\n", G.var_nest_level);
7422 if (HUSH_DEBUG && (int)G.var_nest_level < 0) 7430 if (HUSH_DEBUG && (int)G.var_nest_level < 0)
7423 bb_error_msg_and_die("BUG: nesting underflow"); 7431 bb_error_msg_and_die("BUG: nesting underflow");
7424} 7432}
@@ -7431,11 +7439,12 @@ static int run_function(const struct function *funcp, char **argv)
7431 7439
7432 save_and_replace_G_args(&sv, argv); 7440 save_and_replace_G_args(&sv, argv);
7433 7441
7434 /* "we are in function, ok to use return" */ 7442 /* "We are in function, ok to use return" */
7435 sv_flg = G_flag_return_in_progress; 7443 sv_flg = G_flag_return_in_progress;
7436 G_flag_return_in_progress = -1; 7444 G_flag_return_in_progress = -1;
7437 7445
7438 enter_var_nest_level(); 7446 /* Make "local" variables properly shadow previous ones */
7447 IF_HUSH_LOCAL(enter_var_nest_level();)
7439 IF_HUSH_LOCAL(G.func_nest_level++;) 7448 IF_HUSH_LOCAL(G.func_nest_level++;)
7440 7449
7441 /* On MMU, funcp->body is always non-NULL */ 7450 /* On MMU, funcp->body is always non-NULL */
@@ -7451,7 +7460,7 @@ static int run_function(const struct function *funcp, char **argv)
7451 } 7460 }
7452 7461
7453 IF_HUSH_LOCAL(G.func_nest_level--;) 7462 IF_HUSH_LOCAL(G.func_nest_level--;)
7454 leave_var_nest_level(); 7463 IF_HUSH_LOCAL(leave_var_nest_level();)
7455 7464
7456 G_flag_return_in_progress = sv_flg; 7465 G_flag_return_in_progress = sv_flg;
7457 7466
@@ -7608,11 +7617,9 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
7608 7617
7609#if BB_MMU 7618#if BB_MMU
7610 set_vars_and_save_old(new_env); 7619 set_vars_and_save_old(new_env);
7611 free(new_env); /* optional */ 7620 /* we can destroy set_vars_and_save_old's return value,
7612 /* we can also destroy set_vars_and_save_old's return value,
7613 * to save memory */ 7621 * to save memory */
7614#else 7622#else
7615 nommu_save->new_env = new_env;
7616 nommu_save->old_vars = set_vars_and_save_old(new_env); 7623 nommu_save->old_vars = set_vars_and_save_old(new_env);
7617#endif 7624#endif
7618 7625
@@ -8174,10 +8181,10 @@ static int checkjobs_and_fg_shell(struct pipe *fg_pipe)
8174 * subshell: ( list ) [&] 8181 * subshell: ( list ) [&]
8175 */ 8182 */
8176#if !ENABLE_HUSH_MODE_X 8183#if !ENABLE_HUSH_MODE_X
8177#define redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel, argv_expanded) \ 8184#define redirect_and_varexp_helper(old_vars_p, command, squirrel, argv_expanded) \
8178 redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel) 8185 redirect_and_varexp_helper(old_vars_p, command, squirrel)
8179#endif 8186#endif
8180static int redirect_and_varexp_helper(char ***new_env_p, 8187static int redirect_and_varexp_helper(
8181 struct variable **old_vars_p, 8188 struct variable **old_vars_p,
8182 struct command *command, 8189 struct command *command,
8183 struct squirrel **sqp, 8190 struct squirrel **sqp,
@@ -8190,11 +8197,10 @@ static int redirect_and_varexp_helper(char ***new_env_p,
8190 int rcode = setup_redirects(command, sqp); 8197 int rcode = setup_redirects(command, sqp);
8191 if (rcode == 0) { 8198 if (rcode == 0) {
8192 char **new_env = expand_assignments(command->argv, command->assignment_cnt); 8199 char **new_env = expand_assignments(command->argv, command->assignment_cnt);
8193 *new_env_p = new_env;
8194 dump_cmd_in_x_mode(new_env); 8200 dump_cmd_in_x_mode(new_env);
8195 dump_cmd_in_x_mode(argv_expanded); 8201 dump_cmd_in_x_mode(argv_expanded);
8196 if (old_vars_p) 8202 /* this takes ownership of new_env[i] elements, and frees new_env: */
8197 *old_vars_p = set_vars_and_save_old(new_env); 8203 *old_vars_p = set_vars_and_save_old(new_env);
8198 } 8204 }
8199 return rcode; 8205 return rcode;
8200} 8206}
@@ -8284,13 +8290,9 @@ static NOINLINE int run_pipe(struct pipe *pi)
8284 argv = command->argv ? command->argv : (char **) &null_ptr; 8290 argv = command->argv ? command->argv : (char **) &null_ptr;
8285 { 8291 {
8286 const struct built_in_command *x; 8292 const struct built_in_command *x;
8287#if ENABLE_HUSH_FUNCTIONS 8293 IF_HUSH_FUNCTIONS(const struct function *funcp;)
8288 const struct function *funcp; 8294 IF_NOT_HUSH_FUNCTIONS(enum { funcp = 0 };)
8289#else 8295 struct variable *old_vars;
8290 enum { funcp = 0 };
8291#endif
8292 char **new_env = NULL;
8293 struct variable *old_vars = NULL;
8294 8296
8295#if ENABLE_HUSH_LINENO_VAR 8297#if ENABLE_HUSH_LINENO_VAR
8296 if (G.lineno_var) 8298 if (G.lineno_var)
@@ -8303,28 +8305,6 @@ static NOINLINE int run_pipe(struct pipe *pi)
8303 * Try "a=t >file" 8305 * Try "a=t >file"
8304 */ 8306 */
8305 G.expand_exitcode = 0; 8307 G.expand_exitcode = 0;
8306#if 0 /* A few cases in testsuite fail with this code. FIXME */
8307 rcode = redirect_and_varexp_helper(&new_env, /*old_vars:*/ NULL, command, &squirrel, /*argv_expanded:*/ NULL);
8308 /* Set shell variables */
8309 if (new_env) {
8310 argv = new_env;
8311 while (*argv) {
8312 if (set_local_var(*argv, /*flag:*/ 0)) {
8313 /* assignment to readonly var / putenv error? */
8314 rcode = 1;
8315 }
8316 argv++;
8317 }
8318 }
8319 /* Redirect error sets $? to 1. Otherwise,
8320 * if evaluating assignment value set $?, retain it.
8321 * Try "false; q=`exit 2`; echo $?" - should print 2: */
8322 if (rcode == 0)
8323 rcode = G.expand_exitcode;
8324 /* Exit, _skipping_ variable restoring code: */
8325 goto clean_up_and_ret0;
8326
8327#else /* Older, bigger, but more correct code */
8328 8308
8329 rcode = setup_redirects(command, &squirrel); 8309 rcode = setup_redirects(command, &squirrel);
8330 restore_redirects(squirrel); 8310 restore_redirects(squirrel);
@@ -8358,7 +8338,6 @@ static NOINLINE int run_pipe(struct pipe *pi)
8358 debug_leave(); 8338 debug_leave();
8359 debug_printf_exec("run_pipe: return %d\n", rcode); 8339 debug_printf_exec("run_pipe: return %d\n", rcode);
8360 return rcode; 8340 return rcode;
8361#endif
8362 } 8341 }
8363 8342
8364 /* Expand the rest into (possibly) many strings each */ 8343 /* Expand the rest into (possibly) many strings each */
@@ -8372,6 +8351,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
8372 } 8351 }
8373 8352
8374 /* if someone gives us an empty string: `cmd with empty output` */ 8353 /* if someone gives us an empty string: `cmd with empty output` */
8354//TODO: what about: var=EXPR `` >FILE ? will var be set? Will FILE be created?
8375 if (!argv_expanded[0]) { 8355 if (!argv_expanded[0]) {
8376 free(argv_expanded); 8356 free(argv_expanded);
8377 debug_leave(); 8357 debug_leave();
@@ -8394,7 +8374,14 @@ static NOINLINE int run_pipe(struct pipe *pi)
8394 goto clean_up_and_ret1; 8374 goto clean_up_and_ret1;
8395 } 8375 }
8396 } 8376 }
8397 rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, &squirrel, argv_expanded); 8377
8378 /* Without bumping var nesting level, this leaks
8379 * exported $a:
8380 * a=b true; env | grep ^a=
8381 */
8382 enter_var_nest_level();
8383 rcode = redirect_and_varexp_helper(&old_vars, command, &squirrel, argv_expanded);
8384
8398 if (rcode == 0) { 8385 if (rcode == 0) {
8399 if (!funcp) { 8386 if (!funcp) {
8400 debug_printf_exec(": builtin '%s' '%s'...\n", 8387 debug_printf_exec(": builtin '%s' '%s'...\n",
@@ -8405,24 +8392,23 @@ static NOINLINE int run_pipe(struct pipe *pi)
8405 } 8392 }
8406#if ENABLE_HUSH_FUNCTIONS 8393#if ENABLE_HUSH_FUNCTIONS
8407 else { 8394 else {
8408# if ENABLE_HUSH_LOCAL 8395 struct variable **sv = G.shadowed_vars_pp;
8409 struct variable **sv; 8396 /* Make "local" builtins in the called function
8410 sv = G.shadowed_vars_pp; 8397 * stash shadowed variables in old_vars list
8398 */
8411 G.shadowed_vars_pp = &old_vars; 8399 G.shadowed_vars_pp = &old_vars;
8412# endif 8400
8413 debug_printf_exec(": function '%s' '%s'...\n", 8401 debug_printf_exec(": function '%s' '%s'...\n",
8414 funcp->name, argv_expanded[1]); 8402 funcp->name, argv_expanded[1]);
8415 rcode = run_function(funcp, argv_expanded) & 0xff; 8403 rcode = run_function(funcp, argv_expanded) & 0xff;
8416# if ENABLE_HUSH_LOCAL 8404
8417 G.shadowed_vars_pp = sv; 8405 G.shadowed_vars_pp = sv;
8418# endif
8419 } 8406 }
8420#endif 8407#endif
8421 } 8408 }
8422 clean_up_and_ret: 8409 clean_up_and_ret:
8423 unset_vars(new_env); 8410 leave_var_nest_level();
8424 add_vars(old_vars); 8411 add_vars(old_vars);
8425/* clean_up_and_ret0: */
8426 restore_redirects(squirrel); 8412 restore_redirects(squirrel);
8427 /* 8413 /*
8428 * Try "usleep 99999999" + ^C + "echo $?" 8414 * Try "usleep 99999999" + ^C + "echo $?"
@@ -8453,7 +8439,8 @@ static NOINLINE int run_pipe(struct pipe *pi)
8453 if (ENABLE_FEATURE_SH_NOFORK && NUM_APPLETS > 1) { 8439 if (ENABLE_FEATURE_SH_NOFORK && NUM_APPLETS > 1) {
8454 int n = find_applet_by_name(argv_expanded[0]); 8440 int n = find_applet_by_name(argv_expanded[0]);
8455 if (n >= 0 && APPLET_IS_NOFORK(n)) { 8441 if (n >= 0 && APPLET_IS_NOFORK(n)) {
8456 rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, &squirrel, argv_expanded); 8442 enter_var_nest_level();
8443 rcode = redirect_and_varexp_helper(&old_vars, command, &squirrel, argv_expanded);
8457 if (rcode == 0) { 8444 if (rcode == 0) {
8458 debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", 8445 debug_printf_exec(": run_nofork_applet '%s' '%s'...\n",
8459 argv_expanded[0], argv_expanded[1]); 8446 argv_expanded[0], argv_expanded[1]);
@@ -8487,7 +8474,6 @@ static NOINLINE int run_pipe(struct pipe *pi)
8487 struct fd_pair pipefds; 8474 struct fd_pair pipefds;
8488#if !BB_MMU 8475#if !BB_MMU
8489 volatile nommu_save_t nommu_save; 8476 volatile nommu_save_t nommu_save;
8490 nommu_save.new_env = NULL;
8491 nommu_save.old_vars = NULL; 8477 nommu_save.old_vars = NULL;
8492 nommu_save.argv = NULL; 8478 nommu_save.argv = NULL;
8493 nommu_save.argv_from_re_execing = NULL; 8479 nommu_save.argv_from_re_execing = NULL;
@@ -8580,7 +8566,6 @@ static NOINLINE int run_pipe(struct pipe *pi)
8580 /* Clean up after vforked child */ 8566 /* Clean up after vforked child */
8581 free(nommu_save.argv); 8567 free(nommu_save.argv);
8582 free(nommu_save.argv_from_re_execing); 8568 free(nommu_save.argv_from_re_execing);
8583 unset_vars(nommu_save.new_env);
8584 add_vars(nommu_save.old_vars); 8569 add_vars(nommu_save.old_vars);
8585#endif 8570#endif
8586 free(argv_expanded); 8571 free(argv_expanded);
@@ -10066,7 +10051,11 @@ static int FAST_FUNC builtin_local(char **argv)
10066 return EXIT_FAILURE; /* bash compat */ 10051 return EXIT_FAILURE; /* bash compat */
10067 } 10052 }
10068 argv++; 10053 argv++;
10069 return helper_export_local(argv, G.var_nest_level << SETFLAG_VARLVL_SHIFT); 10054 /* Since all builtins run in a nested variable level,
10055 * need to use level - 1 here. Or else the variable will be removed at once
10056 * after builtin returns.
10057 */
10058 return helper_export_local(argv, (G.var_nest_level - 1) << SETFLAG_VARLVL_SHIFT);
10070} 10059}
10071#endif 10060#endif
10072 10061
diff --git a/shell/hush_test/hush-vars/var_nested1.right b/shell/hush_test/hush-vars/var_nested1.right
new file mode 100644
index 000000000..f4bc5f4b6
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_nested1.right
@@ -0,0 +1,3 @@
1Expected:AB Actual:AB
2Expected:Ab Actual:Ab
3Expected:ab Actual:ab
diff --git a/shell/hush_test/hush-vars/var_nested1.tests b/shell/hush_test/hush-vars/var_nested1.tests
new file mode 100755
index 000000000..59e4a14fa
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_nested1.tests
@@ -0,0 +1,16 @@
1f() { a=A; b=B; }
2
3a=a
4b=b
5f
6echo Expected:AB Actual:$a$b
7
8a=a
9b=b
10b= f
11echo Expected:Ab Actual:$a$b
12
13a=a
14b=b
15a= b= f
16echo Expected:ab Actual:$a$b
diff --git a/shell/hush_test/hush-vars/var_nested2.right b/shell/hush_test/hush-vars/var_nested2.right
new file mode 100644
index 000000000..c930d971c
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_nested2.right
@@ -0,0 +1 @@
aB
diff --git a/shell/hush_test/hush-vars/var_nested2.tests b/shell/hush_test/hush-vars/var_nested2.tests
new file mode 100755
index 000000000..e8865861e
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_nested2.tests
@@ -0,0 +1,2 @@
1# the bug was easier to trigger in one-liner form
2a=a; b=b; f() { a=A; b=B; }; a= f; echo $a$b