diff --git a/NEWS b/NEWS index cef2c2a17..c6a1e5e4d 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,12 @@ For full details, see the git log at: https://github.com/ksh93/ksh/tree/1.0 Any uppercase BUG_* names are modernish shell bug IDs. +2022-07-12: + +- The .sh.level variable can now only be changed within a DEBUG trap. When + trap execution ends, the variable and the scope are now restored. These + changes disallow an inconsistent shell scoping state causing instability. + 2022-07-10: - Fixed a potential crash on retrieving an empty line from the command history. diff --git a/src/cmd/ksh93/COMPATIBILITY b/src/cmd/ksh93/COMPATIBILITY index 94aec7dc9..23bb442c6 100644 --- a/src/cmd/ksh93/COMPATIBILITY +++ b/src/cmd/ksh93/COMPATIBILITY @@ -172,6 +172,9 @@ For more details, see the NEWS file and for complete details, see the git log. override and replace special built-in commands, except for type definition commands previously created by these commands. +32. The .sh.level variable is now read-only except inside a DEBUG trap. + The current level/scope is now restored when the DEBUG trap run ends. + ____________________________________________________________________________ KSH-93 VS. KSH-88 diff --git a/src/cmd/ksh93/bltins/misc.c b/src/cmd/ksh93/bltins/misc.c index a22ef3963..1851fabba 100644 --- a/src/cmd/ksh93/bltins/misc.c +++ b/src/cmd/ksh93/bltins/misc.c @@ -230,7 +230,6 @@ int b_dot_cmd(register int n,char *argv[],Shbltin_t *context) volatile struct dolnod *argsave=0; struct checkpt buff; Sfio_t *iop=0; - short level; NOT_USED(context); while (n = optget(argv,sh_optdot)) switch (n) { @@ -248,7 +247,7 @@ int b_dot_cmd(register int n,char *argv[],Shbltin_t *context) errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0)); UNREACHABLE(); } - if(sh.dot_depth+1 > DOTMAX) + if(sh.dot_depth >= DOTMAX) { errormsg(SH_DICT,ERROR_exit(1),e_toodeep,script); UNREACHABLE(); @@ -294,8 +293,6 @@ int b_dot_cmd(register int n,char *argv[],Shbltin_t *context) sh.st.filename = filename; sh.st.lineno = 1; } - level = sh.fn_depth+sh.dot_depth+1; - nv_putval(SH_LEVELNOD,(char*)&level,NV_INT16); sh.st.prevst = prevscope; sh.st.self = &savst; sh.topscope = (Shscope_t*)sh.st.self; @@ -312,6 +309,7 @@ int b_dot_cmd(register int n,char *argv[],Shbltin_t *context) if(jmpval == 0) { sh.dot_depth++; + update_sh_level(); if(np) sh_exec((Shnode_t*)(nv_funtree(np)),sh_isstate(SH_ERREXIT)); else @@ -328,6 +326,7 @@ int b_dot_cmd(register int n,char *argv[],Shbltin_t *context) if(!np) free(tofree); sh.dot_depth--; + update_sh_level(); if((np || argv[1]) && jmpval!=SH_JMPSCRIPT) sh_argreset((struct dolnod*)argsave,saveargfor); else diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index 860b3350d..f144c3069 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -936,6 +936,8 @@ static int setall(char **argv,register int flag,Dt_t *troot,struct tdata *tp newflag = curflag & ~flag; if (tp->aflag && (tp->argnum || (curflag!=newflag))) { + if(np==SH_LEVELNOD) + return(r); if(sh.subshell) sh_assignok(np,2); if(troot!=sh.var_tree) diff --git a/src/cmd/ksh93/data/variables.c b/src/cmd/ksh93/data/variables.c index 85f13a7ce..a4b061b65 100644 --- a/src/cmd/ksh93/data/variables.c +++ b/src/cmd/ksh93/data/variables.c @@ -98,7 +98,7 @@ const struct shtable2 shtab_variables[] = ".sh.file", 0, (char*)0, ".sh.fun", 0, (char*)0, ".sh.subshell", NV_INTEGER|NV_NOFREE, (char*)0, - ".sh.level", 0, (char*)0, + ".sh.level", NV_INT16|NV_NOFREE|NV_RDONLY, (char*)0, ".sh.lineno", NV_INTEGER|NV_NOFREE, (char*)0, ".sh.stats", 0, (char*)0, ".sh.math", 0, (char*)0, diff --git a/src/cmd/ksh93/include/name.h b/src/cmd/ksh93/include/name.h index fd9c34f1d..5710c00ee 100644 --- a/src/cmd/ksh93/include/name.h +++ b/src/cmd/ksh93/include/name.h @@ -169,7 +169,9 @@ struct Ufunction #undef nv_size #define nv_size(np) ((np)->nvsize) #define _nv_hasget(np) ((np)->nvfun && (np)->nvfun->disc && nv_hasget(np)) -#define nv_isnull(np) (!(np)->nvalue.cp && !_nv_hasget(np)) +/* for nv_isnull we must exclude non-pointer value attributes (NV_INT16, NV_UINT16) before accessing cp in union Value */ +#define nv_isnonptr(np) (nv_isattr(np,NV_INT16P)==NV_INT16) +#define nv_isnull(np) (!nv_isnonptr(np) && !(np)->nvalue.cp && !_nv_hasget(np)) /* ... for arrays */ diff --git a/src/cmd/ksh93/include/nval.h b/src/cmd/ksh93/include/nval.h index 459a35455..2a0b73cde 100644 --- a/src/cmd/ksh93/include/nval.h +++ b/src/cmd/ksh93/include/nval.h @@ -192,10 +192,11 @@ struct Namval #define NV_PUBLIC (~(NV_NOSCOPE|NV_ASSIGN|NV_IDENT|NV_VARNAME|NV_NOADD)) /* numeric types */ +/* NV_INT16 and NV_UINT16 store values directly in the node; all the others use pointers */ #define NV_INT16P (NV_LJUST|NV_SHORT|NV_INTEGER) #define NV_INT16 (NV_SHORT|NV_INTEGER) +#define NV_UINT16P (NV_LJUST|NV_UNSIGN|NV_SHORT|NV_INTEGER) #define NV_UINT16 (NV_UNSIGN|NV_SHORT|NV_INTEGER) -#define NV_UINT16P (NV_LJUSTNV_UNSIGN|NV_SHORT|NV_INTEGER) #define NV_INT32 (NV_INTEGER) #define NV_UNT32 (NV_UNSIGN|NV_INTEGER) #define NV_INT64 (NV_LONG|NV_INTEGER) diff --git a/src/cmd/ksh93/include/shell.h b/src/cmd/ksh93/include/shell.h index 33aca423d..4ec58f353 100644 --- a/src/cmd/ksh93/include/shell.h +++ b/src/cmd/ksh93/include/shell.h @@ -169,6 +169,7 @@ extern const char e_restricted[]; extern const char e_recursive[]; extern char e_version[]; +/* Documented public interface to shell scope (see shell.3). */ typedef struct sh_scope { struct sh_scope *par_scope; @@ -182,6 +183,7 @@ typedef struct sh_scope struct sh_scope *self; } Shscope_t; +/* Private interface to shell scope. The first members must match the public interface. */ struct sh_scoped { struct sh_scoped *prevst; /* pointer to previous state */ @@ -337,8 +339,8 @@ struct Shell_s int inuse_bits; struct argnod *envlist; struct dolnod *arglist; - int fn_depth; /* scoped ksh-style function call depth */ - int dot_depth; /* dot-script and POSIX function call depth */ + int16_t fn_depth; /* scoped ksh-style function call depth */ + int16_t dot_depth; /* dot-script and POSIX function call depth */ int hist_depth; int xargmin; int xargmax; diff --git a/src/cmd/ksh93/include/variables.h b/src/cmd/ksh93/include/variables.h index bfd64044f..6de9725b3 100644 --- a/src/cmd/ksh93/include/variables.h +++ b/src/cmd/ksh93/include/variables.h @@ -36,6 +36,14 @@ struct rand extern void sh_reseed_rand(struct rand *); extern void sh_save_rand_seed(struct rand *, int); +/* update ${.sh.level} and, if needed, restore the current scope */ +#define update_sh_level() \ +( \ + SH_LEVELNOD->nvalue.s = sh.fn_depth + sh.dot_depth, \ + sh.topscope != (Shscope_t*)sh.st.self ? sh_setscope(sh.topscope) : 0, \ + 1 \ +) + /* The following defines must be kept synchronous with shtab_variables[] in data/variables.c */ #define PATHNOD (sh.bltin_nodes) diff --git a/src/cmd/ksh93/include/version.h b/src/cmd/ksh93/include/version.h index d4baa1590..c09d3f754 100644 --- a/src/cmd/ksh93/include/version.h +++ b/src/cmd/ksh93/include/version.h @@ -23,7 +23,7 @@ #define SH_RELEASE_FORK "93u+m" /* only change if you develop a new ksh93 fork */ #define SH_RELEASE_SVER "1.0.0-beta.2" /* semantic version number: https://semver.org */ -#define SH_RELEASE_DATE "2022-07-10" /* must be in this format for $((.sh.version)) */ +#define SH_RELEASE_DATE "2022-07-12" /* must be in this format for $((.sh.version)) */ #define SH_RELEASE_CPYR "(c) 2020-2022 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.1 b/src/cmd/ksh93/sh.1 index b141fa1bc..d35c515b6 100644 --- a/src/cmd/ksh93/sh.1 +++ b/src/cmd/ksh93/sh.1 @@ -1721,13 +1721,20 @@ The pathname of the file that contains the current command. The name of the current function that is being executed. .TP .B .sh.level -Set to the current function depth. This can be changed -inside a DEBUG trap and will set the context to the specified -level. +Set to the current call depth of functions and dot scripts. +Normally, this variable is read-only, but while executing a +.B DEBUG +trap, its value may be changed to switch the current function scope +to that of the specified level for the duration of the trap run, +making it possible to access a parent scope for debugging purposes. +When trap execution ends, the variable and the scope are restored. +It is an error to assign a value lower than 0 (the global scope) +or higher than the current call depth. .TP .B .sh.lineno -Set during a DEBUG trap to the line number for the caller of -each function. +Set during a +.B DEBUG +trap to the line number for the caller of each function. .TP .B .sh.match An indexed array which stores the most recent match and subpattern diff --git a/src/cmd/ksh93/sh/init.c b/src/cmd/ksh93/sh/init.c index 217b9d223..fcbec5b57 100644 --- a/src/cmd/ksh93/sh/init.c +++ b/src/cmd/ksh93/sh/init.c @@ -1997,7 +1997,8 @@ Dt_t *sh_inittree(const struct shtable2 *name_vals) { if(name_vals == shtab_variables) np->nvfun = &sh.nvfun; - np->nvalue.cp = (char*)tp->sh_value; + if(!nv_isnonptr(np)) + np->nvalue.cp = (char*)tp->sh_value; } nv_setattr(np,tp->sh_number); if(nv_isattr(np,NV_TABLE)) diff --git a/src/cmd/ksh93/sh/macro.c b/src/cmd/ksh93/sh/macro.c index 69282f1ed..c1dc903d3 100644 --- a/src/cmd/ksh93/sh/macro.c +++ b/src/cmd/ksh93/sh/macro.c @@ -1431,7 +1431,7 @@ retry1: if(np && type==M_BRACE && sh.argaddr) nv_optimize(np); /* needed before calling nv_isnull() */ #endif /* SHOPT_OPTIMIZE */ - if(np && (type==M_BRACE ? (!nv_isnull(np) || np==SH_LEVELNOD) : (type==M_TREE || !c || !ap))) + if(np && (type==M_BRACE ? !nv_isnull(np) : (type==M_TREE || !c || !ap))) { /* Either the parameter is set, or it's a special type of expansion where 'unset' doesn't apply. */ char *savptr; diff --git a/src/cmd/ksh93/sh/name.c b/src/cmd/ksh93/sh/name.c index 3921385f6..fe711bfe3 100644 --- a/src/cmd/ksh93/sh/name.c +++ b/src/cmd/ksh93/sh/name.c @@ -2430,6 +2430,8 @@ void _nv_unset(register Namval_t *np,int flags) #if SHOPT_FIXEDARRAY Namarr_t *ap; #endif /* SHOPT_FIXEDARRAY */ + if(np==SH_LEVELNOD) + return; if(!(flags&NV_RDONLY) && nv_isattr (np,NV_RDONLY)) { errormsg(SH_DICT,ERROR_exit(1),e_readonly, nv_name(np)); @@ -2505,9 +2507,12 @@ void _nv_unset(register Namval_t *np,int flags) /* called from disc, assign the actual value */ nv_local=0; } - if(nv_isattr(np,NV_INT16P) == NV_INT16) + if(nv_isnonptr(np)) { - np->nvalue.cp = nv_isarray(np)?Empty:0; + if(nv_isarray(np)) + np->nvalue.cp = Empty; + else + np->nvalue.s = 0; goto done; } #if SHOPT_FIXEDARRAY @@ -3492,6 +3497,7 @@ Shscope_t *sh_setscope(Shscope_t *scope) sh.var_tree = scope->var_tree; SH_PATHNAMENOD->nvalue.cp = sh.st.filename; SH_FUNNAMENOD->nvalue.cp = sh.st.funname; + error_info.id = scope->cmdname; return(old); } @@ -3598,7 +3604,7 @@ char *nv_name(register Namval_t *np) return((*fp->disc->namef)(np,fp)); } } - if(!(table=sh.last_table) || *np->nvname=='.' || table==sh.namespace || np==table) + if(!(table=sh.last_table) || np->nvname && *np->nvname=='.' || table==sh.namespace || np==table) { #if SHOPT_FIXEDARRAY if(!ap || !ap->fixed || (ap->nelem&ARRAY_UNDEF)) diff --git a/src/cmd/ksh93/sh/nvdisc.c b/src/cmd/ksh93/sh/nvdisc.c index d53a434fb..fbf99fcd8 100644 --- a/src/cmd/ksh93/sh/nvdisc.c +++ b/src/cmd/ksh93/sh/nvdisc.c @@ -558,7 +558,7 @@ char *nv_setdisc(register Namval_t* np,register const char *event,Namval_t *acti action = vp->disc[type]; empty = 0; } - else if(action) + else if(action && np!=SH_LEVELNOD) { Namdisc_t *dp = (Namdisc_t*)vp->fun.disc; if(type==LOOKUPS) diff --git a/src/cmd/ksh93/sh/subshell.c b/src/cmd/ksh93/sh/subshell.c index ab91f76dc..6517ab32b 100644 --- a/src/cmd/ksh93/sh/subshell.c +++ b/src/cmd/ksh93/sh/subshell.c @@ -260,8 +260,6 @@ void sh_save_rand_seed(struct rand *rp, int reseed) * * add == 0: Move the node pointer from the parent shell to the current virtual subshell. * add == 1: Create a copy of the node pointer in the current virtual subshell. - * add == 2: This will create a copy of the node pointer like 1, but it will disable the - * optimization for ${.sh.level}. */ Namval_t *sh_assignok(register Namval_t *np,int add) { @@ -274,9 +272,9 @@ Namval_t *sh_assignok(register Namval_t *np,int add) unsigned int save; /* * Don't create a scope if told not to (see nv_restore()) or if this is a subshare. - * Also, moving/copying ${.sh.level} (SH_LEVELNOD) may crash the shell. + * Also, ${.sh.level} (SH_LEVELNOD) is handled specially and is not scoped in virtual subshells. */ - if(subshell_noscope || sh.subshare || add<2 && np==SH_LEVELNOD) + if(subshell_noscope || sh.subshare || np==SH_LEVELNOD) return(np); if((ap=nv_arrayptr(np)) && (mp=nv_opensub(np))) { diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index 0dd00bb80..4101bd98f 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -541,62 +541,36 @@ static void out_string(Sfio_t *iop, register const char *cp, int c, int quoted) sfputr(iop,cp,c); } -struct Level -{ - Namfun_t hdr; - short maxlevel; -}; - /* - * this is for a debugger but it hasn't been tested yet - * if a debug script sets .sh.level it should set up the scope - * as if you were executing in that level - */ + * If a script changes .sh.level inside a DEBUG trap, it will switch the + * scope as if it were executing the trap at that function call depth. + */ static void put_level(Namval_t* np,const char *val,int flags,Namfun_t *fp) { Shscope_t *sp; - struct Level *lp = (struct Level*)fp; - int16_t level, oldlevel = (int16_t)nv_getnum(np); - nv_putv(np,val,flags,fp); - if(!val) - { - fp = nv_stack(np, NIL(Namfun_t*)); - if(fp && !fp->nofree) - free((void*)fp); + int16_t level, oldlevel = np->nvalue.s; + if(val) + nv_putv(np,val,flags,fp); + else return; - } - level = nv_getnum(np); - if(level<0 || level > lp->maxlevel) + level = np->nvalue.s; + if(level < 0 || level > sh.fn_depth + sh.dot_depth) { - nv_putv(np, (char*)&oldlevel, NV_INT16, fp); - /* perhaps this should be an error */ - return; + np->nvalue.s = oldlevel; + errormsg(SH_DICT,ERROR_exit(1),"%d: level out of range",level); + UNREACHABLE(); } if(level==oldlevel) return; if(sp = sh_getscope(level,SEEK_SET)) - { sh_setscope(sp); - error_info.id = sp->cmdname; - } } -static const Namdisc_t level_disc = { sizeof(struct Level), put_level }; - -static struct Level *init_level(int level) -{ - struct Level *lp = sh_newof(NiL,struct Level,1,0); - lp->maxlevel = level; - _nv_unset(SH_LEVELNOD,0); - nv_onattr(SH_LEVELNOD,NV_INT16|NV_NOFREE); - sh.last_root = nv_dict(DOTSHNOD); - nv_putval(SH_LEVELNOD,(char*)&lp->maxlevel,NV_INT16); - lp->hdr.disc = &level_disc; - nv_disc(SH_LEVELNOD,&lp->hdr,NV_FIRST); - return(lp); -} +static const Namdisc_t level_disc = { sizeof(Namfun_t), put_level }; +static Namfun_t level_disc_fun = { &level_disc, 1 }; /* + * Execute the DEBUG trap: * write the current command on the stack and make it available as .sh.command */ int sh_debug(const char *trap, const char *name, const char *subscript, char *const argv[], int flags) @@ -608,7 +582,6 @@ int sh_debug(const char *trap, const char *name, const char *subscript, char *co char *sav = stkfreeze(stkp,0); const char *cp = "+=( "; Sfio_t *iop = stkstd; - short level; if(sh.indebug) return(0); sh.indebug = 1; @@ -641,23 +614,22 @@ int sh_debug(const char *trap, const char *name, const char *subscript, char *co else if(iop==stkstd) *stkptr(stkp,stktell(stkp)-1) = 0; np->nvalue.cp = stkfreeze(stkp,1); - /* now setup .sh.level variable */ sh.st.lineno = error_info.line; - level = sh.fn_depth+sh.dot_depth; - sh.last_root = nv_dict(DOTSHNOD); - if(!SH_LEVELNOD->nvfun || !SH_LEVELNOD->nvfun->disc || nv_isattr(SH_LEVELNOD,NV_INT16|NV_NOFREE)!=(NV_INT16|NV_NOFREE)) - init_level(level); - else - nv_putval(SH_LEVELNOD,(char*)&level,NV_INT16); savst = sh.st; sh.st.trap[SH_DEBUGTRAP] = 0; + /* set up .sh.level variable */ + if(!SH_LEVELNOD->nvfun || !SH_LEVELNOD->nvfun->disc) + nv_disc(SH_LEVELNOD,&level_disc_fun,NV_FIRST); + nv_offattr(SH_LEVELNOD,NV_RDONLY); + /* run the trap */ n = sh_trap(trap,0); + nv_onattr(SH_LEVELNOD,NV_RDONLY); np->nvalue.cp = 0; sh.indebug = 0; nv_onattr(SH_PATHNAMENOD,NV_NOFREE); nv_onattr(SH_FUNNAMENOD,NV_NOFREE); - if(sh.st.cmdname) - error_info.id = sh.st.cmdname; + /* restore scope */ + update_sh_level(); sh.st = savst; if(sav != stkptr(stkp,0)) stkset(stkp,sav,offset); @@ -3163,12 +3135,14 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) jmpval = sigsetjmp(buffp->buff,0); if(jmpval == 0) { - if(sh.fn_depth++ > MAXDEPTH) + if(sh.fn_depth >= MAXDEPTH) { sh.toomany = 1; siglongjmp(*sh.jmplist,SH_JMPERRFN); } - else if(fun) + sh.fn_depth++; + update_sh_level(); + if(fun) r= (*fun)(arg); else { @@ -3192,9 +3166,9 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) r = sh.exitval; } } - if(sh.topscope != (Shscope_t*)sh.st.self) - sh_setscope(sh.topscope); - if(--sh.fn_depth==1 && jmpval==SH_JMPERRFN) + sh.fn_depth--; + update_sh_level(); + if(sh.fn_depth==1 && jmpval==SH_JMPERRFN) { errormsg(SH_DICT,ERROR_exit(1),e_toodeep,argv[0]); UNREACHABLE(); @@ -3244,20 +3218,15 @@ static void sh_funct(Namval_t *np,int argn, char *argv[],struct argnod *envlist, { struct funenv fun; char *fname = nv_getval(SH_FUNNAMENOD); - struct Level *lp =(struct Level*)(SH_LEVELNOD->nvfun); - int level, pipepid=sh.pipepid; + pid_t pipepid = sh.pipepid; #if !SHOPT_DEVFD Dt_t *save_fifo_tree = sh.fifo_tree; sh.fifo_tree = NIL(Dt_t*); #endif sh.pipepid = 0; sh_stats(STAT_FUNCT); - if(!lp->hdr.disc) - lp = init_level(0); if((struct sh_scoped*)sh.topscope != sh.st.self) sh_setscope(sh.topscope); - level = lp->maxlevel = sh.dot_depth + sh.fn_depth+1; - SH_LEVELNOD->nvalue.s = lp->maxlevel; sh.st.lineno = error_info.line; np->nvalue.rp->running += 2; if(nv_isattr(np,NV_FPOSIX)) @@ -3284,13 +3253,6 @@ static void sh_funct(Namval_t *np,int argn, char *argv[],struct argnod *envlist, fun.nref = 0; sh_funscope(argn,argv,0,&fun,execflg); } - if(level-- != nv_getnum(SH_LEVELNOD)) - { - Shscope_t *sp = sh_getscope(0,SEEK_END); - sh_setscope(sp); - } - lp->maxlevel = level; - SH_LEVELNOD->nvalue.s = lp->maxlevel; sh.last_root = nv_dict(DOTSHNOD); nv_putval(SH_FUNNAMENOD,fname,NV_NOFREE); nv_putval(SH_PATHNAMENOD,sh.st.filename,NV_NOFREE); diff --git a/src/cmd/ksh93/tests/functions.sh b/src/cmd/ksh93/tests/functions.sh index a9e295627..ed20c9439 100755 --- a/src/cmd/ksh93/tests/functions.sh +++ b/src/cmd/ksh93/tests/functions.sh @@ -984,29 +984,25 @@ function _Dbg_debug_trap_handler done } -( -: 'Disabling xtrace while running _Dbg_* functions' -set +x # TODO: the _Dbg_* functions are incompatible with xtrace. To expose the regression - # test failures, run 'bin/shtests -x -p functions'. Is this a bug in ksh? ((baseline=LINENO+2)) trap '_Dbg_debug_trap_handler' DEBUG . $tmp/debug foo bar trap '' DEBUG -exit $Errors -) -Errors=$? caller() { integer .level=.sh.level .max=.sh.level-1 while((--.level>=0)) do - ((.sh.level = .level)) - print -r -- "${.sh.lineno}" + # as of 2022-07-12, .sh.level can only be changed inside a DEBUG trap; + # the trap is executed right before turning it off with 'trap - DEBUG' + trap '((.sh.level = .level)); print -r -- "${.sh.lineno}"' DEBUG + trap - DEBUG done } bar() { caller;} set -- $(bar) -[[ $1 == $2 ]] && err_exit ".sh.inline optimization bug" +[[ $1 == $2 ]] && err_exit ".sh.lineno optimization bug (got values: $*)" + ( $SHELL -c ' function foo { typeset x=$1;print $1;};z=();z=($(foo bar)) ') 2> /dev/null || err_exit 'using a function to set an array in a command sub fails' { diff --git a/src/cmd/ksh93/tests/variables.sh b/src/cmd/ksh93/tests/variables.sh index 2bc4edc44..f208d296e 100755 --- a/src/cmd/ksh93/tests/variables.sh +++ b/src/cmd/ksh93/tests/variables.sh @@ -20,6 +20,7 @@ ######################################################################## . "${SHTESTS_COMMON:-${0%/*}/_common}" +((!.sh.level))||err_exit ".sh.level should be 0 after dot script, is ${.sh.level}" [[ ${.sh.version} == "$KSH_VERSION" ]] || err_exit '.sh.version != KSH_VERSION' unset ss @@ -986,11 +987,11 @@ set -- $( IFS=\" while read -r first varname junk do [[ $first == '};' ]] && exit - [[ -z $junk ]] && continue + [[ -z $junk || $junk == *[![:alpha:]]NV_RDONLY[![:alpha:]]* ]] && continue [[ -n $varname && $varname != '.sh' ]] && print -r -- "$varname" done ) -(($# >= 66)) || err_exit "could not read shtab_variables[]; adjust test script ($# items read)" +(($# >= 65)) || err_exit "could not read shtab_variables[]; adjust test script ($# items read)" # ... unset $SHELL -c ' @@ -1069,10 +1070,7 @@ $SHELL -c ' $SHELL -c ' errors=0 for var - do if [[ $var == .sh.level ]] - then continue # known to fail - fi - if eval "($var=bug); [[ \${$var} == bug ]]" 2>/dev/null + do if eval "($var=bug); [[ \${$var} == bug ]]" 2>/dev/null then echo " $0: special variable $var leaks out of subshell" >&2 let errors++ fi @@ -1378,5 +1376,38 @@ exp='typeset -F 2 foo=10.35' [[ $got == "$exp" ]] || err_exit "Setting attribute after setting getn discipline fails" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" +# ====== +# As of 2022-07-12, the current scope is restored after changing .sh.level in a DEBUG trap +exp=$'a: 2 CHILD\nb: 1 PARENT\nc: 2 CHILD\nd: 1 PARENT' +function leveltest +{ + typeset scope=PARENT + function f + { + typeset scope=CHILD + print "a: ${.sh.level} $scope" + trap 'let ".sh.level=$1"; print "b: ${.sh.level} $scope"' DEBUG + trap - DEBUG + print "c: ${.sh.level} $scope" + } + f "${.sh.level}" + print "d: ${.sh.level} $scope" +} +got=$(ulimit -t unlimited 2>/dev/null; set +x; redirect 2>&1; leveltest) +((!(e = $?))) && [[ $got == "$exp" ]] || err_exit "DEBUG trap does not restore scope after execution" \ + "(expected status 0 and $(printf %q "$exp")," \ + "got status $e$( ((e>128)) && print -n /SIG && kill -l "$e") and $(printf %q "$got"))" +unset -f leveltest + +cat >dotlevel <<\EOF +echo ${.sh.level} +trap '.sh.level=${.sh.level}; echo ${.sh.level}' DEBUG +trap - DEBUG +EOF +got=$(trap "echo ${.sh.level}" DEBUG; trap - DEBUG; . ./dotlevel) +exp=$'0\n1\n1' +[[ $got == "$exp" ]] || err_exit '${.sh.level} in dot script not correct' \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + # ====== exit $((Errors<125?Errors:125))