mirror of
git://git.code.sf.net/p/cdesktopenv/code
synced 2025-02-13 03:32:24 +00:00
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:
parent
3ce064bbba
commit
ffee9100d5
19 changed files with 134 additions and 110 deletions
6
NEWS
6
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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 */
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)))
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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'
|
||||
|
||||
{
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in a new issue