1
0
Fork 0
mirror of git://git.code.sf.net/p/cdesktopenv/code synced 2025-02-13 03:32:24 +00:00

Robustify ${.sh.level} scope switching (re: 69d37d5e, e1c41bb2)

Switching the function scope to a parent scope by assigning to
.sh.level (SH_LEVELNOD) leaves the shell in an inconsistent state,
causing invalid-free and/or use-after-free bugs. The intention of
.sh.level was always to temporarily switch scopes inside a DEBUG
trap, so this commit minimises the pitfalls and instability by
imposing some sensible limitations:
1. .sh.level is now a read-only variable except while executing a
   DEBUG trap;
2. while it's writeable, attempts to unset .sh.level or to change
   its attributes are ignored;
3. attempts to set a discipline function for .sh.level are ignored;
4. it is an error to set a level < 0 or > the current scope.

Even more crashing bugs are fixed by simplifiying the handling and
initialisation of .sh.level and by exempting it completely from
virtual subshell scoping (to which it's irrelevant).

TODO: one thing remains: scope corruption and use-after-free happen
when using the '.' command inside a DEBUG trap with ${.sh.level}
changed. Behaviour same as before this commit. To be investigated.

All changed files:
- Consistently use the int16_t type for level values as that is the
  type of its non-pointer storage in SH_LEVELNOD.
- Update .sh.level by using an update_sh_level() macro that assigns
  directly to the node value, then restores the scope if needed.
- To eliminate implicit typecasts, use the same int16_t type (the
  type used by short ints such as SH_LEVELNOD) for all variables
  containing a function and/or dot script level.

src/cmd/ksh93/include/variables.h:
- Add update_sh_level() macro.

src/cmd/ksh93/include/name.h,
src/cmd/ksh93/sh/macro.c:
- Add a nv_nonptr() macro that checks attributes for a non-pointer
  value -- currently only signed or unsigned short integer value,
  accessed via the 's' member of 'union Value' (e.g. np->nvalue.s).
- nv_isnull(): To avoid undefined behaviour, check for attributes
  indicating a non-pointer value before accessing the nvalue.cp
  pointer (re: 5aba0c72).
- varsub(): In the set/unset check, remove the now-redundant
  exception for SH_LEVELNOD.

src/cmd/ksh93/data/variables.c,
src/cmd/ksh93/sh/init.c:
- shtab_variables[]: Make .sh.level a read-only short integer.
- sh_inittree(): To avoid undefined behaviour, do not assign to the
  'union Value' char pointer if the attribute indicates a non-
  pointer short integer value. Instead, the table value is ignored.

src/cmd/ksh93/sh/subshell.c: sh_assignok():
- Never create a subshell scope for SH_LEVELNOD.

src/cmd/ksh93/sh/xec.c:
- Get rid of 'struct Level' and its maxlevel member. This was only
  used in put_level() to check for an out of range assignment, but
  this can be trivially done by checking sh.fn_depth+sh.dot_depth.
- This in turn allows further simplification that reduces init for
  .sh.level to a single nv_disc() call in sh_debug(), so get rid of
  init_level().
- put_level(): Throw a "level out of range" error if assigned a
  wrong level.
- sh_debug():
  - Turn off the NV_RDONLY (read-only) attribute for SH_LEVELNOD
    while executing the DEBUG trap.
  - Restore the current scope when trap execution is finished.
- sh_funct(): Remove all .sh.level handling. POSIX functions (and
  dot scripts) already handle it in b_dot_cmd(), so sh_funct(),
  which is used by both, is the wrong place to do it.
- sh_funscope(): Update .sh.level for ksh syntax functions here
  instead. Also, do not bother to initialise its discipline here,
  as it can now only be changed in a DEBUG trap.

src/cmd/ksh93/bltins/typeset.c: setall():
- When it's not read-only, ignore all attribute changes for
  .sh.level, as changing the attributes would crash the shell.

src/cmd/ksh93/sh/nvdisc.c: nv_setdisc():
- Ignore all attempts to set a discipline function for .sh.level,
  as doing this would crash the shell.

src/cmd/ksh93/bltins/misc.c: b_dot_cmd():
- Bug fix: also update .sh.level when quitting a dot script.

src/cmd/ksh93/sh/name.c:
- _nv_unset():
  - To avoid an inconsistent state, ignore all attempts to unset
    .sh.level.
  - To avoid undefined behaviour, do not zero np->nvalue.cp if
    attributes for np indicate a non-pointer value (the actual bit
    value of a null pointer is not defined by the standard, so
    there is no guarantee that zeroing .cp will zero .s).
- sh_setscope(): For consistency, always set error_info.id (the
  command name for error messages) to the new scope's cmdname.
  Previously this was only done for two calls of this function.
- nv_name(): Fix a crashing bug by checking that np->nvname is a
  non-null pointer before dereferencing it.

src/cmd/ksh93/include/nval.h:
- The NV_UINT16P macro (which is unsigned NV_INT16P) had a typo in
  it, which went unnoticed for many years because it's not directly
  used (though its bit flags are set and used indirectly). Let's
  fix it anyway and keep it for completeness' sake.
This commit is contained in:
Martijn Dekker 2022-07-12 17:48:44 +02:00
parent 3ce064bbba
commit ffee9100d5
19 changed files with 134 additions and 110 deletions

6
NEWS
View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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,

View file

@ -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 */

View file

@ -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)

View file

@ -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;

View file

@ -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)

View file

@ -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. */

View file

@ -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

View file

@ -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))

View file

@ -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;

View file

@ -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))

View file

@ -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)

View file

@ -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)))
{

View file

@ -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);

View file

@ -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'
{

View file

@ -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))