diff --git a/NEWS b/NEWS index 44fdd3665..0d9e57ad4 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,10 @@ Any uppercase BUG_* names are modernish shell bug IDs. extra characters. 2. The backslash now escapes a subsequent interrupt (^C) as documented. +- Fixed a longstanding bug with shared-state command substitutions of the form + ${ command; }. If these were executed in a subshell, changes made within + could survive not only the command substitution but also the parent subshell. + 2021-02-15: - Fixed a regression introduced by ksh93 (was not in ksh88): an empty 'case' diff --git a/src/cmd/ksh93/sh/subshell.c b/src/cmd/ksh93/sh/subshell.c index 529ef2da1..fe91e1ee5 100644 --- a/src/cmd/ksh93/sh/subshell.c +++ b/src/cmd/ksh93/sh/subshell.c @@ -106,6 +106,8 @@ static struct subshell #endif /* _lib_fchdir */ } *subshell_data; +static char subshell_noscope; /* for temporarily disabling all virtual subshell scope creation */ + static unsigned int subenv; @@ -259,18 +261,39 @@ Namval_t *sh_assignok(register Namval_t *np,int add) { register Namval_t *mp; register struct Link *lp; - register struct subshell *sp = (struct subshell*)subshell_data; + struct subshell *sp = subshell_data; Shell_t *shp = sp->shp; Dt_t *dp= shp->var_tree; Namval_t *mpnext; Namarr_t *ap; unsigned int save; /* - * Don't save if told not to (see nv_restore()) or if we're in a ${ subshare; }. + * Don't create a scope if told not to (see nv_restore()). * Also, moving/copying ${.sh.level} (SH_LEVELNOD) may crash the shell. */ - if(!sp->shpwd || shp->subshare || add<2 && np==SH_LEVELNOD) + if(subshell_noscope || add<2 && np==SH_LEVELNOD) return(np); + /* + * Don't create a scope for a ${ shared-state command substitution; } (a.k.a. subshare). + * However, if the subshare is in a virtual subshell, we need to create a scope in that. + * If so, temporarily make that the current subshell and call this function recursively. + */ + if(shp->subshare) + { + while(subshell_data->subshare) /* move up as long as parent is also a subshare */ + subshell_data = subshell_data->prev; + subshell_data = subshell_data->prev; /* move to first non-subshare parent */ + if(!subshell_data) /* if that's not a virtual subshell, don't create a scope */ + { + subshell_data = sp; + return(np); + } + shp->subshare = 0; + np = sh_assignok(np,add); + shp->subshare = 1; + subshell_data = sp; + return(np); + } if((ap=nv_arrayptr(np)) && (mp=nv_opensub(np))) { shp->last_root = ap->table; @@ -329,10 +352,9 @@ static void nv_restore(struct subshell *sp) { register struct Link *lp, *lq; register Namval_t *mp, *np; - const char *save = sp->shpwd; Namval_t *mpnext; int flags,nofree; - sp->shpwd = 0; /* make sure sh_assignok doesn't save with nv_unset() */ + subshell_noscope = 1; for(lp=sp->svar; lp; lp=lq) { np = (Namval_t*)&lp->dict; @@ -389,7 +411,7 @@ static void nv_restore(struct subshell *sp) free((void*)lp); sp->svar = lq; } - sp->shpwd=save; + subshell_noscope = 0; } /* diff --git a/src/cmd/ksh93/tests/subshell.sh b/src/cmd/ksh93/tests/subshell.sh index d287b8072..1f941e42a 100755 --- a/src/cmd/ksh93/tests/subshell.sh +++ b/src/cmd/ksh93/tests/subshell.sh @@ -914,5 +914,41 @@ got=$( { "$SHELL" -c '(cd /); print -r -- "PWD=$PWD"'; } 2>&1 ) "(got status $e$( ((e>128)) && print -n / && kill -l "$e"), $(printf %q "$got"))" cd "$tmp" +# ====== +# Before 93u+m 2021-02-17, the state of ${ shared-state command substitutions; } leaked out of parent virtual subshells. +# https://github.com/ksh93/ksh/issues/143 + +v=main +d=${ v=shared; } +[[ $v == shared ]] || err_exit "shared comsub in main shell wrongly scoped" + +v=main +(d=${ v=shared; }; [[ $v == shared ]]) || err_exit "shared comsub in subshell wrongly scoped (1)" +[[ $v == main ]] || err_exit "shared comsub leaks out of subshell (1)" + +v=main +(v=sub; d=${ v=shared; }; [[ $v == shared ]]) || err_exit "shared comsub in subshell wrongly scoped (2)" +[[ $v == main ]] || err_exit "shared comsub leaks out of subshell (2)" + +v=main +(v=sub; (d=${ v=shared; }); [[ $v == sub ]]) || err_exit "shared comsub leaks out of nested subshell" +[[ $v == main ]] || err_exit "shared comsub leaks out of subshell (3)" + +v=main +(d=${ d=${ v=shared; }; }; [[ $v == shared ]]) || err_exit "nested shared comsub in subshell wrongly scoped (1)" +[[ $v == main ]] || err_exit "shared comsub leaks out of subshell (4)" + +v=main +(v=sub; d=${ d=${ v=shared; }; }; [[ $v == shared ]]) || err_exit "nested shared comsub in subshell wrongly scoped (2)" +[[ $v == main ]] || err_exit "shared comsub leaks out of subshell (5)" + +v=main +( (d=${ v=shared; }; [[ $v == shared ]]) ) || err_exit "shared comsub in nested subshell wrongly scoped (1)" +[[ $v == main ]] || err_exit "shared comsub leaks out of subshell (6)" + +v=main +(v=sub; (d=${ v=shared; }; [[ $v == shared ]]) ) || err_exit "shared comsub in nested subshell wrongly scoped (2)" +[[ $v == main ]] || err_exit "shared comsub leaks out of subshell (7)" + # ====== exit $((Errors<125?Errors:125))