From 787058bdbf60182c4702de563729c21e2e8e23fa Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Tue, 8 Feb 2022 11:40:17 -0800 Subject: [PATCH] Fix the output of `typeset -p` for two dimensional indexed arrays (#454) In ksh93v- 2012-10-04 the following bugfix is noted in the changelog (this fix was most likely part of ksh93v- 2012-09-27, although that version is not archived anywhere): 12-09-21 A bug in which the output of a two dimensional sparse indexed array would cause the second subscript be treated as an associative array when read back in has been fixed. Elements that are sparse indexed arrays now are prefixed type "typeset -a". Below is a before and after of this change: # Before $ typeset -a foo[1][2]=bar $ typeset -p foo typeset -a foo=([1]=([2]=bar) ) # After $ typeset -a foo[1][2]=bar $ typeset -p foo typeset -a foo=(typeset -a [1]=([2]=bar) ) src/cmd/ksh93/sh/*.c: - Backport changes from ksh93v- to print 'typeset -a' before sparse indexed arrays and properly handle 'typeset -a' in reinput commands from 'typeset -p'. src/cmd/ksh93/tests: - Add two regression tests to arrays.sh for this change. - Update the existing regression tests for compatibility with the new printed typeset output. --- NEWS | 5 ++++ src/cmd/ksh93/include/argnod.h | 1 + src/cmd/ksh93/include/shlex.h | 1 + src/cmd/ksh93/sh/name.c | 4 ++- src/cmd/ksh93/sh/nvtree.c | 3 ++ src/cmd/ksh93/sh/parse.c | 52 ++++++++++++++++++++++++++++----- src/cmd/ksh93/sh/tdump.c | 2 +- src/cmd/ksh93/sh/trestore.c | 2 +- src/cmd/ksh93/tests/arrays.sh | 16 ++++++++++ src/cmd/ksh93/tests/arrays2.sh | 2 +- src/cmd/ksh93/tests/comvar.sh | 2 +- src/cmd/ksh93/tests/nameref.sh | 4 +-- src/cmd/ksh93/tests/treemove.sh | 2 +- 13 files changed, 80 insertions(+), 16 deletions(-) diff --git a/NEWS b/NEWS index f6d47edae..ee998203f 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,11 @@ Any uppercase BUG_* names are modernish shell bug IDs. an incompletely defined 'foo_t' built-in comamnd that will crash the shell when used. Instead, it is now silently ignored for backwards compatibility. +- A bug in which the output of a two dimensional sparse indexed array would + cause the second subscript to be treated as an associative array when read + back in has been fixed. Elements that are sparse indexed arrays are now + prefixed with "typeset -a". + 2022-02-05: - Fixed: for indexed arrays, given an unset array member a[i] with i > 0, diff --git a/src/cmd/ksh93/include/argnod.h b/src/cmd/ksh93/include/argnod.h index 508f85630..54b7370a2 100644 --- a/src/cmd/ksh93/include/argnod.h +++ b/src/cmd/ksh93/include/argnod.h @@ -120,6 +120,7 @@ struct argnod #define ARG_QUOTED 0x20 /* word contained quote characters */ #define ARG_MESSAGE 0x40 /* contains international string */ #define ARG_APPEND 0x80 /* for += assignment */ +#define ARG_ARRAY 0x2 /* for typeset -a */ /* The following can be passed as options to sh_macexpand() */ #define ARG_ARITH 0x100 /* arithmetic expansion */ #define ARG_OPTIMIZE 0x200 /* try to optimize */ diff --git a/src/cmd/ksh93/include/shlex.h b/src/cmd/ksh93/include/shlex.h index 5ae9f5103..5bd8ca89b 100644 --- a/src/cmd/ksh93/include/shlex.h +++ b/src/cmd/ksh93/include/shlex.h @@ -90,6 +90,7 @@ typedef struct _shlex_ char noreserv; /* reserved works not legal */ int inlineno; /* saved value of sh.inlineno */ int firstline; /* saved value of sh.st.firstline */ + int assignlevel; /* nesting level for assignment */ #if SHOPT_KIA Sfio_t *kiafile; /* kia output file */ Sfio_t *kiatmp; /* kia reference file */ diff --git a/src/cmd/ksh93/sh/name.c b/src/cmd/ksh93/sh/name.c index 1c4591684..8f508a01d 100644 --- a/src/cmd/ksh93/sh/name.c +++ b/src/cmd/ksh93/sh/name.c @@ -308,13 +308,15 @@ void nv_setlist(register struct argnod *arg,register int flags, Namval_t *typ) else { stakseek(0); - if(*arg->argval==0 && arg->argchn.ap && !(arg->argflag&~(ARG_APPEND|ARG_QUOTED|ARG_MESSAGE))) + if(*arg->argval==0 && arg->argchn.ap && !(arg->argflag&~(ARG_APPEND|ARG_QUOTED|ARG_MESSAGE|ARG_ARRAY))) { int flag = (NV_VARNAME|NV_ARRAY|NV_ASSIGN); int sub=0; struct fornod *fp=(struct fornod*)arg->argchn.ap; register Shnode_t *tp=fp->fortre; flag |= (flags&(NV_NOSCOPE|NV_STATIC|NV_FARRAY)); + if(arg->argflag&ARG_ARRAY) + array |= NV_IARRAY; if(arg->argflag&ARG_QUOTED) cp = sh_mactrim(fp->fornam,-1); else diff --git a/src/cmd/ksh93/sh/nvtree.c b/src/cmd/ksh93/sh/nvtree.c index 03960afed..befd8fd74 100644 --- a/src/cmd/ksh93/sh/nvtree.c +++ b/src/cmd/ksh93/sh/nvtree.c @@ -587,6 +587,9 @@ void nv_outnode(Namval_t *np, Sfio_t* out, int indent, int special) tabs=0; if(associative||special) { + Namarr_t *aq; + if(mp && (aq=nv_arrayptr(mp)) && !aq->fun && array_elem(aq) < nv_aimax(mp)+1) + sfwrite(out,"typeset -a ",11); if(!(fmtq = nv_getsub(np))) break; sfprintf(out,"[%s]",sh_fmtq(fmtq)); diff --git a/src/cmd/ksh93/sh/parse.c b/src/cmd/ksh93/sh/parse.c index 965291753..86fdb3e23 100644 --- a/src/cmd/ksh93/sh/parse.c +++ b/src/cmd/ksh93/sh/parse.c @@ -377,6 +377,7 @@ void *sh_parse(Sfio_t *iop, int flag) return((void*)sh_trestore(iop)); fcsave(&sav_input); sh.st.staklist = 0; + lexp->assignlevel = 0; lexp->noreserv = 0; lexp->heredoc = 0; lexp->inlineno = sh.inlineno; @@ -967,6 +968,30 @@ static Shnode_t *funct(Lex_t *lexp) return(t); } +static int check_array(Lex_t *lexp) +{ + int n,c; + if(lexp->token==0 && strcmp(lexp->arg->argval, SYSTYPESET->nvname)==0) + { + while((c=fcgetc(n))==' ' || c=='\t'); + if(c=='-') + { + if(fcgetc(n)=='a') + { + lexp->assignok = SH_ASSIGN; + lexp->noreserv = 1; + sh_lex(lexp); + return(1); + } + else + fcseek(-2); + } + else + fcseek(-1); + } + return(0); +} + /* * Compound assignment */ @@ -978,6 +1003,7 @@ static struct argnod *assign(Lex_t *lexp, register struct argnod *ap, int type) Stk_t *stkp = sh.stk; int array=0, index=0; Namval_t *np; + lexp->assignlevel++; n = strlen(ap->argval)-1; if(ap->argval[n]!='=') sh_syntax(lexp); @@ -1002,14 +1028,14 @@ static struct argnod *assign(Lex_t *lexp, register struct argnod *ap, int type) ap->argflag &= ARG_QUOTED; ap->argflag |= array; lexp->assignok = SH_ASSIGN; - if(type==NV_ARRAY) + if(type&NV_ARRAY) { lexp->noreserv = 1; lexp->assignok = 0; } else lexp->aliasok = 2; - array= (type==NV_ARRAY)?SH_ARRAY:0; + array= (type&NV_ARRAY)?SH_ARRAY:0; if((n=skipnl(lexp,0))==RPAREN || n==LPAREN) { struct argnod *ar,*aq,**settail; @@ -1148,6 +1174,7 @@ static struct argnod *assign(Lex_t *lexp, register struct argnod *ap, int type) } *tp = (Shnode_t*)ac; lexp->assignok = 0; + lexp->assignlevel--; return(ap); } @@ -1432,7 +1459,9 @@ static Shnode_t *simple(Lex_t *lexp,int flag, struct ionod *io) Stk_t *stkp = sh.stk; struct argnod **argtail; struct argnod **settail; - int cmdarg=0; + int cmdarg = 0; + int type = 0; + int was_assign = 0; int argno = 0; int assignment = 0; int key_on = (!(flag&SH_NOIO) && sh_isoption(SH_KEYWORD)); @@ -1452,8 +1481,11 @@ static Shnode_t *simple(Lex_t *lexp,int flag, struct ionod *io) t->comnamq = 0; t->comstate = 0; settail = &(t->comset); + if(lexp->assignlevel && (flag&SH_ARRAY) && check_array(lexp)) + type |= NV_ARRAY; while(lexp->token==0) { + was_assign = 0; argp = lexp->arg; if(*argp->argval==LBRACE && (flag&SH_FUNDEF) && argp->argval[1]==0) { @@ -1531,6 +1563,8 @@ static Shnode_t *simple(Lex_t *lexp,int flag, struct ionod *io) } retry: tok = sh_lex(lexp); + if(was_assign && check_array(lexp)) + type = NV_ARRAY; if(tok==LABLSYM && (flag&SH_ASSIGN)) lexp->token = tok = 0; if((tok==IPROCSYM || tok==OPROCSYM)) @@ -1546,7 +1580,6 @@ static Shnode_t *simple(Lex_t *lexp,int flag, struct ionod *io) if(argp->argflag&ARG_ASSIGN) { int intypeset = lexp->intypeset; - int type = 0; lexp->intypeset = 0; if(t->comnamp == SYSCOMPOUND) type = NV_COMVAR; @@ -1558,18 +1591,21 @@ static Shnode_t *simple(Lex_t *lexp,int flag, struct ionod *io) if(*ap->argval!='-') break; if(strchr(ap->argval,'T')) - type = NV_TYPE; + type |= NV_TYPE; else if(strchr(ap->argval,'a')) - type = NV_ARRAY; + type |= NV_ARRAY; else if(strchr(ap->argval,'C')) - type = NV_COMVAR; + type |= NV_COMVAR; else continue; - break; } } argp = assign(lexp,argp,type); + if(type==NV_ARRAY) + argp->argflag |= ARG_ARRAY; lexp->intypeset = intypeset; + if(lexp->assignlevel) + was_assign = 1; if(associative) lexp->assignok |= SH_ASSIGN; goto retry; diff --git a/src/cmd/ksh93/sh/tdump.c b/src/cmd/ksh93/sh/tdump.c index 3701fc371..830e3f33a 100644 --- a/src/cmd/ksh93/sh/tdump.c +++ b/src/cmd/ksh93/sh/tdump.c @@ -160,7 +160,7 @@ static int p_arg(register const struct argnod *arg) struct fornod *fp; while(arg) { - if((n = strlen(arg->argval)) || (arg->argflag&~(ARG_APPEND|ARG_MESSAGE|ARG_QUOTED))) + if((n = strlen(arg->argval)) || (arg->argflag&~(ARG_APPEND|ARG_MESSAGE|ARG_QUOTED|ARG_ARRAY))) fp=0; else { diff --git a/src/cmd/ksh93/sh/trestore.c b/src/cmd/ksh93/sh/trestore.c index a8effa0ac..1907b773a 100644 --- a/src/cmd/ksh93/sh/trestore.c +++ b/src/cmd/ksh93/sh/trestore.c @@ -195,7 +195,7 @@ static struct argnod *r_arg(void) ap = (struct argnod*)stkfreeze(stkp,0); if(*ap->argval==0 && (ap->argflag&ARG_EXP)) ap->argchn.ap = (struct argnod*)r_tree(); - else if(*ap->argval==0 && (ap->argflag&~(ARG_APPEND|ARG_MESSAGE|ARG_QUOTED))==0) + else if(*ap->argval==0 && (ap->argflag&~(ARG_APPEND|ARG_MESSAGE|ARG_QUOTED|ARG_ARRAY))==0) { struct fornod *fp = (struct fornod*)getnode(fornod); fp->fortyp = sfgetu(infile); diff --git a/src/cmd/ksh93/tests/arrays.sh b/src/cmd/ksh93/tests/arrays.sh index b29b6d360..a5df18cc3 100755 --- a/src/cmd/ksh93/tests/arrays.sh +++ b/src/cmd/ksh93/tests/arrays.sh @@ -803,5 +803,21 @@ got=$(set +x; redirect 2>&1; foo[42]=''; : ${foo[42]:?}) [[ $got == *"$exp" ]] || err_exit '${array[index]:?error} does not throw error' \ "(expected match of *$(printf %q "$exp"), got $(printf %q "$got"))" +# ====== +# A multidimensional indexed array should be printed correctly by +# 'typeset -p'. Additionally, the printed command must produce the +# same result when reinput to the shell. +unset foo +typeset -a foo[1][2]=bar +exp='typeset -a foo=(typeset -a [1]=([2]=bar) )' +got=$(typeset -p foo) +[[ $exp == "$got" ]] || err_exit "Multidimensional indexed arrays are not printed correctly with 'typeset -p'" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" +unset foo +typeset -a foo=(typeset -a [1]=([2]=bar) ) +got=$(typeset -p foo) +[[ $exp == "$got" ]] || err_exit "Output from 'typeset -p' for indexed array cannot be used for reinput" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + # ====== exit $((Errors<125?Errors:125)) diff --git a/src/cmd/ksh93/tests/arrays2.sh b/src/cmd/ksh93/tests/arrays2.sh index 9094da06f..da6290909 100755 --- a/src/cmd/ksh93/tests/arrays2.sh +++ b/src/cmd/ksh93/tests/arrays2.sh @@ -159,7 +159,7 @@ done $SHELL 2> /dev/null -c 'compound c;float -a c.ar;(( c.ar[2][3][3] = 5))' || 'multidimensional arrays in arithmetic expressions not working' -expected='typeset -a -l -E c.ar=([2]=([3]=([3]=5) ) )' +expected='typeset -a -l -E c.ar=(typeset -a [2]=(typeset -a [3]=([3]=5) ) )' unset c float c.ar c.ar[2][3][3]=5 diff --git a/src/cmd/ksh93/tests/comvar.sh b/src/cmd/ksh93/tests/comvar.sh index 6b2e74ca1..f25a351bb 100755 --- a/src/cmd/ksh93/tests/comvar.sh +++ b/src/cmd/ksh93/tests/comvar.sh @@ -668,7 +668,7 @@ compound -a c.board for ((i=2; i < 4; i++)) do c.board[1][$i]=(foo=bar) done -exp=$'(\n\ttypeset -C -a board=(\n\t\t[1]=(\n\t\t\t[2]=(\n\t\t\t\tfoo=bar\n\t\t\t)\n\t\t\t[3]=(\n\t\t\t\tfoo=bar\n\t\t\t)\n\t\t)\n\t)\n)' +exp=$'(\n\ttypeset -C -a board=(\n\t\ttypeset -a [1]=(\n\t\t\t[2]=(\n\t\t\t\tfoo=bar\n\t\t\t)\n\t\t\t[3]=(\n\t\t\t\tfoo=bar\n\t\t\t)\n\t\t)\n\t)\n)' [[ "$(print -v c)" == "$exp" ]] || err_exit 'compound variable assignment to two dimensional array not working' unset zz diff --git a/src/cmd/ksh93/tests/nameref.sh b/src/cmd/ksh93/tests/nameref.sh index 25c9a5427..72e0827eb 100755 --- a/src/cmd/ksh93/tests/nameref.sh +++ b/src/cmd/ksh93/tests/nameref.sh @@ -593,7 +593,7 @@ function read_c read -C v } print "( typeset -i x=36 ) " | read_c ar[5][9][2] -exp=$'(\n\t[5]=(\n\t\t[9]=(\n\t\t\t[2]=(\n\t\t\t\ttypeset -i x=36\n\t\t\t)\n\t\t)\n\t)\n)' +exp=$'(\n\ttypeset -a [5]=(\n\t\ttypeset -a [9]=(\n\t\t\t[2]=(\n\t\t\t\ttypeset -i x=36\n\t\t\t)\n\t\t)\n\t)\n)' [[ $(print -v ar) == "$exp" ]] || err_exit 'read into nameref of global array instance from within a function fails' function read_c @@ -606,7 +606,7 @@ function main compound -a ar nameref nar=ar print "( typeset -i x=36 ) " | read_c nar[5][9][2] - exp=$'(\n\t[5]=(\n\t\t[9]=(\n\t\t\t[2]=(\n\t\t\t\ttypeset -i x=36\n\t\t\t)\n\t\t)\n\t)\n)' + exp=$'(\n\ttypeset -a [5]=(\n\t\ttypeset -a [9]=(\n\t\t\t[2]=(\n\t\t\t\ttypeset -i x=36\n\t\t\t)\n\t\t)\n\t)\n)' [[ $(print -v nar) == "$exp" ]] || err_exit 'read from a nameref variable from calling scope fails' } main diff --git a/src/cmd/ksh93/tests/treemove.sh b/src/cmd/ksh93/tests/treemove.sh index 37936db50..d14573bb1 100755 --- a/src/cmd/ksh93/tests/treemove.sh +++ b/src/cmd/ksh93/tests/treemove.sh @@ -108,7 +108,7 @@ function m x_t c.x[4][5][8].field x_t x typeset -m c.x[4][6][9].field=x - exp=$'(\n\ttypeset -C -a x=(\n\t\t[4]=(\n\t\t\t[5]=(\n\t\t\t\t[8]=(\n\t\t\t\t\tx_t field=(\n\t\t\t\t\t\thello=world\n\t\t\t\t\t)\n\t\t\t\t)\n\t\t\t)\n\t\t\t[6]=(\n\t\t\t\t[9]=(\n\t\t\t\t\tx_t field=(\n\t\t\t\t\t\thello=world\n\t\t\t\t\t)\n\t\t\t\t)\n\t\t\t)\n\t\t)\n\t)\n)' + exp=$'(\n\ttypeset -C -a x=(\n\t\ttypeset -a [4]=(\n\t\t\ttypeset -a [5]=(\n\t\t\t\t[8]=(\n\t\t\t\t\tx_t field=(\n\t\t\t\t\t\thello=world\n\t\t\t\t\t)\n\t\t\t\t)\n\t\t\t)\n\t\t\ttypeset -a [6]=(\n\t\t\t\t[9]=(\n\t\t\t\t\tx_t field=(\n\t\t\t\t\t\thello=world\n\t\t\t\t\t)\n\t\t\t\t)\n\t\t\t)\n\t\t)\n\t)\n)' [[ $(print -v c) == "$exp" ]] || err_exit "typeset -m c.x[4][6][9].field=x where x is a type is not working" } m