diff --git a/NEWS b/NEWS index b82989706711..608ef1986bbf 100644 --- a/NEWS +++ b/NEWS @@ -15,6 +15,9 @@ Any uppercase BUG_* names are modernish shell bug IDs. an unrecognized option. 'sleep -: 1' will now show a usage message and exit instead of sleep for one second. +- Fixed a bug that caused the 'typeset' variable attributes -a, -A, -l, and + -u to leak out of a subshell if they were set without assigning a value. + 2020-07-23: - Fixed an infinite loop that could occur when ksh is the system's /bin/sh. diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index 906d0a772a0f..d9175068f0d9 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -691,13 +691,22 @@ static int setall(char **argv,register int flag,Dt_t *troot,struct tdata *tp if(!comvar && !iarray) continue; } - if(!nv_isarray(np) && !strchr(name,'=') && !(shp->envlist && nv_onlist(shp->envlist,name))) + + /* Create local scope for virtual subshell */ + if(shp->subshell) { - if(comvar || (shp->last_root==shp->var_tree && (tp->tp || (!shp->st.real_fun && (nvflags&NV_STATIC)) || (!(flag&(NV_EXPORT|NV_RDONLY)) && nv_isattr(np,(NV_EXPORT|NV_IMPORT))==(NV_EXPORT|NV_IMPORT))))) -{ - _nv_unset(np,0); -} + if(!nv_isattr(np,NV_NODISC|NV_ARRAY) && !nv_isvtree(np)) + { + /* + * Variables with internal trap/discipline functions (LC_*, LINENO, etc.) need to be + * cloned, as moving them will remove the discipline function. + */ + np=sh_assignok(np,2); + } + else + np=sh_assignok(np,0); } + if(troot==shp->var_tree) { if(iarray) diff --git a/src/cmd/ksh93/tests/arrays.sh b/src/cmd/ksh93/tests/arrays.sh index b700d1fb424e..88c1d9de4208 100755 --- a/src/cmd/ksh93/tests/arrays.sh +++ b/src/cmd/ksh93/tests/arrays.sh @@ -180,6 +180,12 @@ then err_exit 'arithmetic expressions in typeset not working' fi unset foo typeset foo=bar +typeset -a foo +if [[ ${foo[0]} != bar ]] +then err_exit 'initial value not preserved when typecast to indexed array' +fi +unset foo +typeset foo=bar typeset -A foo if [[ ${foo[0]} != bar ]] then err_exit 'initial value not preserved when typecast to associative' @@ -674,5 +680,32 @@ actual=$("$SHELL" -c 'A[0]="'\''" B[0]=aa C[0]=aa; typeset -a') \ [[ $actual == "$expect" ]] || err_exit 'Incorrect output when listing indexed array' \ "(expected $(printf %q "$expect"), got $(printf %q "$actual"))" +# ====== +# Arrays in virtual/non-forked subshells + +unset foo +(typeset -a foo) +[[ -n ${ typeset -p foo; } ]] && err_exit 'Null indexed array leaks out of subshell' + +unset foo +(typeset -a foo=(1 2 3)) +[[ -n ${ typeset -p foo; } ]] && err_exit 'Indexed array with init value leaks out of subshell' + +unset foo +(typeset -a foo; foo=(1 2 3)) +[[ -n ${ typeset -p foo; } ]] && err_exit 'Indexed array leaks out of subshell' + +unset foo +(typeset -A foo) +[[ -n ${ typeset -p foo; } ]] && err_exit 'Null associative array leaks out of subshell' + +unset foo +(typeset -A foo=([bar]=baz [lorem]=ipsum)) +[[ -n ${ typeset -p foo; } ]] && err_exit 'Associative array with init value leaks out of subshell' + +unset foo +(typeset -A foo; foo=([bar]=baz [lorem]=ipsum)) +[[ -n ${ typeset -p foo; } ]] && err_exit 'Associative array leaks out of subshell' + # ====== exit $((Errors<125?Errors:125)) diff --git a/src/cmd/ksh93/tests/attributes.sh b/src/cmd/ksh93/tests/attributes.sh index c3b3e0e020c4..faf84d10ada2 100755 --- a/src/cmd/ksh93/tests/attributes.sh +++ b/src/cmd/ksh93/tests/attributes.sh @@ -468,4 +468,53 @@ typeset -Z2 foo=3 export foo [[ $(typeset -p foo) == 'typeset -x -Z 2 -R 2 foo=03' ]] || err_exit '-Z2 not working after export' +# ====== +# unset exported readonly variables, combined with all other possible attributes +typeset -A expect=( + [a]='typeset -x -r -a foo' + [b]='typeset -x -r -b foo' + [i]='typeset -x -r -i foo' + [i37]='typeset -x -r -i 37 foo' + [l]='typeset -x -r -l foo' + [n]='typeset -n -r foo' + [s]='typeset -x -r -s -i 0 foo=0' + [u]='typeset -x -r -u foo' + [A]='typeset -x -r -A foo=()' + [C]='typeset -x -r foo=()' + [E]='typeset -x -r -E foo' + [E12]='typeset -x -r -E 12 foo' + [F]='typeset -x -r -F foo' + [F12]='typeset -x -r -F 12 foo' + [H]='typeset -x -r -H foo' + [L]='typeset -x -r -L 0 foo' +# [L17]='typeset -x -r -L 17 foo' # TODO: outputs '-L 0' + [Mtolower]='typeset -x -r -l foo' + [Mtoupper]='typeset -x -r -u foo' + [R]='typeset -x -r -R 0 foo' +# [R17]='typeset -x -r -R 17 foo' # TODO: outputs '-L 0' + [X]='typeset -x -r -X 32 foo' + [X17]='typeset -x -r -X 17 foo' + [S]='typeset -x -r foo' + [T]='typeset -x -r foo' + [Z]='typeset -x -r -Z 0 -R 0 foo' +# [Z13]='typeset -x -r -Z 13 -R 13 foo' # TODO: outputs 'typeset -x -r -Z 0 -R 0 foo' +) +for flag in a b i i37 l n s u A C E E12 F F12 H L Mtolower Mtoupper R X X17 S T Z +do unset foo + actual=$( + redirect 2>&1 + export foo + (typeset "-$flag" foo; readonly foo; typeset -p foo) + typeset +x foo # unexport + leak=${ typeset -p foo; } + [[ -n $leak ]] && print "SUBSHELL LEAK: $leak" + ) + if [[ $actual != "${expect[$flag]}" ]] + then err_exit "unset exported readonly with -$flag:" \ + "expected $(printf %q "${expect[$flag]}"), got $(printf %q "$actual")" + fi +done +unset expect + +# ====== exit $((Errors<125?Errors:125))