From 9f2389ed93238bf3c0825713cf81ae2fa4a0d209 Mon Sep 17 00:00:00 2001 From: Martijn Dekker Date: Sat, 6 Mar 2021 03:56:52 +0000 Subject: [PATCH] Fix ${x=y} and ${x:=y} for numeric types of x These POSIX expansions first assign y to x if x is unset or empty, respectively, and then they yield the value of x. This was not working on any ksh93 version if x was typeset as numeric (integer or float) but still unset, as in not assigned a value. $ unset a; typeset -i a; printf '%q\n' "${a:=42}" "$a" 0 '' Expected output: 42 42 src/cmd/ksh93/sh/macro.c: - Fix the test for set/unset variable. It was broken because it only checked for the existence of the node, which exists after 'typeset', but did not check if a value had been assigned. This additional check needs to be done with the nv_isnull() macro, but only for expansions of the regular M_BRACE type. Special expansions cannot have an unset state. - As of commit 95294419, we know that an nv_optimize() call may be needed before using nv_isnull() if the shell is compiled with SHOPT_OPTIMIZE. Move the nv_optimize() call from that commit forward to before the new check that calls nv_isnull(), and only bother with it if the type is M_BRACE. src/cmd/ksh93/tests/variables.sh: - Add tests for this bug. Test float and integer, and also check that ${a=b} and ${a:=b} correctly treat the value of 'b' as an arithmetic expression of which the result is assigned to 'a' if 'a' was typeset as numeric. src/cmd/ksh93/tests/attributes.sh, src/cmd/ksh93/tests/comvar.sh, src/cmd/ksh93/tests/nameref.sh, src/cmd/ksh93/tests/types.sh: - Fix a number of tests to report failures correctly. Resolves: https://github.com/ksh93/ksh/issues/157 --- NEWS | 6 ++++++ src/cmd/ksh93/include/version.h | 2 +- src/cmd/ksh93/sh/macro.c | 16 +++++++-------- src/cmd/ksh93/tests/attributes.sh | 2 +- src/cmd/ksh93/tests/comvar.sh | 6 ++++-- src/cmd/ksh93/tests/nameref.sh | 11 +++++----- src/cmd/ksh93/tests/types.sh | 2 +- src/cmd/ksh93/tests/variables.sh | 34 +++++++++++++++++++++++++++++++ 8 files changed, 60 insertions(+), 19 deletions(-) diff --git a/NEWS b/NEWS index 0c2cdc32a..5097a7eea 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,12 @@ For full details, see the git log at: https://github.com/ksh93/ksh Any uppercase BUG_* names are modernish shell bug IDs. +2021-03-06: + +- Fixed an old expansion bug: expansions of type ${var=value} and ${var:=value} + did not perform an assignment and yielded the value 0 if 'var' was typeset as + numeric (integer or float) but had not yet been assigned a value. + 2021-03-05: - Unbalanced quotes and backticks now correctly produce a syntax error diff --git a/src/cmd/ksh93/include/version.h b/src/cmd/ksh93/include/version.h index 6c5932688..485454d96 100644 --- a/src/cmd/ksh93/include/version.h +++ b/src/cmd/ksh93/include/version.h @@ -20,7 +20,7 @@ #define SH_RELEASE_FORK "93u+m" /* only change if you develop a new ksh93 fork */ #define SH_RELEASE_SVER "1.0.0-alpha" /* semantic version number: https://semver.org */ -#define SH_RELEASE_DATE "2021-03-05" /* must be in this format for $((.sh.version)) */ +#define SH_RELEASE_DATE "2021-03-06" /* must be in this format for $((.sh.version)) */ #define SH_RELEASE_CPYR "(c) 2020-2021 Contributors to ksh " SH_RELEASE_FORK /* Scripts sometimes field-split ${.sh.version}, so don't change amount of whitespace. */ diff --git a/src/cmd/ksh93/sh/macro.c b/src/cmd/ksh93/sh/macro.c index 545d09a52..3f7f69496 100644 --- a/src/cmd/ksh93/sh/macro.c +++ b/src/cmd/ksh93/sh/macro.c @@ -1314,7 +1314,7 @@ retry1: * a value; otherwise it always follows the code path for a set parameter, so is * not subject to 'set -u', and may test as set even after 'unset -v IFS'. */ - if(!(*id=='I' && strcmp(id,"IFS")==0 && nv_getval(sh_scoped(mp->shp,IFSNOD)) == NULL)) + if(!(*id=='I' && strcmp(id,"IFS")==0 && nv_getval(sh_scoped(mp->shp,IFSNOD)) == NIL(char*))) np = nv_open(id,mp->shp->var_tree,flag|NV_NOFAIL); if(!np) { @@ -1405,9 +1405,13 @@ retry1: /* * Check if the parameter is set or unset. */ - if(np && (type==M_TREE || !c || !ap)) +#if SHOPT_OPTIMIZE + if(np && type==M_BRACE && mp->shp->argaddr) + nv_optimize(np); /* needed before calling nv_isnull() */ +#endif /* SHOPT_OPTIMIZE */ + if(np && (type==M_BRACE ? !nv_isnull(np) : (type==M_TREE || !c || !ap))) { - /* The parameter is set. */ + /* Either the parameter is set, or it's a special type of expansion where 'unset' doesn't apply. */ char *savptr; c = *((unsigned char*)stkptr(stkp,offset-1)); savptr = stkfreeze(stkp,0); @@ -1443,13 +1447,7 @@ retry1: if(ap) v = nv_arrayisset(np,ap)?(char*)"x":0; else - { -#if SHOPT_OPTIMIZE - if(mp->shp->argaddr) - nv_optimize(np); /* avoid BUG_ISSETLOOP */ -#endif /* SHOPT_OPTIMIZE */ v = nv_isnull(np)?0:(char*)"x"; - } } else v = nv_getval(np); diff --git a/src/cmd/ksh93/tests/attributes.sh b/src/cmd/ksh93/tests/attributes.sh index d32d16117..ddcdc206c 100755 --- a/src/cmd/ksh93/tests/attributes.sh +++ b/src/cmd/ksh93/tests/attributes.sh @@ -204,7 +204,7 @@ else b1=iIWTk5ZAppaZk4Q= fi z=$b1 typeset -b x=$b1 -[[ $x == "$z" ]] || print -u2 'binary variable not expanding correctly' +[[ $x == "$z" ]] || err_exit "binary variable not expanding correctly ($(printf %q "$x") != $(printf %q "$z"))" [[ $(printf "%B" x) == $t1 ]] || err_exit 'typeset -b not working' typeset -b -Z5 a=$b1 [[ $(printf "%B" a) == $w1 ]] || err_exit 'typeset -b -Z5 not working' diff --git a/src/cmd/ksh93/tests/comvar.sh b/src/cmd/ksh93/tests/comvar.sh index 80da1cd46..362e7d6b2 100755 --- a/src/cmd/ksh93/tests/comvar.sh +++ b/src/cmd/ksh93/tests/comvar.sh @@ -482,9 +482,11 @@ typeset -C -A hello19=( ) ) expected="typeset -C -A hello19=([19]=(one='xone 19';two='xtwo 19') [23]=(one='xone 23';two='xtwo 23'))" -[[ $(typeset -p hello19) == "$expected" ]] || print -u2 'typeset -p hello19 incorrect' +got=$(typeset -p hello19) +[[ $got == "$expected" ]] || err_exit "typeset -p hello19 incorrect (expected $(printf %q "$expected"), got $(printf %q "$got"))" expected=$'(\n\tone=\'xone 19\'\n\ttwo=\'xtwo 19\'\n) (\n\tone=\'xone 23\'\n\ttwo=\'xtwo 23\'\n)' -[[ ${hello19[@]} == "$expected" ]] || print -u2 '${hello19[@]} incorrect' +got=${hello19[@]} +[[ $got == "$expected" ]] || err_exit "\${hello19[@]} incorrect (expected $(printf %q "$expected"), got $(printf %q "$got"))" typeset -C -A foo1=( abc="alphabet" ) foo2=( abc="alphabet" ) function add_one diff --git a/src/cmd/ksh93/tests/nameref.sh b/src/cmd/ksh93/tests/nameref.sh index 8cb4e51c7..86e557f44 100755 --- a/src/cmd/ksh93/tests/nameref.sh +++ b/src/cmd/ksh93/tests/nameref.sh @@ -161,9 +161,10 @@ then err_exit 'for loop nameref optimization test2 error' fi unset -n x foo bar -if [[ $(nameref x=foo;for x in foo bar;do print ${!x};done) != $'foo\nbar' ]] -then err_exit 'for loop optimization with namerefs not working' -fi +exp=$'foo\nbar' +got=$(nameref x=foo;for x in foo bar;do print ${!x};done) +[[ $got == "$exp" ]] || err_exit 'for loop optimization with namerefs not working' \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" if [[ $( p=(x=(r=3) y=(r=4)) for i in x y @@ -465,9 +466,9 @@ EOF } 2> /dev/null #|| print -u2 bad exitval=$? if [[ $(kill -l $exitval) == SEGV ]] -then print -u2 'name reference to unset type instance causes segmentation violation' +then err_exit 'name reference to unset type instance causes segmentation violation' else if((exitval)) - then print -u2 'name reference to unset type instance not redirected to .deleted' + then err_exit 'name reference to unset type instance not redirected to .deleted' fi fi diff --git a/src/cmd/ksh93/tests/types.sh b/src/cmd/ksh93/tests/types.sh index 91585325f..91ee33a1b 100755 --- a/src/cmd/ksh93/tests/types.sh +++ b/src/cmd/ksh93/tests/types.sh @@ -84,7 +84,7 @@ Frame_t frame [[ $(typeset -p frame) == 'Frame_t frame=(typeset file;typeset lineno)' ]] || err_exit 'empty fields in type not displayed' x=( typeset -a arr=([2]=abc [4]=(x=1 y=def));zz=abc) typeset -C y=x -[[ "$x" == "$y" ]] || print -u2 'y is not equal to x' +[[ "$x" == "$y" ]] || err_exit "y is not equal to x (y == $(printf %q "$y"); x == $(printf %q "$x"))" Type_t z=(y=(xa=bb xq=cc)) typeset -A arr=([foo]=one [bar]=2) typeset -A brr=([foo]=one [bar]=2) diff --git a/src/cmd/ksh93/tests/variables.sh b/src/cmd/ksh93/tests/variables.sh index 062b6dcb6..2a4565061 100755 --- a/src/cmd/ksh93/tests/variables.sh +++ b/src/cmd/ksh93/tests/variables.sh @@ -1198,5 +1198,39 @@ unset .sh.fun got=$(some_func() { :; }; trap some_func DEBUG; trap - DEBUG; print -r "${.sh.fun}") [[ -z $got ]] || err_exit "\${.sh.fun} leaks out of DEBUG trap (got $(printf %q "$got"))" +# ===== +# Before 2021-03-06, ${foo=bar} and ${foo:=bar} did not work if `foo` had a numeric type +# https://github.com/ksh93/ksh/issues/157 + +unset a b +typeset -i a +b=3+39 +got=${a=b} +[[ $got == 42 ]] || err_exit "\${a=b}: expansion not working for integer type (expected '42', got '$got')" +[[ $a == 42 ]] || err_exit "\${a=b}: a was not assigned the correct integer value (expected '42', got '$a')" + +unset a b +typeset -F a +b=3.75+38.25 +got=${a=b} +exp=42.0000000000 +[[ $got == "$exp" ]] || err_exit "\${a=b}: expansion not working for float type (expected '$exp', got '$got')" +[[ $a == "$exp" ]] || err_exit "\${a=b}: a was not assigned the correct float value (expected '$exp', got '$a')" + +unset a b +typeset -i a +b=3+39 +got=${a:=b} +[[ $got == 42 ]] || err_exit "\${a:=b}: expansion not working for integer type (expected '42', got '$got')" +[[ $a == 42 ]] || err_exit "\${a:=b}: a was not assigned the correct integer value (expected '42', got '$a')" + +unset a b +typeset -F a +b=3.75+38.25 +got=${a:=b} +exp=42.0000000000 +[[ $got == "$exp" ]] || err_exit "\${a:=b}: expansion not working for float type (expected '$exp', got '$got')" +[[ $a == "$exp" ]] || err_exit "\${a:=b}: a was not assigned the correct float value (expected '$exp', got '$a')" + # ====== exit $((Errors<125?Errors:125))