diff --git a/NEWS b/NEWS index 63cd428ac..1e11d4448 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,13 @@ For full details, see the git log at: https://github.com/ksh93/ksh Any uppercase BUG_* names are modernish shell bug IDs. +2021-12-17: + +- Ksh no longer behaves badly when parsing a type definition command + ('typeset -T' or 'enum') without executing it or when executing it in + a subshell. Types can now safely be defined in subshells and defined + conditionally as in 'if condition; then enum ...; fi'. + 2021-12-16: - Changed the default selection of compiled-in /opt/ast/bin built-in libcmd diff --git a/src/cmd/ksh93/bltins/enum.c b/src/cmd/ksh93/bltins/enum.c index 782ecfd51..7ba598009 100644 --- a/src/cmd/ksh93/bltins/enum.c +++ b/src/cmd/ksh93/bltins/enum.c @@ -21,7 +21,7 @@ #pragma prototyped #include "defs.h" -#define ENUM_ID "enum (ksh 93u+m) 2021-11-23" +#define ENUM_ID "enum (ksh 93u+m) 2021-12-17" const char sh_optenum[] = "[-?@(#)$Id: " ENUM_ID " $\n]" @@ -239,6 +239,10 @@ int b_enum(int argc, char** argv, Shbltin_t *context) error(ERROR_USAGE|2, "%s", optusage(NiL)); return 1; } +#ifndef STANDALONE + if(sh.subshell && !sh.subshare) + sh_subfork(); +#endif while(cp = *argv++) { if(!(np = nv_open(cp, (void*)0, NV_VARNAME|NV_NOADD)) || !(ap=nv_arrayptr(np)) || ap->fun || (sz=ap->nelem&(((1L<lastline) #ifndef NIL @@ -169,13 +173,10 @@ static void typeset_order(const char *str,int line) } /* - * Pre-add type declaration built-ins at parse time to avoid - * syntax errors when using -c, shcomp, '.'/source or eval. + * This function handles linting for 'typeset' options via typeset_order(). * - * This hack has a bad side effect: defining a type with 'typeset -T' or 'enum' - * in a subshell or an 'if false' block will cause an inconsistent state. But - * as these built-ins alter the syntax of the shell, it's necessary for making - * them work if we're parsing an entire script before or without executing it. + * Also, upon parsing typeset -T or enum, it pre-adds the type declaration built-ins that these would create to + * an internal tree to avoid syntax errors upon pre-execution parsing of assignment-arguments with parentheses. * * intypeset==1 for typeset & friends; intypeset==2 for enum */ @@ -236,6 +237,40 @@ static void check_typedef(struct comnod *tp, char intypeset) if(cp) nv_onattr(sh_addbuiltin(cp, (Shbltin_f)SYSTRUE->nvalue.bfp, NIL(void*)), NV_BLTIN|BLT_DCL); } +/* + * (De)activate an internal declaration built-ins tree into which check_typedef() can pre-add dummy type + * declaration command nodes, allowing correct parsing of assignment-arguments with parentheses for custom + * type declaration commands before actually executing the commands that create those commands. + * + * A viewpath from the main built-ins tree to this internal tree is added, unifying the two for search + * purposes and causing new nodes to be added to the internal tree. When parsing is done, we close that + * viewpath. This hides those pre-added nodes at execution time, avoiding an inconsistent state if a type + * creation command is parsed but not executed. + */ +static void dcl_hacktivate(void) +{ + if(dcl_recursion++) + return; + if(!dcl_tree) + dcl_tree = dtopen(&_Nvdisc, Dtoset); + dtview(sh.bltin_tree, dcl_tree); + orig_exit = error_info.exit; + error_info.exit = dcl_exit; +} +static void dcl_dehacktivate(void) +{ + if(!dcl_recursion || --dcl_recursion) + return; + error_info.exit = orig_exit; + dtview(sh.bltin_tree, NIL(Dt_t*)); +} +static noreturn void dcl_exit(int e) +{ + dcl_recursion = 1; + dcl_dehacktivate(); + (*error_info.exit)(e); + UNREACHABLE(); +} /* * Make a parent node for fork() or io-redirection @@ -503,6 +538,7 @@ static Shnode_t *sh_cmd(Lex_t *lexp, register int sym, int flag) { register Shnode_t *left, *right; register int type = FINT|FAMP; + dcl_hacktivate(); if(sym==NL) lexp->lasttok = 0; left = list(lexp,flag); @@ -544,6 +580,7 @@ static Shnode_t *sh_cmd(Lex_t *lexp, register int sym, int flag) sh_syntax(lexp); } } + dcl_dehacktivate(); return(left); } @@ -1045,8 +1082,19 @@ static struct argnod *assign(Lex_t *lexp, register struct argnod *ap, int type) if(array && type==NV_TYPE) { struct argnod *arg = lexp->arg; + unsigned save_recursion = dcl_recursion; + int p; + /* + * Forcibly deactivate the dummy declaration built-ins tree as path_search() + * does an FPATH search, which may execute arbitrary code at parse time. + */ n = lexp->token; - if(path_search(lexp->sh,lexp->arg->argval,NIL(Pathcomp_t**),1) && (np=nv_search(lexp->arg->argval,lexp->sh->fun_tree,0)) && nv_isattr(np,BLT_DCL)) + dcl_recursion = 1; + dcl_dehacktivate(); + p = path_search(lexp->sh,lexp->arg->argval,NIL(Pathcomp_t**),1); + dcl_hacktivate(); + dcl_recursion = save_recursion; + if(p && (np=nv_search(lexp->arg->argval,lexp->sh->fun_tree,0)) && nv_isattr(np,BLT_DCL)) { lexp->token = n; lexp->arg = arg; diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index 675523c0f..ba12398aa 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -1088,6 +1088,8 @@ int sh_exec(register const Shnode_t *t, int flags) #if SHOPT_TYPEDEF else if(argn>=3 && checkopt(com,'T')) { + if(sh.subshell && !sh.subshare) + sh_subfork(); # if SHOPT_NAMESPACE if(shp->namespace) { diff --git a/src/cmd/ksh93/tests/enum.sh b/src/cmd/ksh93/tests/enum.sh index 437752b86..d8402492d 100755 --- a/src/cmd/ksh93/tests/enum.sh +++ b/src/cmd/ksh93/tests/enum.sh @@ -155,6 +155,11 @@ got=$(eval 2>&1 'command command command enum -i -i -iii --igno -ii PARSER_t=(r exp='PARSER_t -r -A hack=([C]=g)' [[ $got == "$exp" ]] || err_exit "incorrect typeset output for enum with command prefix and options" \ "(expected $(printf %q "$exp"); got $(printf %q "$got"))" +PATH=/dev/null command -v PARSER_t >/dev/null && err_exit "PARSER_t leaked out of subshell" +if false +then enum PARSER2_t=(a b) +fi +PATH=/dev/null command -v PARSER2_t >/dev/null && err_exit "PARSER2_t incompletely defined though definition was never executed" # ====== exit $((Errors<125?Errors:125)) diff --git a/src/cmd/ksh93/tests/types.sh b/src/cmd/ksh93/tests/types.sh index 3bf9b2115..0dd4bcc41 100755 --- a/src/cmd/ksh93/tests/types.sh +++ b/src/cmd/ksh93/tests/types.sh @@ -452,20 +452,24 @@ cd "$tmp" FPATH=$PWD PATH=$PWD:$PATH cat > A_t <<- \EOF + if false + then typeset -T Parser_shenanigans=(typeset -i foo) + fi typeset -T A_t=( B_t b ) EOF cat > B_t <<- \EOF + PATH=/dev/null command -v Parser_shenanigans typeset -T B_t=( integer n=5 ) EOF unset n -if n=$(FPATH=$PWD PATH=$PWD:$PATH $SHELL 2> /dev/null -c 'A_t a; print ${a.b.n}') -then (( n==5 )) || err_exit 'dynamic loading of types gives wrong result' -else err_exit 'unable to load types dynamically' +if n=$(FPATH=$PWD PATH=$PWD:$PATH "$SHELL" -c 'A_t a; print ${a.b.n}' 2>&1) +then [[ $n == '5' ]] || err_exit "dynamic loading of types gives wrong result (got $(printf %q "$n"))" +else err_exit "unable to load types dynamically (got $(printf %q "$n"))" fi # check that typeset -T reproduces a type. @@ -639,5 +643,19 @@ got=$($SHELL -c 'enum Foo_t=(foo bar); typeset -T') [[ -z $got ]] || err_exit "Types created by enum are listed with 'typeset -T'" \ "(got $(printf %q "$got"))" +# ====== +# Parser shenanigans. +if false +then typeset -T PARSER_t=(typeset name=foobar) +fi +PATH=/dev/null command -v PARSER_t >/dev/null && err_exit "PARSER_t incompletely defined though definition was never executed" + +unset v +got=$( set +x; redirect 2>&1; typeset -T Subsh_t=(typeset -i x); Subsh_t -a v=( (x=1) (x=2) (x=3) ); typeset -p v ) +exp='Subsh_t -a v=((typeset -i x=1) (typeset -i x=2) (typeset -i x=3))' +[[ $got == "$exp" ]] || err_exit "bad typeset output for Subsh_t" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" +PATH=/dev/null command -v Subsh_t >/dev/null && err_exit "Subsh_t leaked out of subshell" + # ====== exit $((Errors<125?Errors:125))