From ccfe8a65de9796dcacb234b73042616933807bf5 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Thu, 22 Apr 2021 05:01:55 -0700 Subject: [PATCH 01/42] Incomplete: Add local and declare builtins with a dynamic scope This implementation of local doesn't have proper scoping. The POSIX function scoping mechanism should be implemented in b_dot_cmd, although it should be done without breaking backward compatibility (i.e., typeset's behavior in POSIX functions shouldn't change as a result of implementing scoping). Discussion: https://github.com/ksh93/ksh/issues/123 src/cmd/ksh93/sh/name.c: - Set a dynamic scope in ksh functions for the local and declare builtins. This currently doesn't work in POSIX functions since b_dot_cmd doesn't apply any scoping right now. src/cmd/ksh93/bltins/typeset.c, src/cmd/ksh93/sh/xec.c: - Only allow the local builtin to run inside of functions. This is accomplished by using a new 'SH_INFUNCTION' state set when ksh is inside of a function. - Set shp->st.var_local to shp->var_tree for declare and local to get a dynamic scope in ksh functions. For typeset use shp->var_base to get a static scope in ksh functions. This will need bug-testing for when local is used in namespaces. - Add local and declare to top comment alongside other builtins. - Add a dictionary generator entry for declare(1). src/cmd/ksh93/data/builtins.c, src/cmd/ksh93/include/builtins.h: - Add local and declare to the builtin table as declaration builtins. Depending on how local is specified in the next POSIX standard, it may need to be changed to a special builtin. That does break backward compatibility with existing ksh scripts though (such as one regression test script), so for now it's just a regular builtin. - Update the man pages with references to local(1). Add information to the man page about the intended scoping of declare and local, although it's not completely implemented in POSIX functions. src/cmd/ksh93/tests/builtins.sh: - Run one of the regression tests for builtins inside of a function to fix a test failure with the local builtin. src/cmd/ksh93/tests/local.sh: - Add a number of regression tests mostly targeted at the local builtin. Most of the bugs tested for here are present in the ksh93v- and ksh2020 implementations of local and declare. --- src/cmd/ksh93/bltins/misc.c | 9 ++- src/cmd/ksh93/bltins/typeset.c | 20 +++++-- src/cmd/ksh93/data/builtins.c | 23 +++++--- src/cmd/ksh93/include/builtins.h | 23 ++++---- src/cmd/ksh93/include/shell.h | 1 + src/cmd/ksh93/sh/name.c | 5 +- src/cmd/ksh93/sh/xec.c | 33 ++++++++++- src/cmd/ksh93/tests/builtins.sh | 34 ++++++----- src/cmd/ksh93/tests/local.sh | 97 ++++++++++++++++++++++++++++++++ 9 files changed, 201 insertions(+), 44 deletions(-) create mode 100755 src/cmd/ksh93/tests/local.sh diff --git a/src/cmd/ksh93/bltins/misc.c b/src/cmd/ksh93/bltins/misc.c index 6f5f10322742..2867975d1d2c 100644 --- a/src/cmd/ksh93/bltins/misc.c +++ b/src/cmd/ksh93/bltins/misc.c @@ -236,7 +236,7 @@ int b_dot_cmd(register int n,char *argv[],Shbltin_t *context) register Shell_t *shp = context->shp; struct sh_scoped savst, *prevscope = shp->st.self; char *filename=0, *buffer=0, *tofree; - int fd; + int fd, infunction = 0; struct dolnod *saveargfor; volatile struct dolnod *argsave=0; struct checkpt buff; @@ -296,6 +296,11 @@ int b_dot_cmd(register int n,char *argv[],Shbltin_t *context) filename = path_fullname(shp,stkptr(shp->stk,PATH_OFFSET)); } } + if(np) + { + infunction = sh_isstate(SH_INFUNCTION); + sh_onstate(SH_INFUNCTION); + } *prevscope = shp->st; shp->st.lineno = np?((struct functnod*)nv_funtree(np))->functline:1; shp->st.var_local = shp->st.save_tree = shp->var_tree; @@ -347,6 +352,8 @@ int b_dot_cmd(register int n,char *argv[],Shbltin_t *context) } if (shp->st.self != &savst) *shp->st.self = shp->st; + if(!infunction) + sh_offstate(SH_INFUNCTION); /* only restore the top Shscope_t portion for posix functions */ memcpy((void*)&shp->st, (void*)prevscope, sizeof(Shscope_t)); shp->topscope = (Shscope_t*)prevscope; diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index 38b88d22e487..f3d5200b7969 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -22,6 +22,8 @@ * export [-p] [arg...] * readonly [-p] [arg...] * typeset [options] [arg...] + * declare [options] [arg...] + * local [options] [arg...] * autoload [options] [arg...] * compound [options] [arg...] * float [options] [arg...] @@ -193,10 +195,11 @@ int b_alias(int argc,register char *argv[],Shbltin_t *context) /* for the dictionary generator */ int b_autoload(int argc,register char *argv[],Shbltin_t *context){} int b_compound(int argc,register char *argv[],Shbltin_t *context){} + int b_declare(int argc,char *argv[],Shbltin_t *context){} int b_float(int argc,register char *argv[],Shbltin_t *context){} int b_functions(int argc,register char *argv[],Shbltin_t *context){} int b_integer(int argc,register char *argv[],Shbltin_t *context){} - int b_local(int argc,register char *argv[],Shbltin_t *context){} + int b_local(int argc,char *argv[],Shbltin_t *context){} int b_nameref(int argc,register char *argv[],Shbltin_t *context){} #endif int b_typeset(int argc,register char *argv[],Shbltin_t *context) @@ -206,18 +209,19 @@ int b_typeset(int argc,register char *argv[],Shbltin_t *context) const char *optstring = sh_opttypeset; Namdecl_t *ntp = (Namdecl_t*)context->ptr; Dt_t *troot; - int isfloat=0, isadjust=0, shortint=0, sflag=0; + int isfloat=0, isadjust=0, shortint=0, sflag=0, local; memset((void*)&tdata,0,sizeof(tdata)); tdata.sh = context->shp; troot = tdata.sh->var_tree; - if(ntp) /* custom declaration command added using enum */ + local = argv[0][0] == 'l'; + if(ntp) /* custom declaration command added using enum */ { tdata.tp = ntp->tp; opt_info.disc = (Optdisc_t*)ntp->optinfof; optstring = ntp->optstring; } - else if(argv[0][0] != 't') /* not ypeset */ + else if(argv[0][0] != 't' && argv[0][0] != 'd' && !local) /* not ypeset, eclare or ocal */ { char **new_argv = (char **)stakalloc((argc + 2) * sizeof(char*)); new_argv[0] = "typeset"; @@ -428,6 +432,14 @@ int b_typeset(int argc,register char *argv[],Shbltin_t *context) } endargs: argv += opt_info.index; + + /* 'local' builtin */ + if(local && !sh_isstate(SH_INFUNCTION)) + { + errormsg(SH_DICT,ERROR_exit(1), "can only be used in a function"); + UNREACHABLE(); + } + opt_info.disc = 0; /* handle argument of + and - specially */ if(*argv && argv[0][1]==0 && (*argv[0]=='+' || *argv[0]=='-')) diff --git a/src/cmd/ksh93/data/builtins.c b/src/cmd/ksh93/data/builtins.c index efa8d736bf40..0622c4ac4019 100644 --- a/src/cmd/ksh93/data/builtins.c +++ b/src/cmd/ksh93/data/builtins.c @@ -54,6 +54,7 @@ /* * IMPORTANT: The order of these struct members must be synchronous * with the offsets on the macros defined in include/builtins.h! + * The order up through "return" is significant. */ const struct shtable3 shtab_builtins[] = { @@ -73,6 +74,8 @@ const struct shtable3 shtab_builtins[] = "functions", NV_BLTIN|BLT_ENV, bltin(typeset), "integer", NV_BLTIN|BLT_ENV|BLT_DCL, bltin(typeset), "nameref", NV_BLTIN|BLT_ENV|BLT_DCL, bltin(typeset), + "local", NV_BLTIN|BLT_ENV|BLT_DCL, bltin(typeset), + "declare", NV_BLTIN|BLT_ENV|BLT_DCL, bltin(typeset), "test", NV_BLTIN|BLT_ENV, bltin(test), "[", NV_BLTIN|BLT_ENV, bltin(test), "let", NV_BLTIN|BLT_ENV, bltin(let), @@ -669,7 +672,7 @@ const char sh_optexport[] = "[+>0?An error occurred.]" "}" -"[+SEE ALSO?\bsh\b(1), \btypeset\b(1)]" +"[+SEE ALSO?\bsh\b(1), \btypeset\b(1), \blocal\b(1)]" ; const char sh_optgetopts[] = @@ -1472,7 +1475,7 @@ const char sh_optreadonly[] = "[+>0?An error occurred.]" "}" -"[+SEE ALSO?\bsh\b(1), \btypeset\b(1)]" +"[+SEE ALSO?\bsh\b(1), \btypeset\b(1), \blocal\b(1)]" ; const char sh_optredirect[] = @@ -1613,6 +1616,7 @@ const char sh_optksh[] = "[+SEE ALSO?\bset\b(1), \bbuiltin\b(1)]" ; + const char sh_optset[] = "+[-1c?\n@(#)$Id: set (AT&T Research) 1999-09-28 $\n]" "[--catalog?" SH_DICT "]" @@ -1649,11 +1653,9 @@ const char sh_optset[] = "[+>0?An error occurred.]" "}" -"[+SEE ALSO?\btypeset\b(1), \bshift\b(1)]" +"[+SEE ALSO?\btypeset\b(1), \blocal\b(1), \bshift\b(1)]" ; - - const char sh_optshift[] = "[-1c?\n@(#)$Id: shift (AT&T Research) 1999-07-07 $\n]" "[--catalog?" SH_DICT "]" @@ -1765,7 +1767,7 @@ const char sh_opttrap[] = ; const char sh_opttypeset[] = -"+[-1c?\n@(#)$Id: typeset (ksh 93u+m) 2021-02-10 $\n]" +"+[-1c?\n@(#)$Id: typeset (ksh 93u+m) 2021-04-08 $\n]" "[--catalog?" SH_DICT "]" "[+NAME?typeset - declare or display variables with attributes]" "[+DESCRIPTION?Without the \b-f\b option, \btypeset\b sets, unsets, " @@ -1778,7 +1780,12 @@ const char sh_opttypeset[] = "[+?When \btypeset\b is called inside a function defined with the " "\bfunction\b reserved word, and \aname\a does not contain a " "\b.\b, then a local variable statically scoped to that function " - "will be created.]" + "will be created. If \btypeset\b is used in a POSIX function, a " + "variable is created in the global scope.]" +"[+?\btypeset\b can also be invoked as \bdeclare\b, or (in functions " + "only) as \blocal\b. A variable created or modified by either \bdeclare\b " + "or \blocal\b is given a dynamic scope limited to the function the " + "variable was created in.]" "[+?Not all option combinations are possible. For example, the numeric " "options \b-i\b, \b-E\b, and \b-F\b cannot be specified with " "the justification options \b-L\b, \b-R\b, and \b-Z\b.]" @@ -1995,7 +2002,7 @@ const char sh_optunset[] = "or an error occurred.]" "}" -"[+SEE ALSO?\btypeset\b(1)]" +"[+SEE ALSO?\btypeset\b(1), \blocal\b(1)]" ; const char sh_optunalias[] = diff --git a/src/cmd/ksh93/include/builtins.h b/src/cmd/ksh93/include/builtins.h index ef3bb1987d26..f8c4eda13ecc 100644 --- a/src/cmd/ksh93/include/builtins.h +++ b/src/cmd/ksh93/include/builtins.h @@ -19,8 +19,6 @@ ***********************************************************************/ #pragma prototyped -#ifndef SYSDECLARE - #include #include "FEATURE/options" #include "FEATURE/dynamic" @@ -49,15 +47,17 @@ /* functions | */ /* integer | */ #define SYSNAMEREF (shgd->bltin_cmds+15) /* nameref | */ -#define SYSTYPESET_END (shgd->bltin_cmds+15) /* / */ - -#define SYSTEST (shgd->bltin_cmds+16) /* test */ -#define SYSBRACKET (shgd->bltin_cmds+17) /* [ */ -#define SYSLET (shgd->bltin_cmds+18) /* let */ -#define SYSEXPORT (shgd->bltin_cmds+19) /* export */ -#define SYSDOT (shgd->bltin_cmds+20) /* . */ -#define SYSSOURCE (shgd->bltin_cmds+21) /* source */ -#define SYSRETURN (shgd->bltin_cmds+22) /* return */ +#define SYSLOCAL (shgd->bltin_cmds+16) /* local */ +#define SYSDECLARE (shgd->bltin_cmds+17) /* declare */ +#define SYSTYPESET_END (shgd->bltin_cmds+17) /* / */ + +#define SYSTEST (shgd->bltin_cmds+18) /* test */ +#define SYSBRACKET (shgd->bltin_cmds+19) /* [ */ +#define SYSLET (shgd->bltin_cmds+20) /* let */ +#define SYSEXPORT (shgd->bltin_cmds+21) /* export */ +#define SYSDOT (shgd->bltin_cmds+22) /* . */ +#define SYSSOURCE (shgd->bltin_cmds+23) /* source */ +#define SYSRETURN (shgd->bltin_cmds+24) /* return */ /* entry point for shell special builtins */ @@ -206,7 +206,6 @@ extern const char sh_optwait[]; #endif /* _cmd_universe */ extern const char sh_optunset[]; extern const char sh_optwhence[]; -#endif /* SYSDECLARE */ extern const char sh_opttimes[]; extern const char e_dict[]; diff --git a/src/cmd/ksh93/include/shell.h b/src/cmd/ksh93/include/shell.h index 740b9592daa9..0c03f04017b7 100644 --- a/src/cmd/ksh93/include/shell.h +++ b/src/cmd/ksh93/include/shell.h @@ -77,6 +77,7 @@ typedef union Shnode_u Shnode_t; #define SH_COMPLETE 19 /* set for command completion */ #define SH_INTESTCMD 20 /* set while test/[ command is being run */ #define SH_XARG 21 /* set while in xarg (command -x) mode */ +#define SH_INFUNCTION 22 /* set while in a function */ /* * Shell options (set -o). Used with sh_isoption(), sh_onoption(), sh_offoption(). diff --git a/src/cmd/ksh93/sh/name.c b/src/cmd/ksh93/sh/name.c index 9b571bd8a8b4..57e72ec892cd 100644 --- a/src/cmd/ksh93/sh/name.c +++ b/src/cmd/ksh93/sh/name.c @@ -2346,8 +2346,11 @@ int nv_scan(Dt_t *root, void (*fn)(Namval_t*,void*), void *data,int mask, int fl */ void sh_scope(Shell_t *shp, struct argnod *envlist, int fun) { - register Dt_t *newscope, *newroot=shp->var_base; + register Dt_t *newscope, *newroot; struct Ufunction *rp; + /* TODO: Fix the scope of POSIX functions with declare and local */ + newroot = shp->st.var_local ? shp->st.var_local : shp->var_base; + #if SHOPT_NAMESPACE if(shp->namespace) newroot = nv_dict(shp->namespace); diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index 21f337421a3f..deead6e805c7 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -1127,6 +1127,27 @@ int sh_exec(register const Shnode_t *t, int flags) } if(np) flgs |= NV_UNJUST; + + /* Prohibit usage of the local builtin outside of functions */ + if(np==SYSLOCAL && !sh_isstate(SH_INFUNCTION)) + { + errormsg(SH_DICT,ERROR_exit(1),"%s: can only be used in a function",com0); + UNREACHABLE(); + } + /* + * The declare and local builtins have a dynamic scope limited + * to the function in which they are called. This should be + * skipped when not in a function. + */ + if((np == SYSLOCAL || (sh_isstate(SH_INFUNCTION) && np == SYSDECLARE)) && shp->st.var_local != shp->var_tree) + { + sh_scope(shp,(struct argnod*)0,0); + shp->st.var_local = shp->var_tree; + } + /* The other typeset builtins have a static scope */ + else if (!(np == SYSLOCAL || np == SYSDECLARE) && shp->st.var_local == shp->var_tree) + shp->st.var_local = shp->var_base; + if(np && np->nvalue.bfp==SYSTYPESET->nvalue.bfp) { /* command calls b_typeset(); treat as a typeset variant */ @@ -3154,7 +3175,7 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) struct dolnod *argsav=0,*saveargfor; struct sh_scoped savst, *prevscope = shp->st.self; struct argnod *envlist=0; - int isig,jmpval; + int isig,jmpval,infunction; volatile int r = 0; int n; char **savsig; @@ -3168,6 +3189,10 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) shp->glob_options = shp->options; else shp->options = shp->glob_options; + + infunction = sh_isstate(SH_INFUNCTION); + sh_onstate(SH_INFUNCTION); + *prevscope = shp->st; sh_offoption(SH_ERREXIT); shp->st.prevst = prevscope; @@ -3300,6 +3325,8 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) shp->trapnote=0; shp->options = options; shp->last_root = last_root; + if(!infunction) + sh_offstate(SH_INFUNCTION); if(jmpval == SH_JMPSUB) siglongjmp(*shp->jmplist,jmpval); if(trap) @@ -3348,11 +3375,11 @@ static void sh_funct(Shell_t *shp,Namval_t *np,int argn, char *argv[],struct arg argv[-1] = 0; shp->st.funname = nv_name(np); shp->last_root = nv_dict(DOTSHNOD); - nv_putval(SH_FUNNAMENOD, nv_name(np),NV_NOFREE); + nv_putval(SH_FUNNAMENOD, nv_name(np), NV_NOFREE); opt_info.index = opt_info.offset = 0; error_info.errors = 0; shp->st.loopcnt = 0; - b_dot_cmd(argn+1,argv-1,&shp->bltindata); + b_dot_cmd(argn+1,argv-1,&shp->bltindata); /* NOTE: This can be replaced with sh_funscope to get a local scope */ shp->st.loopcnt = loopcnt; argv[-1] = save; } diff --git a/src/cmd/ksh93/tests/builtins.sh b/src/cmd/ksh93/tests/builtins.sh index e6fe731ca088..ee6feca6b84a 100755 --- a/src/cmd/ksh93/tests/builtins.sh +++ b/src/cmd/ksh93/tests/builtins.sh @@ -1011,21 +1011,25 @@ EOF "$SHELL" -i "$sleepsig" 2> /dev/null || err_exit "'sleep -s' doesn't work with intervals of more than 30 seconds" # ====== -# Builtins should handle unrecognized options correctly -while IFS= read -r bltin <&3 -do case $bltin in - echo | test | true | false | \[ | : | getconf | */getconf | uname | */uname | catclose | catgets | catopen | Dt* | _Dt* | X* | login | newgrp ) - continue ;; - /*/*) expect="Usage: ${bltin##*/} " - actual=$({ PATH=${bltin%/*}; "${bltin##*/}" --this-option-does-not-exist; } 2>&1) ;; - */*) err_exit "strange path name in 'builtin' output: $(printf %q "$bltin")" - continue ;; - *) expect="Usage: $bltin " - actual=$({ "${bltin}" --this-option-does-not-exist; } 2>&1) ;; - esac - [[ $actual == *"$expect"* ]] || err_exit "$bltin should show usage info on unrecognized options" \ - "(expected string containing $(printf %q "$expect"), got $(printf %q "$actual"))" -done 3< <(builtin) +# Builtins should handle unrecognized options correctly. +# Note: This test is run in a function for compatibility with the local builtin. +function test_usage +{ + while IFS= read -r bltin <&3 + do case $bltin in + echo | test | true | false | \[ | : | getconf | */getconf | uname | */uname | catclose | catgets | catopen | Dt* | _Dt* | X* | login | newgrp ) + continue ;; + /*/*) expect="Usage: ${bltin##*/} " + actual=$({ PATH=${bltin%/*}; "${bltin##*/}" --this-option-does-not-exist; } 2>&1) ;; + */*) err_exit "strange path name in 'builtin' output: $(printf %q "$bltin")" + continue ;; + *) expect="Usage: $bltin " + actual=$({ "${bltin}" --this-option-does-not-exist; } 2>&1) ;; + esac + [[ $actual == *"$expect"* ]] || err_exit "$bltin should show usage info on unrecognized options" \ + "(expected string containing $(printf %q "$expect"), got $(printf %q "$actual"))" + done 3< <(builtin) +}; test_usage # ====== # The 'alarm' builtin could make 'read' crash due to IFS table corruption caused by unsafe asynchronous execution. diff --git a/src/cmd/ksh93/tests/local.sh b/src/cmd/ksh93/tests/local.sh new file mode 100755 index 000000000000..5ce3b1b56cb1 --- /dev/null +++ b/src/cmd/ksh93/tests/local.sh @@ -0,0 +1,97 @@ +######################################################################## +# # +# This file is part of the ksh 93u+m package # +# Copyright (c) 2020-2021 Contributors to ksh 93u+m # +# # +# and is licensed under the # +# Eclipse Public License, Version 1.0 # +# # +# A copy of the License is available at # +# http://www.eclipse.org/org/documents/epl-v10.html # +# (with md5 checksum b35adb5213ca9657e911e9befb180842) # +# # +# Johnothan King # +# # +######################################################################## + +# These are regression tests for the local and declare builtins. +# In the cases when local could return an error, it's run using +# 'command' because it's a special builtin in ksh93v- and ksh2020. + +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + (( Errors+=1 )) +} +alias err_exit='err_exit $LINENO' + +Command=${0##*/} +integer Errors=0 + +# ====== +# This test must be run first due to the next test. +command local 2> /dev/null && err_exit "'local' works outside of functions" + +# local shouldn't suddenly work outside of functions after a function runs local. +posix_dummy() { command local > /dev/null; } +function ksh_dummy { command local > /dev/null; } +posix_dummy && command local 2> /dev/null && err_exit 'the local builtin works outside of functions after a POSIX function runs local' +ksh_dummy && command local 2> /dev/null && err_exit 'the local builtin works outside of functions after a KornShell function runs local' + +# ====== +for i in declare local; do + # local should work inside both kinds of functions, without reliance on environment variables. + function ksh_function_nounset { + command $i foo=bar 2>&1 + } + function ksh_function_unset { + unset .sh.fun + command $i foo=bar 2>&1 + } + posix_function_nounset() { + command $i foo=bar 2>&1 + } + posix_function_unset() { + unset .sh.fun + command $i foo=bar 2>&1 + } + [[ $(ksh_function_nounset) ]] && err_exit "'$i' fails inside of KornShell functions" + [[ $(ksh_function_unset) ]] && err_exit "'$i' fails inside of KornShell functions when \${.sh.fun} is unset" + [[ $(posix_function_nounset) ]] && err_exit "'$i' fails inside of POSIX functions" + [[ $(posix_function_unset) ]] && err_exit "'$i' fails inside of POSIX functions when \${.sh.fun} is unset" + + # The local and declare builtins should have a dynamic scope + # Tests for the scope of POSIX functions + foo=globalscope + subfunc() { + [[ $foo == dynscope ]] + } + mainfunc() { + $i foo=dynscope + subfunc + } + mainfunc || err_exit "'$i' is not using a dynamic scope in POSIX functions" + # TODO: Implement scoping in POSIX functions + #[[ $foo == globalscope ]] || err_exit "'$i' changes variables outside of a POSIX function's scope" + + # Tests for the scope of KornShell functions + bar=globalscope + function subfunc_b { + [[ $bar == dynscope ]] + } + function mainfunc_b { + $i bar=dynscope + subfunc_b + } + mainfunc_b || err_exit "'$i' is not using a dynamic scope in KornShell functions" + [[ $bar == globalscope ]] || err_exit "'$i' changes variables outside of a KornShell function's scope" +done + +# The declare builtin should work outside of functions +unset foo +declare foo=bar +[[ $foo == bar ]] || err_exit "'declare' doesn't work outside of functions" + +# ====== +exit $((Errors<125?Errors:125)) From 94013f56ab6e3c8de23b5be592baa2056b4247c3 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Mon, 6 Dec 2021 11:49:01 -0800 Subject: [PATCH 02/42] Source common shtests code in local.sh (re: 844e6b24, aed5c6d7) --- src/cmd/ksh93/tests/local.sh | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/cmd/ksh93/tests/local.sh b/src/cmd/ksh93/tests/local.sh index 5ce3b1b56cb1..94f2fd5a2de7 100755 --- a/src/cmd/ksh93/tests/local.sh +++ b/src/cmd/ksh93/tests/local.sh @@ -18,16 +18,7 @@ # In the cases when local could return an error, it's run using # 'command' because it's a special builtin in ksh93v- and ksh2020. -function err_exit -{ - print -u2 -n "\t" - print -u2 -r ${Command}[$1]: "${@:2}" - (( Errors+=1 )) -} -alias err_exit='err_exit $LINENO' - -Command=${0##*/} -integer Errors=0 +. "${SHTESTS_COMMON:-${0%/*}/_common}" # ====== # This test must be run first due to the next test. From 91d3079348ab54f8e5542202348b9be86cfc4b61 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Wed, 26 Jan 2022 21:02:40 -0800 Subject: [PATCH 03/42] Fix compile of the forever incomplete local builtin --- src/cmd/ksh93/include/builtins.h | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/cmd/ksh93/include/builtins.h b/src/cmd/ksh93/include/builtins.h index bdcc7159f8ba..c30d5cb0c4c9 100644 --- a/src/cmd/ksh93/include/builtins.h +++ b/src/cmd/ksh93/include/builtins.h @@ -49,19 +49,19 @@ /* float >typeset range */ /* functions | */ /* integer | */ -#define SYSNAMEREF (shgd->bltin_cmds+15) /* nameref | */ -#define SYSLOCAL (shgd->bltin_cmds+16) /* local | */ -#define SYSDECLARE (shgd->bltin_cmds+17) /* declare | */ -#define SYSTYPESET_END (shgd->bltin_cmds+17) /* / */ - -#define SYSTEST (shgd->bltin_cmds+18) /* test */ -#define SYSBRACKET (shgd->bltin_cmds+19) /* [ */ -#define SYSLET (shgd->bltin_cmds+20) /* let */ -#define SYSEXPORT (shgd->bltin_cmds+21) /* export */ -#define SYSDOT (shgd->bltin_cmds+22) /* . */ -#define SYSSOURCE (shgd->bltin_cmds+23) /* source */ -#define SYSRETURN (shgd->bltin_cmds+24) /* return */ -#define SYSENUM (shgd->bltin_cmds+25) /* enum */ +#define SYSNAMEREF (sh.bltin_cmds+15) /* nameref | */ +#define SYSLOCAL (sh.bltin_cmds+16) /* local | */ +#define SYSDECLARE (sh.bltin_cmds+17) /* declare | */ +#define SYSTYPESET_END (sh.bltin_cmds+17) /* / */ + +#define SYSTEST (sh.bltin_cmds+18) /* test */ +#define SYSBRACKET (sh.bltin_cmds+19) /* [ */ +#define SYSLET (sh.bltin_cmds+20) /* let */ +#define SYSEXPORT (sh.bltin_cmds+21) /* export */ +#define SYSDOT (sh.bltin_cmds+22) /* . */ +#define SYSSOURCE (sh.bltin_cmds+23) /* source */ +#define SYSRETURN (sh.bltin_cmds+24) /* return */ +#define SYSENUM (sh.bltin_cmds+25) /* enum */ /* entry point for shell special builtins */ From 7721eebf323f2ef1edddf4ccd1cb0d09a94e321d Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Mon, 1 Jan 2024 02:22:23 -0800 Subject: [PATCH 04/42] Experimental version of local builtin with POSIX function scoping --- src/cmd/ksh93/bltins/misc.c | 71 +++++----- src/cmd/ksh93/bltins/typeset.c | 32 +++-- src/cmd/ksh93/data/builtins.c | 20 ++- src/cmd/ksh93/include/builtins.h | 22 +-- src/cmd/ksh93/include/defs.h | 1 + src/cmd/ksh93/include/shell.h | 3 +- src/cmd/ksh93/sh/init.c | 2 +- src/cmd/ksh93/sh/name.c | 24 +++- src/cmd/ksh93/sh/xec.c | 161 +++++++++++++--------- src/cmd/ksh93/tests/builtins.sh | 2 +- src/cmd/ksh93/tests/local.sh | 227 +++++++++++++++++++++++++++++++ 11 files changed, 434 insertions(+), 131 deletions(-) create mode 100755 src/cmd/ksh93/tests/local.sh diff --git a/src/cmd/ksh93/bltins/misc.c b/src/cmd/ksh93/bltins/misc.c index 92b018352ec9..a7fba022e333 100644 --- a/src/cmd/ksh93/bltins/misc.c +++ b/src/cmd/ksh93/bltins/misc.c @@ -220,7 +220,7 @@ int b_dot_cmd(int n,char *argv[],Shbltin_t *context) int jmpval; struct sh_scoped savst, *prevscope = sh.st.self; char *filename=0, *buffer=0, *tofree; - int fd; + int fd, infunction=-1; struct dolnod *saveargfor; volatile struct dolnod *argsave=0; struct checkpt buff; @@ -247,56 +247,57 @@ int b_dot_cmd(int n,char *argv[],Shbltin_t *context) errormsg(SH_DICT,ERROR_exit(1),e_toodeep,script); UNREACHABLE(); } - if(!(np=sh.posix_fun)) + /* check for KornShell style function first */ + np = nv_search(script,sh.fun_tree,0); + if(np && is_afunction(np) && !nv_isattr(np,NV_FPOSIX) && !(sh_isoption(SH_POSIX) && sh.bltindata.bnode==SYSDOT)) { - /* check for KornShell style function first */ - np = nv_search(script,sh.fun_tree,0); - if(np && is_afunction(np) && !nv_isattr(np,NV_FPOSIX) && !(sh_isoption(SH_POSIX) && sh.bltindata.bnode==SYSDOT)) + if(!np->nvalue.ip) { - if(!np->nvalue.ip) + path_search(script,NULL,0); + if(np->nvalue.ip) { - path_search(script,NULL,0); - if(np->nvalue.ip) - { - if(nv_isattr(np,NV_FPOSIX)) - np = 0; - } - else - { - errormsg(SH_DICT,ERROR_exit(1),e_found,script); - UNREACHABLE(); - } + if(nv_isattr(np,NV_FPOSIX)) + np = 0; } - } - else - np = 0; - if(!np) - { - if((fd=path_open(script,path_get(script))) < 0) + else { - errormsg(SH_DICT,ERROR_system(1),e_open,script); + errormsg(SH_DICT,ERROR_exit(1),e_found,script); UNREACHABLE(); } - filename = path_fullname(stkptr(sh.stk,PATH_OFFSET)); } } + else + np = 0; + if(!np) + { + if((fd=path_open(script,path_get(script))) < 0) + { + errormsg(SH_DICT,ERROR_system(1),e_open,script); + UNREACHABLE(); + } + filename = path_fullname(stkptr(sh.stk,PATH_OFFSET)); + } *prevscope = sh.st; + sh.st.prevst = prevscope; + sh.st.self = &savst; + sh.topscope = (Shscope_t*)sh.st.self; + prevscope->save_tree = sh.var_tree; + if(np) + { + infunction = sh.infunction; + sh.infunction = 1; + } sh.st.lineno = np?((struct functnod*)nv_funtree(np))->functline:1; - sh.st.save_tree = sh.var_tree; + sh.st.save_tree = sh.st.var_local = sh.var_tree; if(filename) { sh.st.filename = filename; sh.st.lineno = 1; } - sh.st.prevst = prevscope; - sh.st.self = &savst; - sh.topscope = (Shscope_t*)sh.st.self; - prevscope->save_tree = sh.var_tree; tofree = sh.st.filename; if(np) sh.st.filename = np->nvalue.rp->fname; nv_putval(SH_PATHNAMENOD, sh.st.filename ,NV_NOFREE); - sh.posix_fun = 0; if(np || argv[1]) argsave = sh_argnew(argv,&saveargfor); sh_pushcontext(&buff,SH_JMPDOT); @@ -331,12 +332,14 @@ int b_dot_cmd(int n,char *argv[],Shbltin_t *context) prevscope->dolc = sh.st.dolc; prevscope->dolv = sh.st.dolv; } - if (sh.st.self != &savst) + if(sh.st.self != &savst) *sh.st.self = sh.st; - /* only restore the top Shscope_t portion for POSIX functions */ + if(infunction != -1) + sh.infunction = infunction; + /* only restore the top Shscope_t portion */ memcpy(&sh.st, prevscope, sizeof(Shscope_t)); sh.topscope = (Shscope_t*)prevscope; - nv_putval(SH_PATHNAMENOD, sh.st.filename ,NV_NOFREE); + nv_putval(SH_PATHNAMENOD,sh.st.filename,NV_NOFREE); if(jmpval && jmpval!=SH_JMPFUN) siglongjmp(*sh.jmplist,jmpval); return sh.exitval; diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index 1c15b3a255e1..9de6e510f57f 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -21,6 +21,8 @@ * export [-p] [arg...] * readonly [-p] [arg...] * typeset [options] [arg...] + * declare [options] [arg...] + * local [options] [arg...] * autoload [options] [arg...] * compound [options] [arg...] * float [options] [arg...] @@ -208,6 +210,7 @@ int b_alias(int argc,char *argv[],Shbltin_t *context) /* for the dictionary generator */ int b_autoload(int argc,char *argv[],Shbltin_t *context){} int b_compound(int argc,char *argv[],Shbltin_t *context){} + int b_declare(int argc,char *argv[],Shbltin_t *context){} int b_float(int argc,char *argv[],Shbltin_t *context){} int b_functions(int argc,char *argv[],Shbltin_t *context){} int b_integer(int argc,char *argv[],Shbltin_t *context){} @@ -221,17 +224,18 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) const char *optstring = sh_opttypeset; Namdecl_t *ntp = (Namdecl_t*)context->ptr; Dt_t *troot; - int isfloat=0, isadjust=0, shortint=0, sflag=0; + int isfloat=0, isadjust=0, shortint=0, sflag=0, local; memset(&tdata,0,sizeof(tdata)); troot = sh.var_tree; + local = argv[0][0] == 'l'; if(ntp) /* custom declaration command added using enum */ { tdata.tp = ntp->tp; opt_info.disc = (Optdisc_t*)ntp->optinfof; optstring = ntp->optstring; } - else if(argv[0][0] != 't') /* not ypeset */ + else if(argv[0][0] != 't' && argv[0][0] != 'd' && !local) /* not ypeset, eclare or ocal */ { char **new_argv = (char **)stkalloc(sh.stk, (argc + 2) * sizeof(char*)); error_info.id = new_argv[0] = SYSTYPESET->nvname; @@ -444,6 +448,12 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) endargs: argv += opt_info.index; opt_info.disc = 0; + /* 'local' builtin */ + if(local && !sh.infunction) + { + errormsg(SH_DICT,ERROR_exit(1), "can only be used in a function"); + UNREACHABLE(); + } /* handle argument of + and - specially */ if(*argv && argv[0][1]==0 && (*argv[0]=='+' || *argv[0]=='-')) tdata.aflag = *argv[0]; @@ -647,17 +657,21 @@ static int setall(char **argv,int flag,Dt_t *troot,struct tdata *tp) char *last = 0; int nvflags=(flag&(NV_ARRAY|NV_NOARRAY|NV_VARNAME|NV_IDENT|NV_ASSIGN|NV_STATIC|NV_MOVE)); int r=0, ref=0, comvar=(flag&NV_COMVAR),iarray=(flag&NV_IARRAY); - Dt_t *save_vartree; + Dt_t *save_vartree, *save_varlocal = 0; Namval_t *save_namespace; - if(flag&NV_GLOBAL) + if((flag&NV_GLOBAL) || (sh.infunction==1 && sh.st.var_local == sh.var_base)) { save_vartree = sh.var_tree; - troot = sh.var_tree = sh.var_base; + save_varlocal = sh.st.var_local; + troot = sh.var_tree = sh.st.var_local = sh.var_base; + } #if SHOPT_NAMESPACE + if(flag&NV_GLOBAL) + { save_namespace = sh.namespace; sh.namespace = NULL; -#endif } +#endif if(!sh.prefix) { if(!tp->pflag) @@ -1031,13 +1045,15 @@ static int setall(char **argv,int flag,Dt_t *troot,struct tdata *tp) if(r==0) r = 1; /* ensure the exit status is at least 1 */ } - if(flag&NV_GLOBAL) + if((flag&NV_GLOBAL) || (sh.infunction==1 && save_varlocal)) { sh.var_tree = save_vartree; + sh.st.var_local = save_varlocal; + } #if SHOPT_NAMESPACE + if(flag&NV_GLOBAL) sh.namespace = save_namespace; #endif - } return r; } diff --git a/src/cmd/ksh93/data/builtins.c b/src/cmd/ksh93/data/builtins.c index b48a5a9b2a6d..caa2275c4d73 100644 --- a/src/cmd/ksh93/data/builtins.c +++ b/src/cmd/ksh93/data/builtins.c @@ -70,6 +70,8 @@ const struct shtable3 shtab_builtins[] = "functions", NV_BLTIN|BLT_ENV, bltin(typeset), "integer", NV_BLTIN|BLT_ENV|BLT_DCL, bltin(typeset), "nameref", NV_BLTIN|BLT_ENV|BLT_DCL, bltin(typeset), + "local", NV_BLTIN|BLT_ENV|BLT_DCL, bltin(typeset), + "declare", NV_BLTIN|BLT_ENV|BLT_DCL, bltin(typeset), "test", NV_BLTIN|BLT_ENV, bltin(test), "[", NV_BLTIN|BLT_ENV, bltin(test), "let", NV_BLTIN|BLT_ENV, bltin(let), @@ -729,7 +731,7 @@ const char sh_optexport[] = "[+>0?An error occurred.]" "}" -"[+SEE ALSO?\bsh\b(1), \btypeset\b(1)]" +"[+SEE ALSO?\bsh\b(1), \btypeset\b(1), \blocal\b(1)]" ; const char sh_optgetopts[] = @@ -1544,7 +1546,7 @@ const char sh_optreadonly[] = "[+>0?An error occurred.]" "}" -"[+SEE ALSO?\bsh\b(1), \btypeset\b(1)]" +"[+SEE ALSO?\bsh\b(1), \btypeset\b(1), \blocal\b(1)]" ; const char sh_optredirect[] = @@ -1692,6 +1694,7 @@ const char sh_optksh[] = "[+SEE ALSO?\bset\b(1), \bbuiltin\b(1)]" ; + const char sh_optset[] = "+[-1c?\n@(#)$Id: set (ksh 93u+m) 2023-05-18 $\n]" "[--catalog?" SH_DICT "]" @@ -1728,11 +1731,9 @@ const char sh_optset[] = "[+>0?An error occurred.]" "}" -"[+SEE ALSO?\btypeset\b(1), \bshift\b(1)]" +"[+SEE ALSO?\btypeset\b(1), \blocal\b(1), \bshift\b(1)]" ; - - const char sh_optshift[] = "[-1c?\n@(#)$Id: shift (AT&T Research) 1999-07-07 $\n]" "[--catalog?" SH_DICT "]" @@ -1857,7 +1858,12 @@ const char sh_opttypeset[] = "[+?When \btypeset\b is called inside a function defined with the " "\bfunction\b reserved word, and \aname\a does not contain a " "\b.\b, then a local variable statically scoped to that function " - "will be created.]" + "will be created. If \btypeset\b is used in a POSIX function, a " + "variable is created in the global scope.]" +"[+?\btypeset\b can also be invoked as \bdeclare\b, or (in functions " + "only) as \blocal\b. A variable created or modified by either \bdeclare\b " + "or \blocal\b is given a dynamic scope limited to the function the " + "variable was created in.]" "[+?Not all option combinations are possible. For example, the numeric " "options \b-i\b, \b-E\b, and \b-F\b cannot be specified with " "the justification options \b-L\b, \b-R\b, and \b-Z\b.]" @@ -2076,7 +2082,7 @@ const char sh_optunset[] = "or an error occurred.]" "}" -"[+SEE ALSO?\btypeset\b(1)]" +"[+SEE ALSO?\btypeset\b(1), \blocal\b(1)]" ; const char sh_optunalias[] = diff --git a/src/cmd/ksh93/include/builtins.h b/src/cmd/ksh93/include/builtins.h index f6b3f59eced8..ef483fe4236e 100644 --- a/src/cmd/ksh93/include/builtins.h +++ b/src/cmd/ksh93/include/builtins.h @@ -46,16 +46,18 @@ /* functions | */ /* integer | */ #define SYSNAMEREF (sh.bltin_cmds+15) /* nameref | */ -#define SYSTYPESET_END (sh.bltin_cmds+15) /* / */ - -#define SYSTEST (sh.bltin_cmds+16) /* test */ -#define SYSBRACKET (sh.bltin_cmds+17) /* [ */ -#define SYSLET (sh.bltin_cmds+18) /* let */ -#define SYSEXPORT (sh.bltin_cmds+19) /* export */ -#define SYSDOT (sh.bltin_cmds+20) /* . */ -#define SYSSOURCE (sh.bltin_cmds+21) /* source */ -#define SYSRETURN (sh.bltin_cmds+22) /* return */ -#define SYSENUM (sh.bltin_cmds+23) /* enum */ +#define SYSLOCAL (sh.bltin_cmds+16) /* local | */ +#define SYSDECLARE (sh.bltin_cmds+17) /* declare | */ +#define SYSTYPESET_END (sh.bltin_cmds+17) /* / */ + +#define SYSTEST (sh.bltin_cmds+18) /* test */ +#define SYSBRACKET (sh.bltin_cmds+19) /* [ */ +#define SYSLET (sh.bltin_cmds+20) /* let */ +#define SYSEXPORT (sh.bltin_cmds+21) /* export */ +#define SYSDOT (sh.bltin_cmds+22) /* . */ +#define SYSSOURCE (sh.bltin_cmds+23) /* source */ +#define SYSRETURN (sh.bltin_cmds+24) /* return */ +#define SYSENUM (sh.bltin_cmds+25) /* enum */ /* entry point for shell special builtins */ diff --git a/src/cmd/ksh93/include/defs.h b/src/cmd/ksh93/include/defs.h index 0dede50a0be5..39fef51acd1b 100644 --- a/src/cmd/ksh93/include/defs.h +++ b/src/cmd/ksh93/include/defs.h @@ -128,6 +128,7 @@ extern int sh_macfun(const char*,int); extern void sh_machere(Sfio_t*, Sfio_t*, char*); extern void *sh_macopen(void); extern char *sh_macpat(struct argnod*,int); +extern void local_exports(Namval_t*, void*); extern Sfdouble_t sh_mathfun(void*, int, Sfdouble_t*); extern int sh_outtype(Sfio_t*); extern char *sh_mactry(char*); diff --git a/src/cmd/ksh93/include/shell.h b/src/cmd/ksh93/include/shell.h index 32b18780f53b..4fcd81d87bbc 100644 --- a/src/cmd/ksh93/include/shell.h +++ b/src/cmd/ksh93/include/shell.h @@ -198,6 +198,7 @@ struct sh_scoped int lineno; Dt_t *save_tree; /* var_tree for calling function */ struct sh_scoped *self; /* pointer to copy of this scope */ + Dt_t *var_local; /* local level variables used by 'local' and 'declare' */ struct slnod *staklist; /* link list of function stacks */ int states; /* shell state bits used by sh_isstate(), etc. */ int breakcnt; /* number of levels to 'break'/'continue' (negative if 'continue') */ @@ -294,7 +295,6 @@ struct Shell_s unsigned int jobenv; /* subshell number for jobs */ int infd; /* input file descriptor */ short nextprompt; /* next prompt is PS */ - Namval_t *posix_fun; /* points to last name() function */ char *outbuff; /* pointer to output buffer */ char *errbuff; /* pointer to stderr buffer */ char *prompt; /* pointer to prompt string */ @@ -317,6 +317,7 @@ struct Shell_s char funload; char used_pos; /* used positional parameter */ char universe; + char infunction; /* 0 outside of functions, 1 in ksh functions run from '.' and POSIX functions, 2 in regular ksh functions */ char winch; /* set upon window size change or 'set -b' notification */ short arithrecursion; /* current arithmetic recursion level */ char indebug; /* set when in debug trap */ diff --git a/src/cmd/ksh93/sh/init.c b/src/cmd/ksh93/sh/init.c index 65f80169d1dc..d52f73df7948 100644 --- a/src/cmd/ksh93/sh/init.c +++ b/src/cmd/ksh93/sh/init.c @@ -1515,7 +1515,7 @@ int sh_reinit(char *argv[]) int nofree; char *savfpath = NULL; sh_onstate(SH_INIT); - sh.subshell = sh.realsubshell = sh.comsub = sh.curenv = sh.jobenv = sh.inuse_bits = sh.fn_depth = sh.dot_depth = 0; + sh.subshell = sh.realsubshell = sh.comsub = sh.curenv = sh.jobenv = sh.inuse_bits = sh.fn_depth = sh.dot_depth = sh.infunction = 0; sh.envlist = NULL; sh.last_root = sh.var_tree; if(sh.heredocs) diff --git a/src/cmd/ksh93/sh/name.c b/src/cmd/ksh93/sh/name.c index 60f72ae9b159..a6e14ee63e5b 100644 --- a/src/cmd/ksh93/sh/name.c +++ b/src/cmd/ksh93/sh/name.c @@ -233,19 +233,23 @@ void nv_setlist(struct argnod *arg,int flags, Namval_t *typ) struct Namref nr; int maketype = flags&NV_TYPE; /* make a 'typeset -T' type definition command */ struct sh_type shtp; - Dt_t *vartree, *save_vartree; + Dt_t *vartree, *save_vartree, *save_varlocal = 0; #if SHOPT_NAMESPACE Namval_t *save_namespace; #endif - if(flags&NV_GLOBAL) + if((flags&NV_GLOBAL) || (sh.infunction==1 && sh.st.var_local == sh.var_base)) { save_vartree = sh.var_tree; - sh.var_tree = sh.var_base; + save_varlocal = sh.st.var_local; + sh.var_tree = sh.st.var_local = sh.var_base; + } #if SHOPT_NAMESPACE + if(flags&NV_GLOBAL) + { save_namespace = sh.namespace; sh.namespace = NULL; -#endif } +#endif if(maketype) { shtp.previous = sh.mktype; @@ -382,6 +386,7 @@ void nv_setlist(struct argnod *arg,int flags, Namval_t *typ) Dt_t *last_root = sh.last_root; char **argv = sh_argbuild(&argc,&tp->com,0); sh.last_root = last_root; + /* TODO: Implement a proper check for POSIX functions here */ if(sh.mktype && sh.dot_depth==0 && np==((struct sh_type*)sh.mktype)->nodes[0]) { sh.mktype = 0; @@ -657,13 +662,15 @@ void nv_setlist(struct argnod *arg,int flags, Namval_t *typ) } /* continue loop */ } - if(flags&NV_GLOBAL) + if((flags&NV_GLOBAL) || (sh.infunction==1 && save_varlocal)) { sh.var_tree = save_vartree; + sh.st.var_local = save_varlocal; + } #if SHOPT_NAMESPACE + if(flags&NV_GLOBAL) sh.namespace = save_namespace; #endif - } } /* @@ -2283,7 +2290,10 @@ int nv_scan(Dt_t *root, void (*fn)(Namval_t*,void*), void *data,int mask, int fl */ void sh_scope(struct argnod *envlist, int fun) { - Dt_t *newscope, *newroot=sh.var_base; + /* TODO: This is a very limited approach for dynamic scoping. + * Implement a more flexible approach that allows for static scoping as well. + */ + Dt_t *newscope, *newroot = sh.st.var_local ? sh.st.var_local : sh.var_base; struct Ufunction *rp; #if SHOPT_NAMESPACE if(sh.namespace) diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index f3dc73b65b51..c245b682716a 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -1002,6 +1002,12 @@ int sh_exec(const Shnode_t *t, int flags) else if(checkopt(com,'a')) flgs |= NV_IARRAY; } + /* Prohibit usage of the local builtin outside of functions */ + if(np==SYSLOCAL && !sh.infunction) + { + errormsg(SH_DICT,ERROR_exit(1),"%s: can only be used in a function",com0); + UNREACHABLE(); + } if(np && funptr(np)==b_typeset) { /* command calls b_typeset(); treat as a typeset variant */ @@ -1011,6 +1017,18 @@ int sh_exec(const Shnode_t *t, int flags) sh.typeinit = np; tp = nv_type(np); } + /* + * The declare and local builtins have a dynamic scope limited + * to the function in which they are called. This should be + * skipped when not in a function. + */ + if(np == SYSLOCAL || (sh.infunction && np == SYSDECLARE)) + sh.st.var_local = sh.var_tree; + else + { + /* The other typeset builtins have a static scope */ + sh.st.var_local = sh.var_base; + } if(np==SYSCOMPOUND || checkopt(com,'C')) flgs |= NV_COMVAR; if(checkopt(com,'S')) @@ -2902,7 +2920,7 @@ pid_t sh_fork(int flags, int *jobid) /* * add exports from previous scope to the new scope */ -static void local_exports(Namval_t *np, void *data) +void local_exports(Namval_t *np, void *data) { Namval_t *mp; NOT_USED(data); @@ -2964,7 +2982,7 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) int isig,jmpval; volatile int r = 0; int n; - char save_invoc_local; + char save_invoc_local, infunction, posix_fun = 0; char **savsig, *save_debugtrap = 0; struct funenv *fp = 0; struct checkpt *buffp = (struct checkpt*)stkalloc(sh.stk,sizeof(struct checkpt)); @@ -2978,7 +2996,6 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) else sh.options = sh.glob_options; *prevscope = sh.st; - sh_offoption(SH_ERREXIT); sh.st.prevst = prevscope; sh.st.self = savst; sh.topscope = (Shscope_t*)sh.st.self; @@ -2990,7 +3007,16 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) fp = (struct funenv*)arg; sh.st.real_fun = (fp->node)->nvalue.rp; envlist = fp->env; + posix_fun = nv_isattr(fp->node,NV_FPOSIX); } + infunction = sh.infunction; + if(!posix_fun) + { + sh.infunction = 2; + sh_offoption(SH_ERREXIT); + } + else + sh.infunction = 1; prevscope->save_tree = sh.var_tree; n = dtvnext(prevscope->save_tree)!= (sh.namespace?sh.var_base:0); sh_scope(envlist,1); @@ -2999,50 +3025,59 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) /* eliminate parent scope */ nv_scan(prevscope->save_tree, local_exports, NULL, NV_EXPORT, NV_EXPORT|NV_NOSCOPE); } - sh.st.save_tree = sh.var_tree; + if(posix_fun) + /* TODO: Is this even necessary anymore? */ + sh.st.lineno = ((struct functnod*)nv_funtree(fp->node))->functline; + sh.st.save_tree = sh.st.var_local = sh.var_tree; if(!fun) { - if(sh_isoption(SH_FUNCTRACE) && is_option(&options,SH_XTRACE) || nv_isattr(fp->node,NV_TAGGED)) - sh_onoption(SH_XTRACE); - else - sh_offoption(SH_XTRACE); + if(fp->node->nvalue.rp) + sh.st.filename = fp->node->nvalue.rp->fname; + nv_putval(SH_PATHNAMENOD, sh.st.filename, NV_NOFREE); + sh.last_root = nv_dict(DOTSHNOD); } - sh.st.cmdname = argv[0]; - /* save trap table */ - if((nsig=sh.st.trapmax)>0 || sh.st.trapcom[0]) + if(!posix_fun) { - savsig = sh_malloc(nsig * sizeof(char*)); - /* - * the data is, usually, modified in code like: - * tmp = buf[i]; buf[i] = sh_strdup(tmp); free(tmp); - * so sh.st.trapcom needs a "deep copy" to properly save/restore pointers. - */ - for (isig = 0; isig < nsig; ++isig) + if(!fun) { - if(sh.st.trapcom[isig] == Empty) - savsig[isig] = Empty; - else if(sh.st.trapcom[isig]) - savsig[isig] = sh_strdup(sh.st.trapcom[isig]); + if(sh_isoption(SH_FUNCTRACE) && is_option(&options,SH_XTRACE) || nv_isattr(fp->node,NV_TAGGED)) + sh_onoption(SH_XTRACE); else - savsig[isig] = NULL; + sh_offoption(SH_XTRACE); } + sh.st.cmdname = argv[0]; + /* save trap table */ + if((nsig=sh.st.trapmax)>0 || sh.st.trapcom[0]) + { + savsig = sh_malloc(nsig * sizeof(char*)); + /* + * the data is, usually, modified in code like: + * tmp = buf[i]; buf[i] = sh_strdup(tmp); free(tmp); + * so sh.st.trapcom needs a "deep copy" to properly save/restore pointers. + */ + for (isig = 0; isig < nsig; ++isig) + { + if(sh.st.trapcom[isig] == Empty) + savsig[isig] = Empty; + else if(sh.st.trapcom[isig]) + savsig[isig] = sh_strdup(sh.st.trapcom[isig]); + else + savsig[isig] = NULL; + } + } + if(!fun && sh_isoption(SH_FUNCTRACE) && sh.st.trap[SH_DEBUGTRAP] && *sh.st.trap[SH_DEBUGTRAP]) + save_debugtrap = sh_strdup(sh.st.trap[SH_DEBUGTRAP]); + sh_sigreset(-1); + if(save_debugtrap) + sh.st.trap[SH_DEBUGTRAP] = save_debugtrap; } - if(!fun && sh_isoption(SH_FUNCTRACE) && sh.st.trap[SH_DEBUGTRAP] && *sh.st.trap[SH_DEBUGTRAP]) - save_debugtrap = sh_strdup(sh.st.trap[SH_DEBUGTRAP]); - sh_sigreset(-1); - if(save_debugtrap) - sh.st.trap[SH_DEBUGTRAP] = save_debugtrap; argsav = sh_argnew(argv,&saveargfor); sh_pushcontext(buffp,SH_JMPFUN); errorpush(&buffp->err,0); error_info.id = argv[0]; if(!fun) { - if(fp->node->nvalue.rp) - sh.st.filename = fp->node->nvalue.rp->fname; sh.st.funname = nv_name(fp->node); - sh.last_root = nv_dict(DOTSHNOD); - nv_putval(SH_PATHNAMENOD,sh.st.filename,NV_NOFREE); nv_putval(SH_FUNNAMENOD,sh.st.funname,NV_NOFREE); } save_invoc_local = sh.invoc_local; @@ -3084,7 +3119,8 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) sh.invoc_local = save_invoc_local; sh.fn_depth--; update_sh_level(); - if(sh.fn_depth==1 && jmpval==SH_JMPERRFN) + sh.infunction = infunction; + if(!posix_fun && sh.fn_depth==1 && jmpval==SH_JMPERRFN) { errormsg(SH_DICT,ERROR_exit(1),e_toodeep,argv[0]); UNREACHABLE(); @@ -3093,12 +3129,26 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) sh_unscope(); sh.namespace = nspace; sh.var_tree = (Dt_t*)prevscope->save_tree; - sh_argreset(argsav,saveargfor); - trap = sh.st.trapcom[0]; - sh.st.trapcom[0] = 0; - sh_sigreset(1); + if(!posix_fun || jmpval!=SH_JMPSCRIPT) + sh_argreset(argsav,saveargfor); + if(!posix_fun) + { + trap = sh.st.trapcom[0]; + sh.st.trapcom[0] = 0; + sh_sigreset(1); + } sh.st = *prevscope; sh.topscope = (Shscope_t*)prevscope; + sh.last_root = last_root; + if(posix_fun) + { + if(sh.st.self != &savst) + *sh.st.self = sh.st; + nv_putval(SH_PATHNAMENOD,sh.st.filename,NV_NOFREE); + if(jmpval && jmpval!=SH_JMPFUN) + siglongjmp(*sh.jmplist,jmpval); + return sh.exitval; + } nv_getval(sh_scoped(IFSNOD)); if(nsig) { @@ -3108,9 +3158,8 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) memcpy((char*)&sh.st.trapcom[0],savsig,nsig*sizeof(char*)); free(savsig); } - sh.trapnote=0; + sh.trapnote = 0; sh.options = options; - sh.last_root = last_root; if(jmpval == SH_JMPSUB) siglongjmp(*sh.jmplist,jmpval); if(trap) @@ -3133,8 +3182,9 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) static void sh_funct(Namval_t *np,int argn, char *argv[],struct argnod *envlist,int execflg) { struct funenv fun; - char *fname = nv_getval(SH_FUNNAMENOD); + char *fname = nv_getval(SH_FUNNAMENOD); pid_t pipepid = sh.pipepid; + int loopcnt; #if !SHOPT_DEVFD Dt_t *save_fifo_tree = sh.fifo_tree; sh.fifo_tree = NULL; @@ -3144,38 +3194,25 @@ static void sh_funct(Namval_t *np,int argn, char *argv[],struct argnod *envlist, if((struct sh_scoped*)sh.topscope != sh.st.self) sh_setscope(sh.topscope); sh.st.lineno = error_info.line; - np->nvalue.rp->running += 2; + np->nvalue.rp->running += 2; if(nv_isattr(np,NV_FPOSIX)) { - char *save; - int loopcnt = sh.st.loopcnt; - sh.posix_fun = np; - save = argv[-1]; - argv[-1] = 0; - sh.st.funname = nv_name(np); - sh.last_root = nv_dict(DOTSHNOD); - nv_putval(SH_FUNNAMENOD, nv_name(np),NV_NOFREE); - opt_info.index = opt_info.offset = 0; - error_info.errors = 0; + loopcnt = sh.st.loopcnt; sh.st.loopcnt = 0; - b_dot_cmd(argn+1,argv-1,&sh.bltindata); - sh.st.loopcnt = loopcnt; - argv[-1] = save; - } - else - { - fun.env = envlist; - fun.node = np; - fun.nref = 0; - sh_funscope(argn,argv,0,&fun,execflg); } + fun.env = envlist; + fun.node = np; + fun.nref = 0; + sh_funscope(argn,argv,0,&fun,execflg); + if(nv_isattr(np,NV_FPOSIX)) + sh.st.loopcnt = loopcnt; sh.last_root = nv_dict(DOTSHNOD); nv_putval(SH_FUNNAMENOD,fname,NV_NOFREE); nv_putval(SH_PATHNAMENOD,sh.st.filename,NV_NOFREE); sh.pipepid = pipepid; if(np->nvalue.rp) { - np->nvalue.rp->running -= 2; + np->nvalue.rp->running -= 2; if(np->nvalue.rp->running==1) { np->nvalue.rp->running = 0; diff --git a/src/cmd/ksh93/tests/builtins.sh b/src/cmd/ksh93/tests/builtins.sh index 7b616d663c1e..3d66dc110161 100755 --- a/src/cmd/ksh93/tests/builtins.sh +++ b/src/cmd/ksh93/tests/builtins.sh @@ -1212,7 +1212,7 @@ function test_usage actual=$({ PATH=${bltin%/*}; "${bltin##*/}" --this-option-does-not-exist; } 2>&1) ;; */*) err_exit "strange path name in 'builtin' output: $(printf %q "$bltin")" continue ;; - autoload | compound | float | functions | integer | nameref) + autoload | compound | float | functions | integer | nameref | local | declare) bltin=typeset ;& *) expect="Usage: $bltin " actual=$({ "${bltin}" --this-option-does-not-exist; } 2>&1) ;; diff --git a/src/cmd/ksh93/tests/local.sh b/src/cmd/ksh93/tests/local.sh new file mode 100755 index 000000000000..7ba6bcf59699 --- /dev/null +++ b/src/cmd/ksh93/tests/local.sh @@ -0,0 +1,227 @@ +######################################################################## +# # +# This file is part of the ksh 93u+m package # +# Copyright (c) 2020-2024 Contributors to ksh 93u+m # +# # +# and is licensed under the # +# Eclipse Public License, Version 2.0 # +# # +# A copy of the License is available at # +# https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html # +# (with md5 checksum 84283fa8859daf213bdda5a9f8d1be1d) # +# # +# Johnothan King # +# # +######################################################################## + +# These are regression tests for the local and declare builtins. +# In the cases when local could return an error, it's run using +# 'command' because it's a special builtin in ksh93v- and ksh2020. + +. "${SHTESTS_COMMON:-${0%/*}/_common}" + +# ====== +# This test must be run first due to the next test. +command local 2> /dev/null && err_exit "'local' works outside of functions" + +# local shouldn't suddenly work outside of functions after a function runs local. +posix_dummy() { command local > /dev/null; } +function ksh_dummy { command local > /dev/null; } +posix_dummy && command local 2> /dev/null && err_exit 'the local builtin works outside of functions after a POSIX function runs local' +ksh_dummy && command local 2> /dev/null && err_exit 'the local builtin works outside of functions after a KornShell function runs local' + +# ====== +for i in declare local; do + unset foo bar + # local should work inside both kinds of functions, without reliance on environment variables. + function ksh_function_nounset { + command $i foo=bar 2>&1 + } + function ksh_function_unset { + unset .sh.fun + command $i foo=bar 2>&1 + } + posix_function_nounset() { + command $i foo=bar 2>&1 + } + posix_function_unset() { + unset .sh.fun + command $i foo=bar 2>&1 + } + [[ $(ksh_function_nounset) ]] && err_exit "'$i' fails inside of KornShell functions" + [[ $(ksh_function_unset) ]] && err_exit "'$i' fails inside of KornShell functions when \${.sh.fun} is unset" + [[ $(posix_function_nounset) ]] && err_exit "'$i' fails inside of POSIX functions" + [[ $(posix_function_unset) ]] && err_exit "'$i' fails inside of POSIX functions when \${.sh.fun} is unset" + + # The local and declare builtins should have a dynamic scope + # Tests for the scope of POSIX functions + foo=globalscope + subfunc() { + [[ $foo == dynscope ]] + } + mainfunc() { + $i foo=dynscope + subfunc + } + mainfunc || err_exit "'$i' is not using a dynamic scope in POSIX functions" + [[ $foo == globalscope ]] || err_exit "'$i' changes variables outside of a POSIX function's scope" + + # Tests for the scope of KornShell functions + bar=globalscope + function subfunc_b { + [[ $bar == dynscope ]] + } + function mainfunc_b { + $i bar=dynscope + subfunc_b + } + mainfunc_b || err_exit "'$i' is not using a dynamic scope in KornShell functions" + [[ $bar == globalscope ]] || err_exit "'$i' changes variables outside of a KornShell function's scope" +done + +# The declare builtin should work outside of functions +unset foo +declare foo=bar +[[ $foo == bar ]] || err_exit "'declare' doesn't work outside of functions" + +# Test 1: make dynamic $bar static with typeset(1) in ksh function +tst=$tmp/tst.sh +cat > "$tst" << 'EOF' +function nxt { + echo $bar +} +function foo { + local bar=1 + nxt + local bar=2 + function infun { + echo $bar + } + typeset bar=BAD + infun +} +foo +echo $bar +EOF +exp=$'1\n2\n' +got=$("$SHELL" "$tst") +[[ $exp == "$got" ]] || err_exit "Cannot switch from dynamic scope to static scope in ksh functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + +# Test 2: make static $bar dynamic with local(1) in ksh function +cat > "$tst" << 'EOF' +function nxt { + echo $bar +} +function foo { + typeset bar=BAD + nxt + local bar=1 + function infun { + echo $bar + } + infun +} +foo +echo ${bar}2 +EOF +exp=$'\n1\n2' +got=$("$SHELL" "$tst") +[[ $exp == "$got" ]] || err_exit "Cannot switch from static scope to dynamic scope in ksh functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + +# Test 3: make dynamic $bar global with typeset(1) in POSIX function +tst=$tmp/tst.sh +cat > "$tst" << 'EOF' +nxt() { + echo $bar +} +foo() { + local bar=1 + nxt + typeset bar=2 + function infun { + echo $bar + } + infun + typeset bar=3 +} +foo +echo $bar +EOF +exp=$'1\n2\n3' +got=$("$SHELL" "$tst") +[[ $exp == "$got" ]] || err_exit "Cannot switch from dynamic scope to global scope in POSIX functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + +# Test 4: make local duplicate of global bar created with typeset in POSIX function +cat > "$tst" << 'EOF' +nxt() { + echo $bar +} +foo() { + typeset bar=1 + nxt + typeset bar=3 + local bar=2 + function infun { + echo $bar + } + infun +} +foo +echo $bar +EOF +exp=$'1\n2\n3' +got=$("$SHELL" "$tst") +[[ $exp == "$got" ]] || err_exit "Cannot create local version of \$bar in POSIX funtions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + +# Test 5: ensure local really works in POSIX functions +cat > "$tst" << 'EOF' +nxt() { + echo $bar +} +foo() { + local bar=1 + nxt + local bar=2 + function infun { + echo $bar + } + infun + local bar=BAD +} +foo +echo ${bar}3 +EOF +exp=$'1\n2\n3' +got=$("$SHELL" "$tst") +[[ $exp == "$got" ]] || err_exit "Cannot create local variables in POSIX funtions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + +# Test 6: ensure typeset doesn't default to making static variables in POSIX functions +cat > "$tst" << 'EOF' +nxt() { + echo $bar +} +foo() { + typeset bar=1 + nxt + typeset bar=2 + function infun { + echo $bar + } + infun + typeset bar=3 +} +foo +echo $bar +EOF +exp=$'1\n2\n3' +got=$("$SHELL" "$tst") +[[ $exp == "$got" ]] || err_exit "Cannot create global variables with plain typeset invocation in POSIX funtions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + +# ====== +exit $((Errors<125?Errors:125)) From 666cd88524cfe65e49830030ff5631ce1a8d4b05 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Mon, 1 Jan 2024 02:55:05 -0800 Subject: [PATCH 05/42] Fix builtins.sh failure by setting NV_GLOBAL in POSIX functions POSIX functions should default to placing variables on the global scope, so this should be perfectly fine. --- src/cmd/ksh93/bltins/typeset.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index 9de6e510f57f..7490013fc4b2 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -224,10 +224,11 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) const char *optstring = sh_opttypeset; Namdecl_t *ntp = (Namdecl_t*)context->ptr; Dt_t *troot; - int isfloat=0, isadjust=0, shortint=0, sflag=0, local; + int isfloat=0, isadjust=0, shortint=0, sflag=0, local, declare; memset(&tdata,0,sizeof(tdata)); troot = sh.var_tree; + declare = argv[0][0] == 'd'; local = argv[0][0] == 'l'; if(ntp) /* custom declaration command added using enum */ { @@ -235,7 +236,7 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) opt_info.disc = (Optdisc_t*)ntp->optinfof; optstring = ntp->optstring; } - else if(argv[0][0] != 't' && argv[0][0] != 'd' && !local) /* not ypeset, eclare or ocal */ + else if(argv[0][0] != 't' && !declare && !local) /* not ypeset, eclare or ocal */ { char **new_argv = (char **)stkalloc(sh.stk, (argc + 2) * sizeof(char*)); error_info.id = new_argv[0] = SYSTYPESET->nvname; @@ -454,6 +455,8 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) errormsg(SH_DICT,ERROR_exit(1), "can only be used in a function"); UNREACHABLE(); } + else if(sh.infunction==1 && !(local || declare) && !sh.mktype) + flag |= NV_GLOBAL; /* handle argument of + and - specially */ if(*argv && argv[0][1]==0 && (*argv[0]=='+' || *argv[0]=='-')) tdata.aflag = *argv[0]; From c9a2289f50e173915c00fad39a83d22818d8f79e Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Mon, 1 Jan 2024 03:08:27 -0800 Subject: [PATCH 06/42] Revert leftover changes from failed b_dot_cmd experiment --- src/cmd/ksh93/bltins/misc.c | 17 +++++++++-------- src/cmd/ksh93/include/defs.h | 1 - src/cmd/ksh93/sh/xec.c | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/cmd/ksh93/bltins/misc.c b/src/cmd/ksh93/bltins/misc.c index a7fba022e333..83c90f9204a4 100644 --- a/src/cmd/ksh93/bltins/misc.c +++ b/src/cmd/ksh93/bltins/misc.c @@ -220,7 +220,8 @@ int b_dot_cmd(int n,char *argv[],Shbltin_t *context) int jmpval; struct sh_scoped savst, *prevscope = sh.st.self; char *filename=0, *buffer=0, *tofree; - int fd, infunction=-1; + int fd; + char infunction = -1; struct dolnod *saveargfor; volatile struct dolnod *argsave=0; struct checkpt buff; @@ -277,23 +278,23 @@ int b_dot_cmd(int n,char *argv[],Shbltin_t *context) } filename = path_fullname(stkptr(sh.stk,PATH_OFFSET)); } - *prevscope = sh.st; - sh.st.prevst = prevscope; - sh.st.self = &savst; - sh.topscope = (Shscope_t*)sh.st.self; - prevscope->save_tree = sh.var_tree; - if(np) + else { infunction = sh.infunction; sh.infunction = 1; } + *prevscope = sh.st; sh.st.lineno = np?((struct functnod*)nv_funtree(np))->functline:1; - sh.st.save_tree = sh.st.var_local = sh.var_tree; + sh.st.save_tree = sh.var_tree; if(filename) { sh.st.filename = filename; sh.st.lineno = 1; } + sh.st.prevst = prevscope; + sh.st.self = &savst; + sh.topscope = (Shscope_t*)sh.st.self; + prevscope->save_tree = sh.var_tree; tofree = sh.st.filename; if(np) sh.st.filename = np->nvalue.rp->fname; diff --git a/src/cmd/ksh93/include/defs.h b/src/cmd/ksh93/include/defs.h index 39fef51acd1b..0dede50a0be5 100644 --- a/src/cmd/ksh93/include/defs.h +++ b/src/cmd/ksh93/include/defs.h @@ -128,7 +128,6 @@ extern int sh_macfun(const char*,int); extern void sh_machere(Sfio_t*, Sfio_t*, char*); extern void *sh_macopen(void); extern char *sh_macpat(struct argnod*,int); -extern void local_exports(Namval_t*, void*); extern Sfdouble_t sh_mathfun(void*, int, Sfdouble_t*); extern int sh_outtype(Sfio_t*); extern char *sh_mactry(char*); diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index c245b682716a..da3bbb648e7d 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -2920,7 +2920,7 @@ pid_t sh_fork(int flags, int *jobid) /* * add exports from previous scope to the new scope */ -void local_exports(Namval_t *np, void *data) +static void local_exports(Namval_t *np, void *data) { Namval_t *mp; NOT_USED(data); From eba8be8af6ada836fa4fff73ba4c9b1ac0dc128f Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Mon, 1 Jan 2024 03:55:12 -0800 Subject: [PATCH 07/42] Experimental fix for the functions.sh test failure --- src/cmd/ksh93/sh/name.c | 9 ++++- src/cmd/ksh93/sh/xec.c | 9 +++-- src/cmd/ksh93/tests/local.sh | 70 ++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/src/cmd/ksh93/sh/name.c b/src/cmd/ksh93/sh/name.c index a6e14ee63e5b..f84154b2f389 100644 --- a/src/cmd/ksh93/sh/name.c +++ b/src/cmd/ksh93/sh/name.c @@ -2304,7 +2304,14 @@ void sh_scope(struct argnod *envlist, int fun) { dtview(newscope,(Dt_t*)sh.var_tree); sh.var_tree = newscope; - nv_setlist(envlist,NV_EXPORT|NV_NOSCOPE|NV_IDENT|NV_ASSIGN,0); + if(fun==2) + /* TODO: See if this really is the only way to fix the functions.sh + * test failure. This part of the code is surprisingly fragile, + * so if possible I'd rather not change anything here at all. + */ + nv_setlist(envlist,NV_NOSCOPE|NV_IDENT|NV_ASSIGN,0); + else + nv_setlist(envlist,NV_EXPORT|NV_NOSCOPE|NV_IDENT|NV_ASSIGN,0); if(!fun) return; sh.var_tree = dtview(newscope,0); diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index da3bbb648e7d..07a17e129438 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -3018,8 +3018,11 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) else sh.infunction = 1; prevscope->save_tree = sh.var_tree; - n = dtvnext(prevscope->save_tree)!= (sh.namespace?sh.var_base:0); - sh_scope(envlist,1); + n = dtvnext(prevscope->save_tree) != (sh.namespace?sh.var_base:0); + if(posix_fun) + sh_scope(envlist,2); + else + sh_scope(envlist,1); if(n) { /* eliminate parent scope */ @@ -3093,7 +3096,7 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) sh.fn_depth++; update_sh_level(); if(fun) - r= (*fun)(arg); + r = (*fun)(arg); else { char **arg = sh.st.real_fun->argv; diff --git a/src/cmd/ksh93/tests/local.sh b/src/cmd/ksh93/tests/local.sh index 7ba6bcf59699..9193a140e378 100755 --- a/src/cmd/ksh93/tests/local.sh +++ b/src/cmd/ksh93/tests/local.sh @@ -223,5 +223,75 @@ got=$("$SHELL" "$tst") [[ $exp == "$got" ]] || err_exit "Cannot create global variables with plain typeset invocation in POSIX funtions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" +# Test 7 +cat > "$tst" << 'EOF' +nxt() { + echo $bar +} +foo() { + typeset bar=1 + nxt + typeset bar=2 + infun() { + echo $bar + } + infun + typeset bar=3 +} +foo +echo $bar +EOF +exp=$'1\n2\n3' +got=$("$SHELL" "$tst") +[[ $exp == "$got" ]] || err_exit "Cannot create global variables with plain typeset invocation in POSIX funtions with nested POSIX functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + +# Test 8 +cat > "$tst" << 'EOF' +nxt() { + echo $bar +} +foo() { + local bar=1 + nxt + local bar=2 + infun() { + echo $bar + } + infun + local bar=BAD +} +foo +echo ${bar}3 +EOF +exp=$'1\n2\n3' +got=$("$SHELL" "$tst") +[[ $exp == "$got" ]] || err_exit "Cannot create local variables with local builtin in POSIX funtions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + +# Test 9 +cat > "$tst" << 'EOF' +nxt() { + echo $bar +} +foo() { + bar=1 + nxt + bar=2 + function infun { + echo $bar + } + infun + bar=3 +} +foo +echo $bar +EOF +exp=$'1\n2\n3' +got=$("$SHELL" "$tst") +[[ $exp == "$got" ]] || err_exit "Cannot create global variables in POSIX funtions without direct typeset invocation" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + + # ====== exit $((Errors<125?Errors:125)) From 3493f2d7aa48e3d7596a5335f4145ec560223137 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Mon, 1 Jan 2024 04:06:46 -0800 Subject: [PATCH 08/42] More tests --- src/cmd/ksh93/tests/local.sh | 47 ++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/cmd/ksh93/tests/local.sh b/src/cmd/ksh93/tests/local.sh index 9193a140e378..e8ec1bda0010 100755 --- a/src/cmd/ksh93/tests/local.sh +++ b/src/cmd/ksh93/tests/local.sh @@ -292,6 +292,53 @@ got=$("$SHELL" "$tst") [[ $exp == "$got" ]] || err_exit "Cannot create global variables in POSIX funtions without direct typeset invocation" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" +# Test 10: Make static variable global in KornShell function +tst=$tmp/tst.sh +cat > "$tst" << 'EOF' +function nxt { + echo ${bar}1 +} +function foo { + typeset bar=BAD + nxt + typeset -g bar=2 + function infun { + echo $bar + } + infun + typeset -g bar=3 +} +foo +echo $bar +EOF +exp=$'1\n2\n3' +got=$("$SHELL" "$tst") +[[ $exp == "$got" ]] || err_exit "Cannot switch from static scope to global scope in KornShell functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + +# Test 11: Make dynamic variables global in KornShell functions +tst=$tmp/tst.sh +cat > "$tst" << 'EOF' +function nxt { + echo ${bar} +} +function foo { + local bar=1 + nxt + local -g bar=2 + function infun { + echo $bar + } + infun + typeset -g bar=3 +} +foo +echo $bar +EOF +exp=$'1\n2\n3' +got=$("$SHELL" "$tst") +[[ $exp == "$got" ]] || err_exit "Cannot switch from dynamic scope to global scope in KornShell functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # ====== exit $((Errors<125?Errors:125)) From fa23bd46a0fb703ed22cb98f2bfc1717c8cb7a0b Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Mon, 1 Jan 2024 04:30:15 -0800 Subject: [PATCH 09/42] Add another safety check --- src/cmd/ksh93/bltins/typeset.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index 7490013fc4b2..2f8949cdb7d4 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -455,7 +455,7 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) errormsg(SH_DICT,ERROR_exit(1), "can only be used in a function"); UNREACHABLE(); } - else if(sh.infunction==1 && !(local || declare) && !sh.mktype) + else if(sh.infunction==1 && !(local || declare) && !sh.mktype && troot==sh.var_tree) flag |= NV_GLOBAL; /* handle argument of + and - specially */ if(*argv && argv[0][1]==0 && (*argv[0]=='+' || *argv[0]=='-')) From 10f9ad177b527e29380b64e68174b0f8132d32a9 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Mon, 1 Jan 2024 06:27:49 -0800 Subject: [PATCH 10/42] Add NV_DYNAMIC to open the door for flexible behavior --- src/cmd/ksh93/bltins/typeset.c | 36 ++++++++++++++++++++++++++-------- src/cmd/ksh93/data/builtins.c | 1 + src/cmd/ksh93/include/nval.h | 1 + src/cmd/ksh93/sh/name.c | 17 +++++++++++++--- src/cmd/ksh93/sh/xec.c | 17 +++++++++------- src/cmd/ksh93/tests/local.sh | 12 ++++++------ 6 files changed, 60 insertions(+), 24 deletions(-) diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index 2f8949cdb7d4..d56c19bf2bc8 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -435,8 +435,13 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) flag |= (NV_EXPORT|NV_IDENT); break; case 'g': + flag &= ~NV_DYNAMIC; flag |= NV_GLOBAL; break; + case 'D': + flag &= ~NV_GLOBAL; + flag |= NV_DYNAMIC; + break; case ':': errormsg(SH_DICT,2, "%s", opt_info.arg); break; @@ -455,8 +460,13 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) errormsg(SH_DICT,ERROR_exit(1), "can only be used in a function"); UNREACHABLE(); } - else if(sh.infunction==1 && !(local || declare) && !sh.mktype && troot==sh.var_tree) - flag |= NV_GLOBAL; + if(troot == sh.var_tree && !sh.mktype) + { + if(sh.infunction==1 && !(local || declare) && !(flag&NV_DYNAMIC)) + flag |= NV_GLOBAL; + else if(sh.infunction && (local || declare) && !(flag&NV_GLOBAL)) + flag |= NV_DYNAMIC; + } /* handle argument of + and - specially */ if(*argv && argv[0][1]==0 && (*argv[0]=='+' || *argv[0]=='-')) tdata.aflag = *argv[0]; @@ -479,9 +489,9 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) errormsg(SH_DICT,2,e_optincompat1,"-m"); error_info.errors++; } - if((flag&NV_REF) && (flag&~(NV_REF|NV_IDENT|NV_ASSIGN|NV_GLOBAL))) + if((flag&NV_REF) && (flag&~(NV_REF|NV_IDENT|NV_ASSIGN|NV_GLOBAL|NV_DYNAMIC))) { - errormsg(SH_DICT,2,e_optincompat2,"-n","other options except -g"); + errormsg(SH_DICT,2,e_optincompat2,"-n","other options except -g and -D"); error_info.errors++; } if((flag&NV_TYPE) && (flag&~(NV_TYPE|NV_VARNAME|NV_ASSIGN))) @@ -510,9 +520,10 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) errormsg(SH_DICT,ERROR_exit(2),"option argument cannot be greater than %d",SHRT_MAX); UNREACHABLE(); } - if((flag&NV_GLOBAL) && sh.mktype) + if((flag&(NV_GLOBAL|NV_DYNAMIC)) && sh.mktype) { - errormsg(SH_DICT,ERROR_exit(2),"-g: type members cannot be global"); + /* TODO: Improve unfinished error message */ + errormsg(SH_DICT,ERROR_exit(2),"type members cannot be global or dynamic"); UNREACHABLE(); } if(isfloat) @@ -662,12 +673,16 @@ static int setall(char **argv,int flag,Dt_t *troot,struct tdata *tp) int r=0, ref=0, comvar=(flag&NV_COMVAR),iarray=(flag&NV_IARRAY); Dt_t *save_vartree, *save_varlocal = 0; Namval_t *save_namespace; - if((flag&NV_GLOBAL) || (sh.infunction==1 && sh.st.var_local == sh.var_base)) + if(flag&NV_GLOBAL) { save_vartree = sh.var_tree; save_varlocal = sh.st.var_local; troot = sh.var_tree = sh.st.var_local = sh.var_base; } + /*else if(flag&NV_DYNAMIC) + sh.st.var_local = sh.var_tree; + else + sh.st.var_local = sh.var_base;*/ #if SHOPT_NAMESPACE if(flag&NV_GLOBAL) { @@ -1048,11 +1063,16 @@ static int setall(char **argv,int flag,Dt_t *troot,struct tdata *tp) if(r==0) r = 1; /* ensure the exit status is at least 1 */ } - if((flag&NV_GLOBAL) || (sh.infunction==1 && save_varlocal)) + if(flag&NV_GLOBAL) { sh.var_tree = save_vartree; sh.st.var_local = save_varlocal; } + /*else if(flag&NV_DYNAMIC) + { + sh.var_tree = save_vartree; + sh.st.var_local = save_varlocal; + }*/ #if SHOPT_NAMESPACE if(flag&NV_GLOBAL) sh.namespace = save_namespace; diff --git a/src/cmd/ksh93/data/builtins.c b/src/cmd/ksh93/data/builtins.c index caa2275c4d73..68a11931f8c7 100644 --- a/src/cmd/ksh93/data/builtins.c +++ b/src/cmd/ksh93/data/builtins.c @@ -1969,6 +1969,7 @@ const char sh_opttypeset[] = "unset prior to processing the assignment list.]" "[T]:?[tname?\atname\a is the name of a type name given to each \aname\a.]" "[Z]#?[n?Zero fill. If \an\a is given it represents the field width.]" +"[D?Dynamic option test.]" "\n" "\n[name[=value]...]\n" " -f [-tu] [name...]\n" diff --git a/src/cmd/ksh93/include/nval.h b/src/cmd/ksh93/include/nval.h index adef2ae92e86..a908225c32ab 100644 --- a/src/cmd/ksh93/include/nval.h +++ b/src/cmd/ksh93/include/nval.h @@ -183,6 +183,7 @@ struct Namval #define NV_NODISC NV_IDENT /* ignore disciplines */ #define NV_UNATTR 0x800000 /* unset attributes before assignment */ #define NV_GLOBAL 0x20000000 /* create global variable, ignoring local scope */ +#define NV_DYNAMIC 0x40000000 /* create dynamically scoped variable */ #define NV_FUNCT NV_IDENT /* option for nv_create */ #define NV_BLTINOPT NV_ZFILL /* mark builtins in libcmd */ diff --git a/src/cmd/ksh93/sh/name.c b/src/cmd/ksh93/sh/name.c index f84154b2f389..168a39839a2e 100644 --- a/src/cmd/ksh93/sh/name.c +++ b/src/cmd/ksh93/sh/name.c @@ -237,12 +237,16 @@ void nv_setlist(struct argnod *arg,int flags, Namval_t *typ) #if SHOPT_NAMESPACE Namval_t *save_namespace; #endif - if((flags&NV_GLOBAL) || (sh.infunction==1 && sh.st.var_local == sh.var_base)) + if(flags&NV_GLOBAL) { save_vartree = sh.var_tree; save_varlocal = sh.st.var_local; sh.var_tree = sh.st.var_local = sh.var_base; } + /*else if(flags&NV_DYNAMIC) + sh.st.var_local = sh.var_tree; + else + sh.st.var_local = sh.var_base;*/ #if SHOPT_NAMESPACE if(flags&NV_GLOBAL) { @@ -662,11 +666,16 @@ void nv_setlist(struct argnod *arg,int flags, Namval_t *typ) } /* continue loop */ } - if((flags&NV_GLOBAL) || (sh.infunction==1 && save_varlocal)) + if(flags&NV_GLOBAL) { sh.var_tree = save_vartree; sh.st.var_local = save_varlocal; } + /*else if(flags&NV_DYNAMIC) + { + sh.var_tree = save_vartree; + sh.st.var_local = save_varlocal; + }*/ #if SHOPT_NAMESPACE if(flags&NV_GLOBAL) sh.namespace = save_namespace; @@ -2293,12 +2302,14 @@ void sh_scope(struct argnod *envlist, int fun) /* TODO: This is a very limited approach for dynamic scoping. * Implement a more flexible approach that allows for static scoping as well. */ - Dt_t *newscope, *newroot = sh.st.var_local ? sh.st.var_local : sh.var_base; + Dt_t *newscope, *newroot; struct Ufunction *rp; #if SHOPT_NAMESPACE if(sh.namespace) newroot = nv_dict(sh.namespace); + else #endif /* SHOPT_NAMESPACE */ + newroot = sh.st.var_local ? sh.st.var_local : sh.var_base; newscope = dtopen(&_Nvdisc,Dtoset); if(envlist) { diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index 07a17e129438..8f423b325ad0 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -1022,13 +1022,6 @@ int sh_exec(const Shnode_t *t, int flags) * to the function in which they are called. This should be * skipped when not in a function. */ - if(np == SYSLOCAL || (sh.infunction && np == SYSDECLARE)) - sh.st.var_local = sh.var_tree; - else - { - /* The other typeset builtins have a static scope */ - sh.st.var_local = sh.var_base; - } if(np==SYSCOMPOUND || checkopt(com,'C')) flgs |= NV_COMVAR; if(checkopt(com,'S')) @@ -1037,6 +1030,8 @@ int sh_exec(const Shnode_t *t, int flags) flgs |= NV_MOVE; if(checkopt(com,'g')) flgs |= NV_GLOBAL; + if(checkopt(com,'D')) + flgs |= NV_DYNAMIC; if(np==SYSNAMEREF || checkopt(com,'n')) flgs |= NV_NOREF; else if(argn>=3 && checkopt(com,'T')) @@ -1056,6 +1051,14 @@ int sh_exec(const Shnode_t *t, int flags) sh.prefix = NV_CLASS; flgs |= NV_TYPE; } + if((np==SYSLOCAL || np==SYSDECLARE) && !(flgs&NV_GLOBAL)) + flgs |= NV_DYNAMIC; + else if(sh.infunction==1 && !(flgs&NV_DYNAMIC)) + flgs |= NV_GLOBAL; + if(flgs&NV_DYNAMIC) + sh.st.var_local = sh.var_tree; + else + sh.st.var_local = sh.var_base; if(sh.fn_depth && !sh.prefix) flgs |= NV_NOSCOPE; } diff --git a/src/cmd/ksh93/tests/local.sh b/src/cmd/ksh93/tests/local.sh index e8ec1bda0010..9a76c6ae164a 100755 --- a/src/cmd/ksh93/tests/local.sh +++ b/src/cmd/ksh93/tests/local.sh @@ -174,7 +174,7 @@ echo $bar EOF exp=$'1\n2\n3' got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "Cannot create local version of \$bar in POSIX funtions" \ +[[ $exp == "$got" ]] || err_exit "Cannot create local version of \$bar in POSIX functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Test 5: ensure local really works in POSIX functions @@ -197,7 +197,7 @@ echo ${bar}3 EOF exp=$'1\n2\n3' got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "Cannot create local variables in POSIX funtions" \ +[[ $exp == "$got" ]] || err_exit "Cannot create local variables in POSIX functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Test 6: ensure typeset doesn't default to making static variables in POSIX functions @@ -220,7 +220,7 @@ echo $bar EOF exp=$'1\n2\n3' got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "Cannot create global variables with plain typeset invocation in POSIX funtions" \ +[[ $exp == "$got" ]] || err_exit "Cannot create global variables with plain typeset invocation in POSIX functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Test 7 @@ -243,7 +243,7 @@ echo $bar EOF exp=$'1\n2\n3' got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "Cannot create global variables with plain typeset invocation in POSIX funtions with nested POSIX functions" \ +[[ $exp == "$got" ]] || err_exit "Cannot create global variables with plain typeset invocation in POSIX functions with nested POSIX functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Test 8 @@ -266,7 +266,7 @@ echo ${bar}3 EOF exp=$'1\n2\n3' got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "Cannot create local variables with local builtin in POSIX funtions" \ +[[ $exp == "$got" ]] || err_exit "Cannot create local variables with local builtin in POSIX functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Test 9 @@ -289,7 +289,7 @@ echo $bar EOF exp=$'1\n2\n3' got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "Cannot create global variables in POSIX funtions without direct typeset invocation" \ +[[ $exp == "$got" ]] || err_exit "Cannot create global variables in POSIX functions without direct typeset invocation" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Test 10: Make static variable global in KornShell function From dc7841d1f6a727692cf80b7c6480aab54191d8b4 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Mon, 1 Jan 2024 07:15:03 -0800 Subject: [PATCH 11/42] Avoid possible bugs relating to declare/local -T --- src/cmd/ksh93/bltins/typeset.c | 14 +++++++------- src/cmd/ksh93/tests/types.sh | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index d56c19bf2bc8..493bcb411db5 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -460,13 +460,6 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) errormsg(SH_DICT,ERROR_exit(1), "can only be used in a function"); UNREACHABLE(); } - if(troot == sh.var_tree && !sh.mktype) - { - if(sh.infunction==1 && !(local || declare) && !(flag&NV_DYNAMIC)) - flag |= NV_GLOBAL; - else if(sh.infunction && (local || declare) && !(flag&NV_GLOBAL)) - flag |= NV_DYNAMIC; - } /* handle argument of + and - specially */ if(*argv && argv[0][1]==0 && (*argv[0]=='+' || *argv[0]=='-')) tdata.aflag = *argv[0]; @@ -526,6 +519,13 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) errormsg(SH_DICT,ERROR_exit(2),"type members cannot be global or dynamic"); UNREACHABLE(); } + if(troot == sh.var_tree && !sh.mktype) + { + if(sh.infunction==1 && !(local || declare) && !(flag&NV_DYNAMIC)) + flag |= NV_GLOBAL; + else if(sh.infunction && (local || declare) && !(flag&NV_GLOBAL)) + flag |= NV_DYNAMIC; + } if(isfloat) flag |= NV_DOUBLE; if(sflag) diff --git a/src/cmd/ksh93/tests/types.sh b/src/cmd/ksh93/tests/types.sh index 97f1f63d16ca..83cf2b96e6be 100755 --- a/src/cmd/ksh93/tests/types.sh +++ b/src/cmd/ksh93/tests/types.sh @@ -795,5 +795,37 @@ e=$? let "e==0" || err_exit 'crash involving short int as first type member' \ "(got status $e$( ((e>128)) && print -n /SIG && kill -l "$e"), $(printf %q "$got"))" +# -T must work with typeset, local and declare +got=$({ "$SHELL" -c ' + typeset -T Coord_t=( + typeset -i x + ) + declare -T Job_t=( + typeset -si BUGTRIGGER + Coord_t pos + set_pos() { _.pos.x=0 ;} + ) + Job_t job + job.set_pos + job.set_pos + posix_fun() { + local -T Bar_t=( + declare -li val + declare -si var + typeset string + ) + Bar_t mrbar + mrbar.val=34 + mrbar.var=34 + mrbar.string=teststr + [[ ${mrbar.string} == teststr ]] + ((mrbar.var == mrbar.val)) + } + posix_fun +'; } 2>&1) +e=$? +let "e==0" || err_exit 'cannot use -T flag with typeset, local and/or declare reliably' \ + "(got status $e$( ((e>128)) && print -n /SIG && kill -l "$e"), $(printf %q "$got"))" + # ====== exit $((Errors<125?Errors:125)) From c91998b5453c4f6bc9651e9e334cd60289ad7ea5 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Mon, 1 Jan 2024 08:02:07 -0800 Subject: [PATCH 12/42] FINALLY figured out how to move that scoping code away from xec.c --- src/cmd/ksh93/bltins/typeset.c | 4 ++-- src/cmd/ksh93/sh/name.c | 4 ++-- src/cmd/ksh93/sh/xec.c | 4 ---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index 493bcb411db5..76dfdb277c85 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -679,10 +679,10 @@ static int setall(char **argv,int flag,Dt_t *troot,struct tdata *tp) save_varlocal = sh.st.var_local; troot = sh.var_tree = sh.st.var_local = sh.var_base; } - /*else if(flag&NV_DYNAMIC) + else if(flag&NV_DYNAMIC) sh.st.var_local = sh.var_tree; else - sh.st.var_local = sh.var_base;*/ + sh.st.var_local = sh.var_base; #if SHOPT_NAMESPACE if(flag&NV_GLOBAL) { diff --git a/src/cmd/ksh93/sh/name.c b/src/cmd/ksh93/sh/name.c index 168a39839a2e..2323ccfe27c0 100644 --- a/src/cmd/ksh93/sh/name.c +++ b/src/cmd/ksh93/sh/name.c @@ -243,10 +243,10 @@ void nv_setlist(struct argnod *arg,int flags, Namval_t *typ) save_varlocal = sh.st.var_local; sh.var_tree = sh.st.var_local = sh.var_base; } - /*else if(flags&NV_DYNAMIC) + else if(flags&NV_DYNAMIC) sh.st.var_local = sh.var_tree; else - sh.st.var_local = sh.var_base;*/ + sh.st.var_local = sh.var_base; #if SHOPT_NAMESPACE if(flags&NV_GLOBAL) { diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index 8f423b325ad0..6f5da2fa151e 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -1055,10 +1055,6 @@ int sh_exec(const Shnode_t *t, int flags) flgs |= NV_DYNAMIC; else if(sh.infunction==1 && !(flgs&NV_DYNAMIC)) flgs |= NV_GLOBAL; - if(flgs&NV_DYNAMIC) - sh.st.var_local = sh.var_tree; - else - sh.st.var_local = sh.var_base; if(sh.fn_depth && !sh.prefix) flgs |= NV_NOSCOPE; } From bffb6b8c3a2afe56b2892663bec00a6c327b8c0f Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Mon, 1 Jan 2024 08:28:20 -0800 Subject: [PATCH 13/42] Fix oversights in the new regression tests --- src/cmd/ksh93/tests/local.sh | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/cmd/ksh93/tests/local.sh b/src/cmd/ksh93/tests/local.sh index 9a76c6ae164a..e40bea6c8151 100755 --- a/src/cmd/ksh93/tests/local.sh +++ b/src/cmd/ksh93/tests/local.sh @@ -85,6 +85,8 @@ declare foo=bar [[ $foo == bar ]] || err_exit "'declare' doesn't work outside of functions" # Test 1: make dynamic $bar static with typeset(1) in ksh function +# TODO: This doesn't work, likely because the static scope is effectively lost +# after a dynamic scope is made. That will need to be fixed. tst=$tmp/tst.sh cat > "$tst" << 'EOF' function nxt { @@ -99,11 +101,12 @@ function foo { } typeset bar=BAD infun + nxt } foo echo $bar EOF -exp=$'1\n2\n' +exp=$'1\n2\n2' got=$("$SHELL" "$tst") [[ $exp == "$got" ]] || err_exit "Cannot switch from dynamic scope to static scope in ksh functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" @@ -139,12 +142,12 @@ nxt() { foo() { local bar=1 nxt - typeset bar=2 + typeset bar=3 + declare bar=2 function infun { echo $bar } infun - typeset bar=3 } foo echo $bar @@ -325,14 +328,18 @@ function nxt { function foo { local bar=1 nxt - local -g bar=2 + local -g bar=3 + local bar=2 function infun { + # The dynamic scope still applies, so the $bar value + # from 'function foo' is inherited and used instead + # of the global value. echo $bar } infun - typeset -g bar=3 } foo +# This will be '3' because of the earlier 'local -g' echo $bar EOF exp=$'1\n2\n3' From 33a6f17ed3e464b8c4e1a9be2ef4eeab97057797 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Mon, 1 Jan 2024 10:08:37 -0800 Subject: [PATCH 14/42] Add more (failing) tests I suspect that the only reason dynamic scoping even somewhat works is not because an actual scope was opened, but rather because the child functions use the parent function's scope outright. This is no good and will need a rewrite. --- src/cmd/ksh93/tests/local.sh | 96 ++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/src/cmd/ksh93/tests/local.sh b/src/cmd/ksh93/tests/local.sh index e40bea6c8151..3041895188b1 100755 --- a/src/cmd/ksh93/tests/local.sh +++ b/src/cmd/ksh93/tests/local.sh @@ -347,5 +347,101 @@ got=$("$SHELL" "$tst") [[ $exp == "$got" ]] || err_exit "Cannot switch from dynamic scope to global scope in KornShell functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" +# Test 12: Variables shouldn't leak out of nested POSIX functions +tst=$tmp/tst.sh +cat > "$tst" << 'EOF' +foo() { + local foo=foo + bar() { + local foo=bar + baz() { + local foo=baz + echo $foo + } + baz + echo $foo + } + bar + echo $foo +} +foo +EOF +exp=$'baz\nbar\nfoo' +got=$("$SHELL" "$tst") +[[ $exp == "$got" ]] || err_exit "Local variables from nested POSIX functions leak out into the parent functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + +# Test 13: Variables shouldn't leak out of nested KornShell functions +tst=$tmp/tst.sh +cat > "$tst" << 'EOF' +function foo { + local foo=foo + function bar { + local foo=bar + function baz { + local foo=baz + echo $foo + } + baz + echo $foo + } + bar + echo $foo +} +foo +EOF +exp=$'baz\nbar\nfoo' +got=$("$SHELL" "$tst") +[[ $exp == "$got" ]] || err_exit "Local variables from nested KornShell functions leak out into the parent functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + +# Test 14: Variables shouldn't leak out into other POSIX functions +tst=$tmp/tst.sh +cat > "$tst" << 'EOF' +baz() { + local foo=baz + echo $foo +} +bar() { + local foo=bar + baz + echo $foo +} +foo() { + local foo=foo + bar + echo $foo +} +foo +EOF +exp=$'baz\nbar\nfoo' +got=$("$SHELL" "$tst") +[[ $exp == "$got" ]] || err_exit "Local variables from POSIX functions leak out into other functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + +# Test 15: Variables shouldn't leak out into other KornShell functions +tst=$tmp/tst.sh +cat > "$tst" << 'EOF' +function baz { + local foo=baz + echo $foo +} +function bar { + local foo=bar + baz + echo $foo +} +function foo { + local foo=foo + bar + echo $foo +} +foo +EOF +exp=$'baz\nbar\nfoo' +got=$("$SHELL" "$tst") +[[ $exp == "$got" ]] || err_exit "Local variables from KornShell functions leak out into other functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + # ====== exit $((Errors<125?Errors:125)) From 88d77a524d535eee7365b2bf88ec35db07053ae6 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Mon, 15 Jan 2024 15:06:56 -0800 Subject: [PATCH 15/42] Abandon unsalvageable ksh93v- scoping code and rewrite scoping See the lengthy comment in xec.c for a detailed explanation. --- src/cmd/ksh93/bltins/typeset.c | 19 +++++------------- src/cmd/ksh93/data/builtins.c | 3 +-- src/cmd/ksh93/include/shell.h | 1 - src/cmd/ksh93/sh/name.c | 17 ++++------------ src/cmd/ksh93/sh/xec.c | 22 +++++++++++++++++++-- src/cmd/ksh93/tests/local.sh | 36 ++++++++++++++++++++++------------ 6 files changed, 54 insertions(+), 44 deletions(-) diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index 76dfdb277c85..553798764a2b 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -519,11 +519,11 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) errormsg(SH_DICT,ERROR_exit(2),"type members cannot be global or dynamic"); UNREACHABLE(); } - if(troot == sh.var_tree && !sh.mktype) + if(troot==sh.var_tree && !sh.mktype && sh.infunction) { if(sh.infunction==1 && !(local || declare) && !(flag&NV_DYNAMIC)) flag |= NV_GLOBAL; - else if(sh.infunction && (local || declare) && !(flag&NV_GLOBAL)) + else if((local || declare) && !(flag&NV_GLOBAL)) flag |= NV_DYNAMIC; } if(isfloat) @@ -671,18 +671,15 @@ static int setall(char **argv,int flag,Dt_t *troot,struct tdata *tp) char *last = 0; int nvflags=(flag&(NV_ARRAY|NV_NOARRAY|NV_VARNAME|NV_IDENT|NV_ASSIGN|NV_STATIC|NV_MOVE)); int r=0, ref=0, comvar=(flag&NV_COMVAR),iarray=(flag&NV_IARRAY); - Dt_t *save_vartree, *save_varlocal = 0; + Dt_t *save_vartree; Namval_t *save_namespace; if(flag&NV_GLOBAL) { save_vartree = sh.var_tree; - save_varlocal = sh.st.var_local; - troot = sh.var_tree = sh.st.var_local = sh.var_base; + troot = sh.var_tree = sh.var_base; } else if(flag&NV_DYNAMIC) - sh.st.var_local = sh.var_tree; - else - sh.st.var_local = sh.var_base; + nvflags |= NV_TAGGED; #if SHOPT_NAMESPACE if(flag&NV_GLOBAL) { @@ -1066,13 +1063,7 @@ static int setall(char **argv,int flag,Dt_t *troot,struct tdata *tp) if(flag&NV_GLOBAL) { sh.var_tree = save_vartree; - sh.st.var_local = save_varlocal; } - /*else if(flag&NV_DYNAMIC) - { - sh.var_tree = save_vartree; - sh.st.var_local = save_varlocal; - }*/ #if SHOPT_NAMESPACE if(flag&NV_GLOBAL) sh.namespace = save_namespace; diff --git a/src/cmd/ksh93/data/builtins.c b/src/cmd/ksh93/data/builtins.c index 68a11931f8c7..58c66ca4ce93 100644 --- a/src/cmd/ksh93/data/builtins.c +++ b/src/cmd/ksh93/data/builtins.c @@ -1862,8 +1862,7 @@ const char sh_opttypeset[] = "variable is created in the global scope.]" "[+?\btypeset\b can also be invoked as \bdeclare\b, or (in functions " "only) as \blocal\b. A variable created or modified by either \bdeclare\b " - "or \blocal\b is given a dynamic scope limited to the function the " - "variable was created in.]" + "or \blocal\b is given a dynamic scope.]" "[+?Not all option combinations are possible. For example, the numeric " "options \b-i\b, \b-E\b, and \b-F\b cannot be specified with " "the justification options \b-L\b, \b-R\b, and \b-Z\b.]" diff --git a/src/cmd/ksh93/include/shell.h b/src/cmd/ksh93/include/shell.h index 4fcd81d87bbc..2d591966f945 100644 --- a/src/cmd/ksh93/include/shell.h +++ b/src/cmd/ksh93/include/shell.h @@ -198,7 +198,6 @@ struct sh_scoped int lineno; Dt_t *save_tree; /* var_tree for calling function */ struct sh_scoped *self; /* pointer to copy of this scope */ - Dt_t *var_local; /* local level variables used by 'local' and 'declare' */ struct slnod *staklist; /* link list of function stacks */ int states; /* shell state bits used by sh_isstate(), etc. */ int breakcnt; /* number of levels to 'break'/'continue' (negative if 'continue') */ diff --git a/src/cmd/ksh93/sh/name.c b/src/cmd/ksh93/sh/name.c index 2323ccfe27c0..e9569a2803aa 100644 --- a/src/cmd/ksh93/sh/name.c +++ b/src/cmd/ksh93/sh/name.c @@ -233,20 +233,17 @@ void nv_setlist(struct argnod *arg,int flags, Namval_t *typ) struct Namref nr; int maketype = flags&NV_TYPE; /* make a 'typeset -T' type definition command */ struct sh_type shtp; - Dt_t *vartree, *save_vartree, *save_varlocal = 0; + Dt_t *vartree, *save_vartree; #if SHOPT_NAMESPACE Namval_t *save_namespace; #endif if(flags&NV_GLOBAL) { save_vartree = sh.var_tree; - save_varlocal = sh.st.var_local; - sh.var_tree = sh.st.var_local = sh.var_base; + sh.var_tree = sh.var_base; } else if(flags&NV_DYNAMIC) - sh.st.var_local = sh.var_tree; - else - sh.st.var_local = sh.var_base; + flags |= NV_TAGGED; #if SHOPT_NAMESPACE if(flags&NV_GLOBAL) { @@ -669,13 +666,7 @@ void nv_setlist(struct argnod *arg,int flags, Namval_t *typ) if(flags&NV_GLOBAL) { sh.var_tree = save_vartree; - sh.st.var_local = save_varlocal; } - /*else if(flags&NV_DYNAMIC) - { - sh.var_tree = save_vartree; - sh.st.var_local = save_varlocal; - }*/ #if SHOPT_NAMESPACE if(flags&NV_GLOBAL) sh.namespace = save_namespace; @@ -2309,7 +2300,7 @@ void sh_scope(struct argnod *envlist, int fun) newroot = nv_dict(sh.namespace); else #endif /* SHOPT_NAMESPACE */ - newroot = sh.st.var_local ? sh.st.var_local : sh.var_base; + newroot = sh.var_base; newscope = dtopen(&_Nvdisc,Dtoset); if(envlist) { diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index 6f5da2fa151e..14ed1988be7e 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -3024,13 +3024,31 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) sh_scope(envlist,1); if(n) { - /* eliminate parent scope */ + /* + * Dynamic variables are implemented by being marked with NV_TAGGED, then imported into nested functions + * via the nv_scan call below (currently this is separated from NV_EXPORT to avoid confusion with -x). + * The old method ksh93v- uses for its bash mode creates a viewport between sh.var_tree and sh.var_base + * with the help of a sh.st.var_local pointer. This could fake dynamic scoping convincingly for the + * bash mode, but in reality that only creates a global scope local to the top level function (i.e., + * nested functions have no scoping.) This design flaw in ksh93v- made its scoping code unsalvageable. + * + * However, this approach is not free of pitfalls either. The nvflag variable the NV_* bits + * are retrieved from is an unsigned short variable. It cannot be changed to an unsigned int because + * the codebase make too many assumptions about its size (cf. https://github.com/att/ast/issues/1038). + * Perhaps adding an nvscope variable would be a suitable workaround... + * + * In any case the NV_TAGGED feature is directly tied to 'typeset -t', which is supposed to be a + * user-defined feature that the shell itself otherwise leaves untouched. The current approach here + * is not backward compatible and runs the risk of breaking many scripts, so it really needs to be + * changed. + */ + nv_scan(prevscope->save_tree, local_exports, NULL, NV_TAGGED, NV_TAGGED|NV_NOSCOPE); nv_scan(prevscope->save_tree, local_exports, NULL, NV_EXPORT, NV_EXPORT|NV_NOSCOPE); } if(posix_fun) /* TODO: Is this even necessary anymore? */ sh.st.lineno = ((struct functnod*)nv_funtree(fp->node))->functline; - sh.st.save_tree = sh.st.var_local = sh.var_tree; + sh.st.save_tree = sh.var_tree; if(!fun) { if(fp->node->nvalue.rp) diff --git a/src/cmd/ksh93/tests/local.sh b/src/cmd/ksh93/tests/local.sh index 3041895188b1..9700c5d6a97d 100755 --- a/src/cmd/ksh93/tests/local.sh +++ b/src/cmd/ksh93/tests/local.sh @@ -56,27 +56,35 @@ for i in declare local; do # The local and declare builtins should have a dynamic scope # Tests for the scope of POSIX functions foo=globalscope + exp=dynscope subfunc() { - [[ $foo == dynscope ]] + print $foo } mainfunc() { - $i foo=dynscope + $i foo=$exp subfunc } - mainfunc || err_exit "'$i' is not using a dynamic scope in POSIX functions" - [[ $foo == globalscope ]] || err_exit "'$i' changes variables outside of a POSIX function's scope" + got=${ mainfunc } + [[ $exp == $got ]] || err_exit "'$i' is not using a dynamic scope in POSIX functions" \ + "(expected $exp, got $(printf %q "$got"))" + [[ $foo == globalscope ]] || err_exit "'$i' changes variables outside of a POSIX function's scope" \ + "(expected globalscope, got $(printf %q "$foo"))" # Tests for the scope of KornShell functions bar=globalscope + exp=dynscope function subfunc_b { - [[ $bar == dynscope ]] + print $bar } function mainfunc_b { $i bar=dynscope subfunc_b } - mainfunc_b || err_exit "'$i' is not using a dynamic scope in KornShell functions" - [[ $bar == globalscope ]] || err_exit "'$i' changes variables outside of a KornShell function's scope" + got=${ mainfunc_b } + [[ $exp == $got ]] || err_exit "'$i' is not using a dynamic scope in KornShell functions" \ + "(expected $exp, got $(printf %q "$got"))" + [[ $bar == globalscope ]] || err_exit "'$i' changes variables outside of a KornShell function's scope" \ + "(expected globalscope, got $(printf %q "$bar"))" done # The declare builtin should work outside of functions @@ -84,9 +92,9 @@ unset foo declare foo=bar [[ $foo == bar ]] || err_exit "'declare' doesn't work outside of functions" +# TODO: Implement optional static scoping via a flag, for use in POSIX functions. + # Test 1: make dynamic $bar static with typeset(1) in ksh function -# TODO: This doesn't work, likely because the static scope is effectively lost -# after a dynamic scope is made. That will need to be fixed. tst=$tmp/tst.sh cat > "$tst" << 'EOF' function nxt { @@ -95,18 +103,22 @@ function nxt { function foo { local bar=1 nxt - local bar=2 + local bar=BAD1 function infun { echo $bar } - typeset bar=BAD + # The current implementation does not make a seperate version of var for the static scope. + # Rather, it changes foo()'s $bar variable back to a static scope, which prevents it from being + # accessed by called functions as $bar is no longer in a dynamic scope. Consequently, both infun + # and nxt are expected to print only a newline. + typeset bar=BAD2 infun nxt } foo echo $bar EOF -exp=$'1\n2\n2' +exp=1 got=$("$SHELL" "$tst") [[ $exp == "$got" ]] || err_exit "Cannot switch from dynamic scope to static scope in ksh functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" From babebd0a2b5a97a19223439115ed4eb791837217 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Tue, 16 Jan 2024 00:59:42 -0800 Subject: [PATCH 16/42] Implement np->dynscope to avoid reuse of NV_TAGGED --- src/cmd/ksh93/bltins/typeset.c | 14 ++++++++------ src/cmd/ksh93/include/nval.h | 5 +++-- src/cmd/ksh93/sh/name.c | 26 ++++++++++++++++---------- src/cmd/ksh93/sh/xec.c | 29 +++++++++++++---------------- 4 files changed, 40 insertions(+), 34 deletions(-) diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index 553798764a2b..2053b10e4ce9 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -671,22 +671,20 @@ static int setall(char **argv,int flag,Dt_t *troot,struct tdata *tp) char *last = 0; int nvflags=(flag&(NV_ARRAY|NV_NOARRAY|NV_VARNAME|NV_IDENT|NV_ASSIGN|NV_STATIC|NV_MOVE)); int r=0, ref=0, comvar=(flag&NV_COMVAR),iarray=(flag&NV_IARRAY); + unsigned char dynscope=0; Dt_t *save_vartree; Namval_t *save_namespace; if(flag&NV_GLOBAL) { save_vartree = sh.var_tree; troot = sh.var_tree = sh.var_base; - } - else if(flag&NV_DYNAMIC) - nvflags |= NV_TAGGED; #if SHOPT_NAMESPACE - if(flag&NV_GLOBAL) - { save_namespace = sh.namespace; sh.namespace = NULL; - } #endif + } + if(flag&NV_DYNAMIC) + dynscope = 1; if(!sh.prefix) { if(!tp->pflag) @@ -811,6 +809,10 @@ static int setall(char **argv,int flag,Dt_t *troot,struct tdata *tp) } continue; } + // TODO: Move this into nv_open + if(troot==sh.var_tree) + np->dynscope = dynscope; + // if(np->nvflag&NV_RDONLY && !tp->pflag && (tp->aflag=='+' || flag & ~(NV_ASSIGN|NV_RDONLY|NV_EXPORT))) /* allow readonly/export on readonly vars */ { diff --git a/src/cmd/ksh93/include/nval.h b/src/cmd/ksh93/include/nval.h index a908225c32ab..f4d85d79ef14 100644 --- a/src/cmd/ksh93/include/nval.h +++ b/src/cmd/ksh93/include/nval.h @@ -108,15 +108,16 @@ struct Namval #if _ast_sizeof_pointer == 8 # if _ast_intswap > 0 unsigned short nvflag; /* attributes */ - unsigned short pad1; + unsigned short dynscope; # else - unsigned short pad1; + unsigned short dynscope; unsigned short nvflag; /* attributes */ # endif uint32_t nvsize; /* size or base */ #else unsigned short nvflag; /* attributes */ unsigned short nvsize; /* size or base */ + unsigned int dynscope; #endif #ifdef _NV_PRIVATE _NV_PRIVATE diff --git a/src/cmd/ksh93/sh/name.c b/src/cmd/ksh93/sh/name.c index e9569a2803aa..b1e7d76c4364 100644 --- a/src/cmd/ksh93/sh/name.c +++ b/src/cmd/ksh93/sh/name.c @@ -228,6 +228,7 @@ void nv_setlist(struct argnod *arg,int flags, Namval_t *typ) char *prefix = sh.prefix; int traceon = (sh_isoption(SH_XTRACE)!=0); int array = (flags&(NV_ARRAY|NV_IARRAY)); + unsigned char dynscope = 0; Namarr_t *ap; Namval_t node; struct Namref nr; @@ -241,16 +242,13 @@ void nv_setlist(struct argnod *arg,int flags, Namval_t *typ) { save_vartree = sh.var_tree; sh.var_tree = sh.var_base; - } - else if(flags&NV_DYNAMIC) - flags |= NV_TAGGED; #if SHOPT_NAMESPACE - if(flags&NV_GLOBAL) - { save_namespace = sh.namespace; sh.namespace = NULL; - } #endif + } + if(flags&NV_DYNAMIC) + dynscope = 1; if(maketype) { shtp.previous = sh.mktype; @@ -324,6 +322,7 @@ void nv_setlist(struct argnod *arg,int flags, Namval_t *typ) } } np = nv_open(cp,sh.var_tree,flag|NV_ASSIGN); + np->dynscope = dynscope; if((arg->argflag&ARG_APPEND) && (tp->tre.tretyp&COMMSK)==TCOM && tp->com.comset && !nv_isvtree(np) && (((ap=nv_arrayptr(np)) && !ap->fun && !nv_opensub(np)) || (!ap && nv_isarray(np) && tp->com.comarg && !((mp=nv_search(tp->com.comarg->argval,sh.fun_tree,0)) && nv_isattr(mp,BLT_DCL))))) { if(tp->com.comarg) @@ -441,9 +440,11 @@ void nv_setlist(struct argnod *arg,int flags, Namval_t *typ) { unsigned short nvflag = np->nvflag; uint32_t nvsize = np->nvsize; + uint32_t nvdynscope = np->dynscope; _nv_unset(np,NV_EXPORT); np->nvflag = nvflag; np->nvsize = nvsize; + np->dynscope = nvdynscope; } else { @@ -556,6 +557,7 @@ void nv_setlist(struct argnod *arg,int flags, Namval_t *typ) { L_ARGNOD->nvalue.nrp = node.nvalue.nrp; L_ARGNOD->nvflag = node.nvflag; + L_ARGNOD->dynscope = node.dynscope; L_ARGNOD->nvfun = node.nvfun; } sh.prefix = prefix; @@ -592,6 +594,7 @@ void nv_setlist(struct argnod *arg,int flags, Namval_t *typ) mp = nv_search(cp,vartree,NV_ADD|NV_NOSCOPE); mp->nvname = np->nvname; /* put_lang() (init.c) compares nvname pointers */ mp->nvflag = np->nvflag; + mp->dynscope = np->dynscope; mp->nvsize = np->nvsize; mp->nvfun = nv_cover(np); } @@ -658,6 +661,7 @@ void nv_setlist(struct argnod *arg,int flags, Namval_t *typ) { L_ARGNOD->nvalue.nrp = node.nvalue.nrp; L_ARGNOD->nvflag = node.nvflag; + L_ARGNOD->dynscope = node.dynscope; L_ARGNOD->nvfun = node.nvfun; } } @@ -2211,14 +2215,16 @@ struct scan static int scanfilter(Namval_t *np, struct scan *sp) { - int k=np->nvflag; + int np_flags = np->nvflag; struct adata *tp = (struct adata*)sp->scandata; char *cp; if(!is_abuiltin(np) && tp && tp->tp && nv_type(np)!=tp->tp) return 0; if(sp->scanmask==NV_TABLE && nv_isvtree(np)) - k = NV_TABLE; - if(sp->scanmask?(k&sp->scanmask)==sp->scanflags:(!sp->scanflags || (k&sp->scanflags))) + np_flags = NV_TABLE; + else if(np->dynscope==1) + np_flags |= NV_DYNAMIC; + if(sp->scanmask?(np_flags&sp->scanmask)==sp->scanflags:(!sp->scanflags || (np_flags&sp->scanflags))) { if(tp && tp->mapname) { @@ -2260,7 +2266,7 @@ void nv_rehash(Namval_t *np,void *data) /* * Walk through the name-value pairs - * if is non-zero, then only nodes with (nvflags&mask)==flags + * if is non-zero, then only nodes with (nvflag&mask)==flags * are visited * If is zero, and non-zero, then nodes with one or * more of is visited diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index 14ed1988be7e..869cdd4be43e 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -3025,25 +3025,22 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) if(n) { /* - * Dynamic variables are implemented by being marked with NV_TAGGED, then imported into nested functions - * via the nv_scan call below (currently this is separated from NV_EXPORT to avoid confusion with -x). - * The old method ksh93v- uses for its bash mode creates a viewport between sh.var_tree and sh.var_base - * with the help of a sh.st.var_local pointer. This could fake dynamic scoping convincingly for the - * bash mode, but in reality that only creates a global scope local to the top level function (i.e., - * nested functions have no scoping.) This design flaw in ksh93v- made its scoping code unsalvageable. + * Dynamic variables are implemented by being marked via np->dynscope, then + * imported into nested functions via the nv_scan call below with scanflags + * set to NV_DYNAMIC. The separate variable is necessary because np->nvflag + * is an unsigned short and cannot hold the NV_DYNAMIC bitflag. It cannot + * be changed to an unsigned int because the codebase makes too many assumptions + * about its size (cf. https://github.com/att/ast/issues/1038). * - * However, this approach is not free of pitfalls either. The nvflag variable the NV_* bits - * are retrieved from is an unsigned short variable. It cannot be changed to an unsigned int because - * the codebase make too many assumptions about its size (cf. https://github.com/att/ast/issues/1038). - * Perhaps adding an nvscope variable would be a suitable workaround... - * - * In any case the NV_TAGGED feature is directly tied to 'typeset -t', which is supposed to be a - * user-defined feature that the shell itself otherwise leaves untouched. The current approach here - * is not backward compatible and runs the risk of breaking many scripts, so it really needs to be - * changed. + * The old method ksh93v- uses for its bash mode creates a viewport between + * sh.var_tree and sh.var_base with the help of a sh.st.var_local pointer. + * This could fake dynamic scoping convincingly for the bash mode, but in + * reality that only manages to create a global scope local to the top level + * function (i.e., nested functions have no scoping.) This design flaw in + * ksh93v- made its scoping code unsalvageable. */ - nv_scan(prevscope->save_tree, local_exports, NULL, NV_TAGGED, NV_TAGGED|NV_NOSCOPE); nv_scan(prevscope->save_tree, local_exports, NULL, NV_EXPORT, NV_EXPORT|NV_NOSCOPE); + nv_scan(prevscope->save_tree, local_exports, NULL, NV_DYNAMIC, NV_DYNAMIC|NV_NOSCOPE); } if(posix_fun) /* TODO: Is this even necessary anymore? */ From 50bb7872c9b5de28c9c658b7d920c28be9411b46 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Tue, 16 Jan 2024 01:32:19 -0800 Subject: [PATCH 17/42] Remove old vestiges from the original implementation --- src/cmd/ksh93/bltins/misc.c | 2 +- src/cmd/ksh93/bltins/typeset.c | 5 ++--- src/cmd/ksh93/include/shell.h | 2 +- src/cmd/ksh93/sh/name.c | 6 +----- src/cmd/ksh93/sh/xec.c | 16 ++++------------ 5 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/cmd/ksh93/bltins/misc.c b/src/cmd/ksh93/bltins/misc.c index 83c90f9204a4..7c6ab96ea7d9 100644 --- a/src/cmd/ksh93/bltins/misc.c +++ b/src/cmd/ksh93/bltins/misc.c @@ -281,7 +281,7 @@ int b_dot_cmd(int n,char *argv[],Shbltin_t *context) else { infunction = sh.infunction; - sh.infunction = 1; + sh.infunction = 2; } *prevscope = sh.st; sh.st.lineno = np?((struct functnod*)nv_funtree(np))->functline:1; diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index f40dbbc3645b..9d8bef562034 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -521,7 +521,7 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) } if(troot==sh.var_tree && !sh.mktype && sh.infunction) { - if(sh.infunction==1 && !(local || declare) && !(flag&NV_DYNAMIC)) + if(sh.infunction==2 && !(local || declare) && !(flag&NV_DYNAMIC)) flag |= NV_GLOBAL; else if((local || declare) && !(flag&NV_GLOBAL)) flag |= NV_DYNAMIC; @@ -1065,11 +1065,10 @@ static int setall(char **argv,int flag,Dt_t *troot,struct tdata *tp) if(flag&NV_GLOBAL) { sh.var_tree = save_vartree; - } #if SHOPT_NAMESPACE - if(flag&NV_GLOBAL) sh.namespace = save_namespace; #endif + } return r; } diff --git a/src/cmd/ksh93/include/shell.h b/src/cmd/ksh93/include/shell.h index 2d591966f945..a7b6ef58533f 100644 --- a/src/cmd/ksh93/include/shell.h +++ b/src/cmd/ksh93/include/shell.h @@ -316,7 +316,7 @@ struct Shell_s char funload; char used_pos; /* used positional parameter */ char universe; - char infunction; /* 0 outside of functions, 1 in ksh functions run from '.' and POSIX functions, 2 in regular ksh functions */ + char infunction; /* 0 outside of functions, 1 in ksh functions, 2 in POSIX functions and ksh functions ran from '.' */ char winch; /* set upon window size change or 'set -b' notification */ short arithrecursion; /* current arithmetic recursion level */ char indebug; /* set when in debug trap */ diff --git a/src/cmd/ksh93/sh/name.c b/src/cmd/ksh93/sh/name.c index 5aa44e908c24..cb3e32378d65 100644 --- a/src/cmd/ksh93/sh/name.c +++ b/src/cmd/ksh93/sh/name.c @@ -670,11 +670,10 @@ void nv_setlist(struct argnod *arg,int flags, Namval_t *typ) if(flags&NV_GLOBAL) { sh.var_tree = save_vartree; - } #if SHOPT_NAMESPACE - if(flags&NV_GLOBAL) sh.namespace = save_namespace; #endif + } } /* @@ -2293,9 +2292,6 @@ int nv_scan(Dt_t *root, void (*fn)(Namval_t*,void*), void *data,int mask, int fl */ void sh_scope(struct argnod *envlist, int fun) { - /* TODO: This is a very limited approach for dynamic scoping. - * Implement a more flexible approach that allows for static scoping as well. - */ Dt_t *newscope, *newroot; struct Ufunction *rp; #if SHOPT_NAMESPACE diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index d40491659ec1..e05ad4011520 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -1017,11 +1017,6 @@ int sh_exec(const Shnode_t *t, int flags) sh.typeinit = np; tp = nv_type(np); } - /* - * The declare and local builtins have a dynamic scope limited - * to the function in which they are called. This should be - * skipped when not in a function. - */ if(np==SYSCOMPOUND || checkopt(com,'C')) flgs |= NV_COMVAR; if(checkopt(com,'S')) @@ -1053,7 +1048,7 @@ int sh_exec(const Shnode_t *t, int flags) } if((np==SYSLOCAL || np==SYSDECLARE) && !(flgs&NV_GLOBAL)) flgs |= NV_DYNAMIC; - else if(sh.infunction==1 && !(flgs&NV_DYNAMIC)) + else if(sh.infunction==2 && !(flgs&NV_DYNAMIC)) flgs |= NV_GLOBAL; if(sh.fn_depth && !sh.prefix) flgs |= NV_NOSCOPE; @@ -3011,17 +3006,14 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) infunction = sh.infunction; if(!posix_fun) { - sh.infunction = 2; + sh.infunction = 1; sh_offoption(SH_ERREXIT); } else - sh.infunction = 1; + sh.infunction = 2; prevscope->save_tree = sh.var_tree; n = dtvnext(prevscope->save_tree) != (sh.namespace?sh.var_base:0); - if(posix_fun) - sh_scope(envlist,2); - else - sh_scope(envlist,1); + sh_scope(envlist,sh.infunction); if(n) { /* From 81499fcb06dc6e5e7819dd8b78a38dcfeea26b41 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Tue, 16 Jan 2024 03:00:47 -0800 Subject: [PATCH 18/42] Fix modernish regression tests failing on init Bear in mind that there are still three test failures in the modernish test suite, which are probably cause by switching to sh_funscope() for POSIX function handling: 001: push;set;check;send sig;unset;pop;check - FAIL: check traps, test var=$(trap) 007: trap stack in a subshell - FAIL: wrong output from 'trap' (3) 008: 'trap' can ignore sig if no stack traps - FAIL: not ignored while no stack traps These will need to be fixed at some point. --- src/cmd/ksh93/sh/xec.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index e05ad4011520..4979d17e5965 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -2985,10 +2985,6 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) Shopt_t options; options = sh.options; NOT_USED(argn); - if(sh.fn_depth==0) - sh.glob_options = sh.options; - else - sh.options = sh.glob_options; *prevscope = sh.st; sh.st.prevst = prevscope; sh.st.self = savst; @@ -3006,6 +3002,10 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) infunction = sh.infunction; if(!posix_fun) { + if(sh.fn_depth==0) + sh.glob_options = sh.options; + else + sh.options = sh.glob_options; sh.infunction = 1; sh_offoption(SH_ERREXIT); } From 6b5534ed0ba0a9826b5408b6b345b3ba255ff337 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Tue, 16 Jan 2024 10:02:16 -0800 Subject: [PATCH 19/42] Attempt at freeze fix --- src/cmd/ksh93/bltins/misc.c | 49 +++++++------ src/cmd/ksh93/bltins/typeset.c | 2 +- src/cmd/ksh93/include/shell.h | 11 ++- src/cmd/ksh93/sh/name.c | 10 +-- src/cmd/ksh93/sh/xec.c | 116 +++++++++++++++++++------------ src/cmd/ksh93/tests/functions.sh | 10 +-- 6 files changed, 113 insertions(+), 85 deletions(-) diff --git a/src/cmd/ksh93/bltins/misc.c b/src/cmd/ksh93/bltins/misc.c index 7c6ab96ea7d9..abb919314a7b 100644 --- a/src/cmd/ksh93/bltins/misc.c +++ b/src/cmd/ksh93/bltins/misc.c @@ -56,8 +56,6 @@ #include #endif -#define DOTMAX MAXDEPTH /* maximum level of . nesting */ - /* * Handler function for nv_scan() that unsets a variable's export attribute */ @@ -215,17 +213,17 @@ int b_eval(int argc,char *argv[], Shbltin_t *context) #endif int b_dot_cmd(int n,char *argv[],Shbltin_t *context) { - char *script; - Namval_t *np; - int jmpval; - struct sh_scoped savst, *prevscope = sh.st.self; - char *filename=0, *buffer=0, *tofree; - int fd; - char infunction = -1; - struct dolnod *saveargfor; - volatile struct dolnod *argsave=0; - struct checkpt buff; - Sfio_t *iop=0; + char *script; + Namval_t *np; + struct sh_scoped savst, *prevscope = sh.st.self; + char *filename=0, *buffer=0, *tofree; + int fd, dtret, jmpval, save_invoc_local; + char save_infunction = -1; + struct dolnod *saveargfor; + volatile struct dolnod *argsave=0; + struct checkpt buff; + Sfio_t *iop=0; + Namval_t *nspace = sh.namespace; NOT_USED(context); while (n = optget(argv,sh_optdot)) switch (n) { @@ -243,7 +241,7 @@ int b_dot_cmd(int n,char *argv[],Shbltin_t *context) errormsg(SH_DICT,ERROR_usage(2),"%s",optusage(NULL)); UNREACHABLE(); } - if(sh.dot_depth >= DOTMAX) + if(sh.dot_depth >= MAXDEPTH) { errormsg(SH_DICT,ERROR_exit(1),e_toodeep,script); UNREACHABLE(); @@ -271,6 +269,7 @@ int b_dot_cmd(int n,char *argv[],Shbltin_t *context) np = 0; if(!np) { + /* Open the dot script */ if((fd=path_open(script,path_get(script))) < 0) { errormsg(SH_DICT,ERROR_system(1),e_open,script); @@ -280,25 +279,23 @@ int b_dot_cmd(int n,char *argv[],Shbltin_t *context) } else { - infunction = sh.infunction; - sh.infunction = 2; + /* We are executing a KornShell function as a dot script */ + save_infunction = sh.infunction; + sh.infunction = FUN_KSHDOT; } *prevscope = sh.st; - sh.st.lineno = np?((struct functnod*)nv_funtree(np))->functline:1; - sh.st.save_tree = sh.var_tree; + sh.st.lineno = np ? ((struct functnod*)nv_funtree(np))->functline : 1; if(filename) - { sh.st.filename = filename; - sh.st.lineno = 1; - } sh.st.prevst = prevscope; sh.st.self = &savst; sh.topscope = (Shscope_t*)sh.st.self; prevscope->save_tree = sh.var_tree; + sh.st.save_tree = sh.var_tree; tofree = sh.st.filename; if(np) sh.st.filename = np->nvalue.rp->fname; - nv_putval(SH_PATHNAMENOD, sh.st.filename ,NV_NOFREE); + nv_putval(SH_PATHNAMENOD,sh.st.filename,NV_NOFREE); if(np || argv[1]) argsave = sh_argnew(argv,&saveargfor); sh_pushcontext(&buff,SH_JMPDOT); @@ -310,9 +307,11 @@ int b_dot_cmd(int n,char *argv[],Shbltin_t *context) sh.dot_depth++; update_sh_level(); if(np) + /* Execute the function as though it were a dot script */ sh_exec((Shnode_t*)(nv_funtree(np)),sh_isstate(SH_ERREXIT)); else { + /* Run the dot script */ buffer = sh_malloc(IOBSIZE+1); iop = sfnew(NULL,buffer,IOBSIZE,fd,SF_READ); sh_offstate(SH_NOFORK); @@ -335,9 +334,9 @@ int b_dot_cmd(int n,char *argv[],Shbltin_t *context) } if(sh.st.self != &savst) *sh.st.self = sh.st; - if(infunction != -1) - sh.infunction = infunction; - /* only restore the top Shscope_t portion */ + if(save_infunction != -1) + sh.infunction = save_infunction; + /* Only restore the top Shscope_t portion for functions */ memcpy(&sh.st, prevscope, sizeof(Shscope_t)); sh.topscope = (Shscope_t*)prevscope; nv_putval(SH_PATHNAMENOD,sh.st.filename,NV_NOFREE); diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index 9d8bef562034..4ee08f50195a 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -521,7 +521,7 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) } if(troot==sh.var_tree && !sh.mktype && sh.infunction) { - if(sh.infunction==2 && !(local || declare) && !(flag&NV_DYNAMIC)) + if((sh.infunction==FUN_POSIX && !(local || declare) && !(flag&NV_DYNAMIC)) || sh.infunction==FUN_KSHDOT) flag |= NV_GLOBAL; else if((local || declare) && !(flag&NV_GLOBAL)) flag |= NV_DYNAMIC; diff --git a/src/cmd/ksh93/include/shell.h b/src/cmd/ksh93/include/shell.h index a7b6ef58533f..a49c4d85a9c2 100644 --- a/src/cmd/ksh93/include/shell.h +++ b/src/cmd/ksh93/include/shell.h @@ -154,11 +154,18 @@ typedef union Shnode_u Shnode_t; #define SH_COMMANDLINE 0x100 /* bit flag for invocation-only options ('set -o' cannot change them) */ /* - * passed as flags to builtins in Nambltin_t struct when BLT_OPTIM is on + * Passed as flags to builtins in Nambltin_t struct when BLT_OPTIM is on */ #define SH_BEGIN_OPTIM 0x1 #define SH_END_OPTIM 0x2 +/* + * Used by sh.infunction + */ +#define FUN_KSH 1 /* 'function name {' */ +#define FUN_POSIX 2 /* 'name() {' */ +#define FUN_KSHDOT 3 /* Ksh function run by '.' command */ + /* The following type is used for error messages */ /* error messages */ @@ -316,7 +323,7 @@ struct Shell_s char funload; char used_pos; /* used positional parameter */ char universe; - char infunction; /* 0 outside of functions, 1 in ksh functions, 2 in POSIX functions and ksh functions ran from '.' */ + char infunction; char winch; /* set upon window size change or 'set -b' notification */ short arithrecursion; /* current arithmetic recursion level */ char indebug; /* set when in debug trap */ diff --git a/src/cmd/ksh93/sh/name.c b/src/cmd/ksh93/sh/name.c index cb3e32378d65..ece502519a7d 100644 --- a/src/cmd/ksh93/sh/name.c +++ b/src/cmd/ksh93/sh/name.c @@ -386,7 +386,6 @@ void nv_setlist(struct argnod *arg,int flags, Namval_t *typ) Dt_t *last_root = sh.last_root; char **argv = sh_argbuild(&argc,&tp->com,0); sh.last_root = last_root; - /* TODO: Implement a proper check for POSIX functions here */ if(sh.mktype && sh.dot_depth==0 && np==((struct sh_type*)sh.mktype)->nodes[0]) { sh.mktype = 0; @@ -2305,14 +2304,7 @@ void sh_scope(struct argnod *envlist, int fun) { dtview(newscope,(Dt_t*)sh.var_tree); sh.var_tree = newscope; - if(fun==2) - /* TODO: See if this really is the only way to fix the functions.sh - * test failure. This part of the code is surprisingly fragile, - * so if possible I'd rather not change anything here at all. - */ - nv_setlist(envlist,NV_NOSCOPE|NV_IDENT|NV_ASSIGN,0); - else - nv_setlist(envlist,NV_EXPORT|NV_NOSCOPE|NV_IDENT|NV_ASSIGN,0); + nv_setlist(envlist,NV_EXPORT|NV_NOSCOPE|NV_IDENT|NV_ASSIGN,0); if(!fun) return; sh.var_tree = dtview(newscope,0); diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index 4979d17e5965..fa190e992ee8 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -1048,9 +1048,9 @@ int sh_exec(const Shnode_t *t, int flags) } if((np==SYSLOCAL || np==SYSDECLARE) && !(flgs&NV_GLOBAL)) flgs |= NV_DYNAMIC; - else if(sh.infunction==2 && !(flgs&NV_DYNAMIC)) + else if((sh.infunction==FUN_POSIX && !(flgs&NV_DYNAMIC)) || sh.infunction==FUN_KSHDOT) flgs |= NV_GLOBAL; - if(sh.fn_depth && !sh.prefix) + if((sh.infunction==FUN_POSIX || sh.fn_depth) && !sh.prefix) flgs |= NV_NOSCOPE; } else if(np==SYSEXPORT) @@ -2969,52 +2969,60 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) { char *trap; int nsig; - struct dolnod *argsav=0,*saveargfor; - struct sh_scoped *savst = (struct sh_scoped*)stkalloc(sh.stk,sizeof(struct sh_scoped)); + struct dolnod *argsav = 0, *saveargfor; struct sh_scoped *prevscope = sh.st.self; - struct argnod *envlist=0; - int isig,jmpval; + struct sh_scoped *savst = (struct sh_scoped*)stkalloc(sh.stk,sizeof(struct sh_scoped)); + struct argnod *envlist = 0; + int isig, jmpval, save_loopcnt = sh.st.loopcnt; volatile int r = 0; - int n; - char save_invoc_local, infunction, posix_fun = 0; + int dtret; + char save_invoc_local, save_infunction, posix_fun = 0; char **savsig, *save_debugtrap = 0; struct funenv *fp = 0; struct checkpt *buffp = (struct checkpt*)stkalloc(sh.stk,sizeof(struct checkpt)); Namval_t *nspace = sh.namespace; Dt_t *last_root = sh.last_root; - Shopt_t options; - options = sh.options; + Shopt_t save_options; NOT_USED(argn); + sh.st.loopcnt = 0; *prevscope = sh.st; sh.st.prevst = prevscope; sh.st.self = savst; sh.topscope = (Shscope_t*)sh.st.self; sh.st.opterror = sh.st.optchar = 0; sh.st.optindex = 1; - sh.st.loopcnt = 0; if(!fun) { fp = (struct funenv*)arg; - sh.st.real_fun = (fp->node)->nvalue.rp; envlist = fp->env; posix_fun = nv_isattr(fp->node,NV_FPOSIX); + if(!posix_fun) + sh.st.real_fun = (fp->node)->nvalue.rp; } - infunction = sh.infunction; + save_infunction = sh.infunction; if(!posix_fun) { + save_options = sh.options; if(sh.fn_depth==0) - sh.glob_options = sh.options; + sh.glob_options = sh.options; else sh.options = sh.glob_options; - sh.infunction = 1; + sh.infunction = FUN_KSH; sh_offoption(SH_ERREXIT); } else - sh.infunction = 2; + { + if(sh.dot_depth >= MAXDEPTH) + { + errormsg(SH_DICT,ERROR_exit(1),e_toodeep,argv[0]); + UNREACHABLE(); + } + sh.infunction = FUN_POSIX; + } prevscope->save_tree = sh.var_tree; - n = dtvnext(prevscope->save_tree) != (sh.namespace?sh.var_base:0); - sh_scope(envlist,sh.infunction); - if(n) + dtret = dtvnext(prevscope->save_tree) != (sh.namespace?sh.var_base:0); + sh_scope(envlist,1); + if(dtret) { /* * Dynamic variables are implemented by being marked via np->dynscope, then @@ -3034,9 +3042,6 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) nv_scan(prevscope->save_tree, local_exports, NULL, NV_EXPORT, NV_EXPORT|NV_NOSCOPE); nv_scan(prevscope->save_tree, local_exports, NULL, NV_DYNAMIC, NV_DYNAMIC|NV_NOSCOPE); } - if(posix_fun) - /* TODO: Is this even necessary anymore? */ - sh.st.lineno = ((struct functnod*)nv_funtree(fp->node))->functline; sh.st.save_tree = sh.var_tree; if(!fun) { @@ -3049,7 +3054,7 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) { if(!fun) { - if(sh_isoption(SH_FUNCTRACE) && is_option(&options,SH_XTRACE) || nv_isattr(fp->node,NV_TAGGED)) + if(sh_isoption(SH_FUNCTRACE) && is_option(&save_options,SH_XTRACE) || nv_isattr(fp->node,NV_TAGGED)) sh_onoption(SH_XTRACE); else sh_offoption(SH_XTRACE); @@ -3094,41 +3099,54 @@ 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(!posix_fun) { - sh.toomany = 1; - siglongjmp(*sh.jmplist,SH_JMPERRFN); + if(sh.fn_depth >= MAXDEPTH) + { + sh.toomany = 1; + siglongjmp(*sh.jmplist,SH_JMPERRFN); + } + sh.fn_depth++; } - sh.fn_depth++; + else + sh.dot_depth++; update_sh_level(); if(fun) r = (*fun)(arg); else { - char **arg = sh.st.real_fun->argv; - Namval_t *np, *nq, **nref; - if(nref=fp->nref) + if(posix_fun) + sh_exec((Shnode_t*)(nv_funtree(fp->node)),sh_isstate(SH_ERREXIT)); + else { - sh.last_root = 0; - for(r=0; arg[r]; r++) + char **arg = sh.st.real_fun->argv; + Namval_t *np, *nq, **nref; + if(nref=fp->nref) { - np = nv_search(arg[r],sh.var_tree,NV_NOSCOPE|NV_ADD); - if(np && (nq=*nref++)) + sh.last_root = 0; + for(r=0; arg[r]; r++) { - np->nvalue.nrp = sh_newof(0,struct Namref,1,0); - np->nvalue.nrp->np = nq; - nv_onattr(np,NV_REF|NV_NOFREE); + np = nv_search(arg[r],sh.var_tree,NV_NOSCOPE|NV_ADD); + if(np && (nq=*nref++)) + { + np->nvalue.nrp = sh_newof(0,struct Namref,1,0); + np->nvalue.nrp->np = nq; + nv_onattr(np,NV_REF|NV_NOFREE); + } } } + sh_exec((Shnode_t*)(nv_funtree((fp->node))),execflg|SH_ERREXIT); } - sh_exec((Shnode_t*)(nv_funtree((fp->node))),execflg|SH_ERREXIT); r = sh.exitval; } } sh.invoc_local = save_invoc_local; - sh.fn_depth--; + if(!posix_fun) + sh.fn_depth--; + else + sh.dot_depth--; update_sh_level(); - sh.infunction = infunction; + sh.infunction = save_infunction; if(!posix_fun && sh.fn_depth==1 && jmpval==SH_JMPERRFN) { errormsg(SH_DICT,ERROR_exit(1),e_toodeep,argv[0]); @@ -3146,18 +3164,28 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) sh.st.trapcom[0] = 0; sh_sigreset(1); } - sh.st = *prevscope; - sh.topscope = (Shscope_t*)prevscope; - sh.last_root = last_root; + /* + * POSIX function cleanup + */ if(posix_fun) { if(sh.st.self != &savst) *sh.st.self = sh.st; + sh.st.loopcnt = save_loopcnt; + /* Only restore the top Shscope_t portion for POSIX functions */ + memcpy(&sh.st, prevscope, sizeof(Shscope_t)); + sh.topscope = (Shscope_t*)prevscope; nv_putval(SH_PATHNAMENOD,sh.st.filename,NV_NOFREE); if(jmpval && jmpval!=SH_JMPFUN) siglongjmp(*sh.jmplist,jmpval); return sh.exitval; } + /* + * KornShell function cleanup + */ + sh.st = *prevscope; + sh.topscope = (Shscope_t*)prevscope; + sh.last_root = last_root; nv_getval(sh_scoped(IFSNOD)); if(nsig) { @@ -3168,7 +3196,7 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) free(savsig); } sh.trapnote = 0; - sh.options = options; + sh.options = save_options; if(jmpval == SH_JMPSUB) siglongjmp(*sh.jmplist,jmpval); if(trap) diff --git a/src/cmd/ksh93/tests/functions.sh b/src/cmd/ksh93/tests/functions.sh index a36f4eac9680..0f7a672b5d93 100755 --- a/src/cmd/ksh93/tests/functions.sh +++ b/src/cmd/ksh93/tests/functions.sh @@ -1208,13 +1208,15 @@ function foo esac done shift $((OPTIND - 1)) - (( OPTIND == 4 )) || err_exit "OPTIND is $OPTIND at end of function foo; it should be 4" + (( OPTIND == 4 )) || err_exit "OPTIND is $OPTIND at end of function foo; it should be 4" [[ $1 == foo2 ]] || err_exit "\$1 is $1, not foo after getopts in function" } -OPTIND=6 OPTARG=xxx +OPTIND=6 OPTARG=xxx exp=xxx exptwo=6 foo -h -i foobar foo2 -[[ $OPTARG == xxx ]] || err_exit 'getopts in function changes global OPTARG' -(( OPTIND == 6 )) || err_exit 'getopts in function changes global OPTIND' +[[ $OPTARG == $exp ]] || err_exit 'getopts in KornShell function changes global OPTARG' \ + "(expected $(printf %q "$exp"), got $(printf %q "$OPTARG"))" +(( OPTIND == exptwo )) || err_exit 'getopts in KornShell function changes global OPTIND' \ + "(expected $(printf %q "$exptwo"), got $(printf %q "$OPTIND"))" if [[ ! $compiled ]] then function foo { getopts --man; } From 9c751fe605c31be0f62715501df162fe95a13735 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Tue, 16 Jan 2024 10:08:02 -0800 Subject: [PATCH 20/42] Fix the (apparently) getopts freeze in modernish --- src/cmd/ksh93/sh/xec.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index fa190e992ee8..74a5348747e2 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -2989,8 +2989,6 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) sh.st.prevst = prevscope; sh.st.self = savst; sh.topscope = (Shscope_t*)sh.st.self; - sh.st.opterror = sh.st.optchar = 0; - sh.st.optindex = 1; if(!fun) { fp = (struct funenv*)arg; @@ -3003,6 +3001,8 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) if(!posix_fun) { save_options = sh.options; + sh.st.opterror = sh.st.optchar = 0; + sh.st.optindex = 1; if(sh.fn_depth==0) sh.glob_options = sh.options; else From 3732068bbae252ab81de456eb2db1ddf9a5cdadc Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Tue, 16 Jan 2024 10:35:21 -0800 Subject: [PATCH 21/42] Fix odd regression in memory leak tests --- src/cmd/ksh93/sh/xec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index 74a5348747e2..e5ba3a910430 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -2984,11 +2984,11 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) Dt_t *last_root = sh.last_root; Shopt_t save_options; NOT_USED(argn); - sh.st.loopcnt = 0; *prevscope = sh.st; sh.st.prevst = prevscope; sh.st.self = savst; sh.topscope = (Shscope_t*)sh.st.self; + sh.st.loopcnt = 0; if(!fun) { fp = (struct funenv*)arg; From 45d8749c7ed58f4b3f121505ea3578bf15eedb9c Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Tue, 16 Jan 2024 10:43:08 -0800 Subject: [PATCH 22/42] Yet more cleanup --- src/cmd/ksh93/bltins/misc.c | 2 +- src/cmd/ksh93/bltins/typeset.c | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cmd/ksh93/bltins/misc.c b/src/cmd/ksh93/bltins/misc.c index abb919314a7b..9f2e63a06be0 100644 --- a/src/cmd/ksh93/bltins/misc.c +++ b/src/cmd/ksh93/bltins/misc.c @@ -285,13 +285,13 @@ int b_dot_cmd(int n,char *argv[],Shbltin_t *context) } *prevscope = sh.st; sh.st.lineno = np ? ((struct functnod*)nv_funtree(np))->functline : 1; + sh.st.save_tree = sh.var_tree; if(filename) sh.st.filename = filename; sh.st.prevst = prevscope; sh.st.self = &savst; sh.topscope = (Shscope_t*)sh.st.self; prevscope->save_tree = sh.var_tree; - sh.st.save_tree = sh.var_tree; tofree = sh.st.filename; if(np) sh.st.filename = np->nvalue.rp->fname; diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index 4ee08f50195a..ad3f0291e273 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -515,7 +515,6 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) } if((flag&(NV_GLOBAL|NV_DYNAMIC)) && sh.mktype) { - /* TODO: Improve unfinished error message */ errormsg(SH_DICT,ERROR_exit(2),"type members cannot be global or dynamic"); UNREACHABLE(); } From 48a7d1d36ffffb58a50c41e9f53cc03bf430892a Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Tue, 16 Jan 2024 11:16:32 -0800 Subject: [PATCH 23/42] Move np->dynscope handling to nv_open --- src/cmd/ksh93/bltins/typeset.c | 9 +-------- src/cmd/ksh93/nval.3 | 3 +++ src/cmd/ksh93/sh/name.c | 11 ++++++----- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index ad3f0291e273..99ff333c18c8 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -668,9 +668,8 @@ static int setall(char **argv,int flag,Dt_t *troot,struct tdata *tp) { char *name; char *last = 0; - int nvflags=(flag&(NV_ARRAY|NV_NOARRAY|NV_VARNAME|NV_IDENT|NV_ASSIGN|NV_STATIC|NV_MOVE)); + int nvflags=(flag&(NV_ARRAY|NV_NOARRAY|NV_VARNAME|NV_IDENT|NV_ASSIGN|NV_STATIC|NV_MOVE|NV_DYNAMIC)); int r=0, ref=0, comvar=(flag&NV_COMVAR),iarray=(flag&NV_IARRAY); - unsigned char dynscope=0; Dt_t *save_vartree = NULL; Namval_t *save_namespace = NULL; if(flag&NV_GLOBAL) @@ -682,8 +681,6 @@ static int setall(char **argv,int flag,Dt_t *troot,struct tdata *tp) sh.namespace = NULL; #endif } - if(flag&NV_DYNAMIC) - dynscope = 1; if(!sh.prefix) { if(!tp->pflag) @@ -808,10 +805,6 @@ static int setall(char **argv,int flag,Dt_t *troot,struct tdata *tp) } continue; } - // TODO: Move this into nv_open - if(troot==sh.var_tree) - np->dynscope = dynscope; - // if(np->nvflag&NV_RDONLY && !tp->pflag && (tp->aflag=='+' || flag & ~(NV_ASSIGN|NV_RDONLY|NV_EXPORT))) /* allow readonly/export on readonly vars */ { diff --git a/src/cmd/ksh93/nval.3 b/src/cmd/ksh93/nval.3 index 36ca006eda40..31fbca457ac2 100644 --- a/src/cmd/ksh93/nval.3 +++ b/src/cmd/ksh93/nval.3 @@ -146,6 +146,9 @@ Instead, a \f3NULL\fP pointer will be returned. \f3NV_NOSCOPE\fP: Only the top level scope is used. .IP +\f3NV_DYNAMIC\fP: +The assignment will place the variable in a dynamic local scope. +.IP \f3NV_NOFAIL\fP: Just return \f3NULL\fP when an error occurs. By default an error message is displayed and the current command diff --git a/src/cmd/ksh93/sh/name.c b/src/cmd/ksh93/sh/name.c index ece502519a7d..fb5d8ce9e7ed 100644 --- a/src/cmd/ksh93/sh/name.c +++ b/src/cmd/ksh93/sh/name.c @@ -228,7 +228,6 @@ void nv_setlist(struct argnod *arg,int flags, Namval_t *typ) char *prefix = sh.prefix; int traceon = (sh_isoption(SH_XTRACE)!=0); int array = (flags&(NV_ARRAY|NV_IARRAY)); - unsigned char dynscope = 0; Namarr_t *ap; Namval_t node; struct Namref nr; @@ -247,8 +246,6 @@ void nv_setlist(struct argnod *arg,int flags, Namval_t *typ) sh.namespace = NULL; #endif } - if(flags&NV_DYNAMIC) - dynscope = 1; if(maketype) { shtp.previous = sh.mktype; @@ -289,7 +286,7 @@ void nv_setlist(struct argnod *arg,int flags, Namval_t *typ) int sub=0; struct fornod *fp=(struct fornod*)arg->argchn.ap; Shnode_t *tp=fp->fortre; - flag |= (flags&(NV_NOSCOPE|NV_STATIC|NV_FARRAY)); + flag |= (flags&(NV_NOSCOPE|NV_STATIC|NV_FARRAY|NV_DYNAMIC)); if(arg->argflag&ARG_ARRAY) array |= NV_IARRAY; if(arg->argflag&ARG_QUOTED) @@ -322,7 +319,6 @@ void nv_setlist(struct argnod *arg,int flags, Namval_t *typ) } } np = nv_open(cp,sh.var_tree,flag|NV_ASSIGN); - np->dynscope = dynscope; if((arg->argflag&ARG_APPEND) && (tp->tre.tretyp&COMMSK)==TCOM && tp->com.comset && !nv_isvtree(np) && (((ap=nv_arrayptr(np)) && !ap->fun && !nv_opensub(np)) || (!ap && nv_isarray(np) && tp->com.comarg && !((mp=nv_search(tp->com.comarg->argval,sh.fun_tree,0)) && nv_isattr(mp,BLT_DCL))))) { if(tp->com.comarg) @@ -1343,6 +1339,7 @@ void nv_delete(Namval_t* np, Dt_t *root, int flags) * If & NV_NOFAIL then don't generate an error message on failure * If & NV_STATIC then unset before an assignment * If & NV_UNATTR then unset attributes before assignment + * If & NV_DYNAMIC then set a dynamic scope during assignment * SH_INIT is only set while initializing the environment */ Namval_t *nv_open(const char *name, Dt_t *root, int flags) @@ -1574,6 +1571,10 @@ Namval_t *nv_open(const char *name, Dt_t *root, int flags) sh.prefix = prefix; } nv_onattr(np, flags&NV_ATTRIBUTES); + if(flags&NV_DYNAMIC) + np->dynscope = 1; + else + np->dynscope = 0; } else if(c) { From c07491a262d51cc4f8e17392f695b940d730f649 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Tue, 16 Jan 2024 11:40:46 -0800 Subject: [PATCH 24/42] Port POSIX function code from b_dot_cmd() --- src/cmd/ksh93/sh/main.c | 2 +- src/cmd/ksh93/sh/xec.c | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/cmd/ksh93/sh/main.c b/src/cmd/ksh93/sh/main.c index 38ae36655c91..af44c80d0df1 100644 --- a/src/cmd/ksh93/sh/main.c +++ b/src/cmd/ksh93/sh/main.c @@ -138,7 +138,7 @@ int sh_main(int ac, char *av[], Shinit_f userinit) /* begin script execution here */ sh_reinit(NULL); } - sh.fn_depth = sh.dot_depth = 0; + sh.fn_depth = sh.dot_depth = sh.infunction = 0; command = error_info.id; path_pwd(); iop = NULL; diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index e5ba3a910430..50239ba84779 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -3007,8 +3007,8 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) sh.glob_options = sh.options; else sh.options = sh.glob_options; - sh.infunction = FUN_KSH; sh_offoption(SH_ERREXIT); + sh.infunction = FUN_KSH; } else { @@ -3019,6 +3019,7 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) } sh.infunction = FUN_POSIX; } + /* Create a local scope for the function */ prevscope->save_tree = sh.var_tree; dtret = dtvnext(prevscope->save_tree) != (sh.namespace?sh.var_base:0); sh_scope(envlist,1); @@ -3147,7 +3148,7 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) sh.dot_depth--; update_sh_level(); sh.infunction = save_infunction; - if(!posix_fun && sh.fn_depth==1 && jmpval==SH_JMPERRFN) + if(sh.fn_depth==1 && jmpval==SH_JMPERRFN) { errormsg(SH_DICT,ERROR_exit(1),e_toodeep,argv[0]); UNREACHABLE(); @@ -3156,8 +3157,13 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) sh_unscope(); sh.namespace = nspace; sh.var_tree = (Dt_t*)prevscope->save_tree; - if(!posix_fun || jmpval!=SH_JMPSCRIPT) + if((posix_fun && jmpval!=SH_JMPSCRIPT) || !posix_fun) sh_argreset(argsav,saveargfor); + else + { + prevscope->dolc = sh.st.dolc; + prevscope->dolv = sh.st.dolv; + } if(!posix_fun) { trap = sh.st.trapcom[0]; From c7ae6c87ea0d06e71eb41e9d9e816046143ed20f Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Tue, 16 Jan 2024 11:54:05 -0800 Subject: [PATCH 25/42] Undo unnecessary changes --- src/cmd/ksh93/sh/xec.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index 50239ba84779..9146c62debfc 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -3154,6 +3154,7 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) UNREACHABLE(); } sh_popcontext(buffp); + /* Reinstate the previous scope */ sh_unscope(); sh.namespace = nspace; sh.var_tree = (Dt_t*)prevscope->save_tree; @@ -3177,7 +3178,6 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) { if(sh.st.self != &savst) *sh.st.self = sh.st; - sh.st.loopcnt = save_loopcnt; /* Only restore the top Shscope_t portion for POSIX functions */ memcpy(&sh.st, prevscope, sizeof(Shscope_t)); sh.topscope = (Shscope_t*)prevscope; @@ -3191,7 +3191,6 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) */ sh.st = *prevscope; sh.topscope = (Shscope_t*)prevscope; - sh.last_root = last_root; nv_getval(sh_scoped(IFSNOD)); if(nsig) { @@ -3203,6 +3202,7 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) } sh.trapnote = 0; sh.options = save_options; + sh.last_root = last_root; if(jmpval == SH_JMPSUB) siglongjmp(*sh.jmplist,jmpval); if(trap) From bfd0052123cff94e3300cb4a9f37e441ce1f049d Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Tue, 16 Jan 2024 12:49:58 -0800 Subject: [PATCH 26/42] There is no form of scoping for dotted funcs, so this is useless --- src/cmd/ksh93/bltins/typeset.c | 2 +- src/cmd/ksh93/sh/xec.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index 99ff333c18c8..10058c73eba3 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -520,7 +520,7 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) } if(troot==sh.var_tree && !sh.mktype && sh.infunction) { - if((sh.infunction==FUN_POSIX && !(local || declare) && !(flag&NV_DYNAMIC)) || sh.infunction==FUN_KSHDOT) + if(sh.infunction==FUN_POSIX && !(local || declare) && !(flag&NV_DYNAMIC)) flag |= NV_GLOBAL; else if((local || declare) && !(flag&NV_GLOBAL)) flag |= NV_DYNAMIC; diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index 9146c62debfc..a3949d2e68ed 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -1048,7 +1048,7 @@ int sh_exec(const Shnode_t *t, int flags) } if((np==SYSLOCAL || np==SYSDECLARE) && !(flgs&NV_GLOBAL)) flgs |= NV_DYNAMIC; - else if((sh.infunction==FUN_POSIX && !(flgs&NV_DYNAMIC)) || sh.infunction==FUN_KSHDOT) + else if(sh.infunction==FUN_POSIX && !(flgs&NV_DYNAMIC)) flgs |= NV_GLOBAL; if((sh.infunction==FUN_POSIX || sh.fn_depth) && !sh.prefix) flgs |= NV_NOSCOPE; From 44347806966cca26165dbf23a27fca984cb020f4 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Tue, 16 Jan 2024 15:46:54 -0800 Subject: [PATCH 27/42] Add static scoping flag and make other improvements List of changes: - A -c flag has been added to typeset that allows you to use statically scoped variables in POSIX functions. - A noteable caveat regarding typeset -p's interactions with the new scoping functionality has been documented. - The regression tests have been overhauled to account for the special handling of 'command typeset' in sh_exec(). More were also added for the new 'typeset -c' feature. - The two nv_scan calls have been rolled into one faster call, which should have equivalent functionality (cf. the code for the scanfilter() function). - Increment copyright dates using the update_copyright script from the wiki. --- src/cmd/ksh93/bltins/misc.c | 2 +- src/cmd/ksh93/bltins/read.c | 2 +- src/cmd/ksh93/bltins/typeset.c | 30 ++- src/cmd/ksh93/data/builtins.c | 10 +- src/cmd/ksh93/edit/emacs.c | 2 +- src/cmd/ksh93/include/builtins.h | 2 +- src/cmd/ksh93/include/nval.h | 4 +- src/cmd/ksh93/include/shell.h | 2 +- src/cmd/ksh93/sh/main.c | 2 +- src/cmd/ksh93/sh/xec.c | 9 +- src/cmd/ksh93/tests/functions.sh | 2 +- src/cmd/ksh93/tests/local.sh | 339 ++++++++++++++++++++----------- src/cmd/ksh93/tests/types.sh | 2 +- 13 files changed, 266 insertions(+), 142 deletions(-) diff --git a/src/cmd/ksh93/bltins/misc.c b/src/cmd/ksh93/bltins/misc.c index 9f2e63a06be0..4e89011fae2a 100644 --- a/src/cmd/ksh93/bltins/misc.c +++ b/src/cmd/ksh93/bltins/misc.c @@ -2,7 +2,7 @@ * * * This software is part of the ast package * * Copyright (c) 1982-2012 AT&T Intellectual Property * -* Copyright (c) 2020-2023 Contributors to ksh 93u+m * +* Copyright (c) 2020-2024 Contributors to ksh 93u+m * * and is licensed under the * * Eclipse Public License, Version 2.0 * * * diff --git a/src/cmd/ksh93/bltins/read.c b/src/cmd/ksh93/bltins/read.c index 64debaadc6b7..8482a8b9d340 100644 --- a/src/cmd/ksh93/bltins/read.c +++ b/src/cmd/ksh93/bltins/read.c @@ -2,7 +2,7 @@ * * * This software is part of the ast package * * Copyright (c) 1982-2012 AT&T Intellectual Property * -* Copyright (c) 2020-2023 Contributors to ksh 93u+m * +* Copyright (c) 2020-2024 Contributors to ksh 93u+m * * and is licensed under the * * Eclipse Public License, Version 2.0 * * * diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index 10058c73eba3..4d2a0a98c848 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -224,7 +224,7 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) const char *optstring = sh_opttypeset; Namdecl_t *ntp = (Namdecl_t*)context->ptr; Dt_t *troot; - int isfloat=0, isadjust=0, shortint=0, sflag=0, local, declare; + int isfloat=0, isadjust=0, shortint=0, sflag=0, local, declare, scoping_flags = 0; memset(&tdata,0,sizeof(tdata)); troot = sh.var_tree; @@ -435,12 +435,19 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) flag |= (NV_EXPORT|NV_IDENT); break; case 'g': - flag &= ~NV_DYNAMIC; + flag &= ~(NV_SCOPES); flag |= NV_GLOBAL; + scoping_flags++; break; case 'D': - flag &= ~NV_GLOBAL; + flag &= ~(NV_SCOPES); flag |= NV_DYNAMIC; + scoping_flags++; + break; + case 'c': + flag &= ~(NV_SCOPES); + flag |= NV_STATSCOPE; + scoping_flags++; break; case ':': errormsg(SH_DICT,2, "%s", opt_info.arg); @@ -482,9 +489,9 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) errormsg(SH_DICT,2,e_optincompat1,"-m"); error_info.errors++; } - if((flag&NV_REF) && (flag&~(NV_REF|NV_IDENT|NV_ASSIGN|NV_GLOBAL|NV_DYNAMIC))) + if((flag&NV_REF) && (flag&~(NV_REF|NV_IDENT|NV_ASSIGN|NV_SCOPES))) { - errormsg(SH_DICT,2,e_optincompat2,"-n","other options except -g and -D"); + errormsg(SH_DICT,2,e_optincompat2,"-n","other options except -c, -D and -g"); error_info.errors++; } if((flag&NV_TYPE) && (flag&~(NV_TYPE|NV_VARNAME|NV_ASSIGN))) @@ -513,16 +520,21 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) errormsg(SH_DICT,ERROR_exit(2),"option argument cannot be greater than %d",SHRT_MAX); UNREACHABLE(); } - if((flag&(NV_GLOBAL|NV_DYNAMIC)) && sh.mktype) + if((flag&NV_SCOPES) && sh.mktype) + { + errormsg(SH_DICT,ERROR_exit(2),"type members cannot use the scoping flags -c, -D and -g"); + UNREACHABLE(); + } + if(scoping_flags > 1) { - errormsg(SH_DICT,ERROR_exit(2),"type members cannot be global or dynamic"); + errormsg(SH_DICT,ERROR_exit(2),"the scoping flags -c, -D and -g cannot be combined"); UNREACHABLE(); } if(troot==sh.var_tree && !sh.mktype && sh.infunction) { - if(sh.infunction==FUN_POSIX && !(local || declare) && !(flag&NV_DYNAMIC)) + if(sh.infunction==FUN_POSIX && !(local || declare) && !(flag&(NV_DYNAMIC|NV_STATSCOPE))) flag |= NV_GLOBAL; - else if((local || declare) && !(flag&NV_GLOBAL)) + else if((local || declare) && !(flag&(NV_GLOBAL|NV_STATSCOPE))) flag |= NV_DYNAMIC; } if(isfloat) diff --git a/src/cmd/ksh93/data/builtins.c b/src/cmd/ksh93/data/builtins.c index cf7a6ce3402c..8b2a69de5cdf 100644 --- a/src/cmd/ksh93/data/builtins.c +++ b/src/cmd/ksh93/data/builtins.c @@ -2,7 +2,7 @@ * * * This software is part of the ast package * * Copyright (c) 1982-2012 AT&T Intellectual Property * -* Copyright (c) 2020-2023 Contributors to ksh 93u+m * +* Copyright (c) 2020-2024 Contributors to ksh 93u+m * * and is licensed under the * * Eclipse Public License, Version 2.0 * * * @@ -1918,7 +1918,10 @@ const char sh_opttypeset[] = "[n?Name reference. The value is the name of a variable that \aname\a " "references. \aname\a cannot contain a \b.\b.]" "[p?Causes the output to be in a format that can be used as input to the " - "shell to recreate the attributes for variables.]" + "shell to recreate the attributes for variables. If this flag " + "is used by \btypeset\b in a POSIX function without also passing " + "\b-D\b or \b-c\b, the local scope is ignored and \btypeset\b will " + "only use the global scope.]" "[r?Enables readonly. Once enabled it cannot be disabled. See " "\breadonly\b(1).]" "[s?Used with \b-i\b to restrict integer size to short.]" @@ -1968,7 +1971,8 @@ const char sh_opttypeset[] = "unset prior to processing the assignment list.]" "[T]:?[tname?\atname\a is the name of a type name given to each \aname\a.]" "[Z]#?[n?Zero fill. If \an\a is given it represents the field width.]" -"[D?Gives the created or modified variable a dynamic scope in functions.]" +"[D?Modifies variables using a dynamic local scope in functions.]" +"[c?Modifies variables using a static local scope in functions.]" "\n" "\n[name[=value]...]\n" " -f [-tu] [name...]\n" diff --git a/src/cmd/ksh93/edit/emacs.c b/src/cmd/ksh93/edit/emacs.c index b90c8a840444..744089cd6f0a 100644 --- a/src/cmd/ksh93/edit/emacs.c +++ b/src/cmd/ksh93/edit/emacs.c @@ -2,7 +2,7 @@ * * * This software is part of the ast package * * Copyright (c) 1982-2012 AT&T Intellectual Property * -* Copyright (c) 2020-2023 Contributors to ksh 93u+m * +* Copyright (c) 2020-2024 Contributors to ksh 93u+m * * and is licensed under the * * Eclipse Public License, Version 2.0 * * * diff --git a/src/cmd/ksh93/include/builtins.h b/src/cmd/ksh93/include/builtins.h index ef483fe4236e..faa91ae879a5 100644 --- a/src/cmd/ksh93/include/builtins.h +++ b/src/cmd/ksh93/include/builtins.h @@ -2,7 +2,7 @@ * * * This software is part of the ast package * * Copyright (c) 1982-2012 AT&T Intellectual Property * -* Copyright (c) 2020-2023 Contributors to ksh 93u+m * +* Copyright (c) 2020-2024 Contributors to ksh 93u+m * * and is licensed under the * * Eclipse Public License, Version 2.0 * * * diff --git a/src/cmd/ksh93/include/nval.h b/src/cmd/ksh93/include/nval.h index f4d85d79ef14..2db6ff353308 100644 --- a/src/cmd/ksh93/include/nval.h +++ b/src/cmd/ksh93/include/nval.h @@ -2,7 +2,7 @@ * * * This software is part of the ast package * * Copyright (c) 1982-2012 AT&T Intellectual Property * -* Copyright (c) 2020-2022 Contributors to ksh 93u+m * +* Copyright (c) 2020-2024 Contributors to ksh 93u+m * * and is licensed under the * * Eclipse Public License, Version 2.0 * * * @@ -185,11 +185,13 @@ struct Namval #define NV_UNATTR 0x800000 /* unset attributes before assignment */ #define NV_GLOBAL 0x20000000 /* create global variable, ignoring local scope */ #define NV_DYNAMIC 0x40000000 /* create dynamically scoped variable */ +#define NV_STATSCOPE 0x80000000 /* force creation of statically scoped variable */ #define NV_FUNCT NV_IDENT /* option for nv_create */ #define NV_BLTINOPT NV_ZFILL /* mark builtins in libcmd */ #define NV_PUBLIC (~(NV_NOSCOPE|NV_ASSIGN|NV_IDENT|NV_VARNAME|NV_NOADD)) +#define NV_SCOPES (NV_DYNAMIC|NV_GLOBAL|NV_STATSCOPE) /* numeric types */ /* NV_INT16 and NV_UINT16 store values directly in the node; all the others use pointers */ diff --git a/src/cmd/ksh93/include/shell.h b/src/cmd/ksh93/include/shell.h index a49c4d85a9c2..6a23de5dc079 100644 --- a/src/cmd/ksh93/include/shell.h +++ b/src/cmd/ksh93/include/shell.h @@ -2,7 +2,7 @@ * * * This software is part of the ast package * * Copyright (c) 1982-2012 AT&T Intellectual Property * -* Copyright (c) 2020-2023 Contributors to ksh 93u+m * +* Copyright (c) 2020-2024 Contributors to ksh 93u+m * * and is licensed under the * * Eclipse Public License, Version 2.0 * * * diff --git a/src/cmd/ksh93/sh/main.c b/src/cmd/ksh93/sh/main.c index af44c80d0df1..a0db579e5134 100644 --- a/src/cmd/ksh93/sh/main.c +++ b/src/cmd/ksh93/sh/main.c @@ -2,7 +2,7 @@ * * * This software is part of the ast package * * Copyright (c) 1982-2012 AT&T Intellectual Property * -* Copyright (c) 2020-2023 Contributors to ksh 93u+m * +* Copyright (c) 2020-2024 Contributors to ksh 93u+m * * and is licensed under the * * Eclipse Public License, Version 2.0 * * * diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index a3949d2e68ed..03c35ccc3591 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -1027,6 +1027,8 @@ int sh_exec(const Shnode_t *t, int flags) flgs |= NV_GLOBAL; if(checkopt(com,'D')) flgs |= NV_DYNAMIC; + if(checkopt(com,'c')) + flgs |= NV_STATSCOPE; if(np==SYSNAMEREF || checkopt(com,'n')) flgs |= NV_NOREF; else if(argn>=3 && checkopt(com,'T')) @@ -1046,9 +1048,9 @@ int sh_exec(const Shnode_t *t, int flags) sh.prefix = NV_CLASS; flgs |= NV_TYPE; } - if((np==SYSLOCAL || np==SYSDECLARE) && !(flgs&NV_GLOBAL)) + if((np==SYSLOCAL || np==SYSDECLARE) && !(flgs&(NV_GLOBAL|NV_STATSCOPE))) flgs |= NV_DYNAMIC; - else if(sh.infunction==FUN_POSIX && !(flgs&NV_DYNAMIC)) + else if(sh.infunction==FUN_POSIX && !(flgs&(NV_DYNAMIC|NV_STATSCOPE))) flgs |= NV_GLOBAL; if((sh.infunction==FUN_POSIX || sh.fn_depth) && !sh.prefix) flgs |= NV_NOSCOPE; @@ -3040,8 +3042,7 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) * function (i.e., nested functions have no scoping.) This design flaw in * ksh93v- made its scoping code unsalvageable. */ - nv_scan(prevscope->save_tree, local_exports, NULL, NV_EXPORT, NV_EXPORT|NV_NOSCOPE); - nv_scan(prevscope->save_tree, local_exports, NULL, NV_DYNAMIC, NV_DYNAMIC|NV_NOSCOPE); + nv_scan(prevscope->save_tree, local_exports, NULL, 0, NV_EXPORT|NV_DYNAMIC|NV_NOSCOPE); } sh.st.save_tree = sh.var_tree; if(!fun) diff --git a/src/cmd/ksh93/tests/functions.sh b/src/cmd/ksh93/tests/functions.sh index 0f7a672b5d93..fe0e2aa66b0d 100755 --- a/src/cmd/ksh93/tests/functions.sh +++ b/src/cmd/ksh93/tests/functions.sh @@ -2,7 +2,7 @@ # # # This software is part of the ast package # # Copyright (c) 1982-2012 AT&T Intellectual Property # -# Copyright (c) 2020-2023 Contributors to ksh 93u+m # +# Copyright (c) 2020-2024 Contributors to ksh 93u+m # # and is licensed under the # # Eclipse Public License, Version 2.0 # # # diff --git a/src/cmd/ksh93/tests/local.sh b/src/cmd/ksh93/tests/local.sh index 9700c5d6a97d..b33702734818 100755 --- a/src/cmd/ksh93/tests/local.sh +++ b/src/cmd/ksh93/tests/local.sh @@ -87,373 +87,478 @@ for i in declare local; do "(expected globalscope, got $(printf %q "$bar"))" done +got=$(command declare -cDg invalid=cannotset 2>&1) +status=$? +((status == 2)) || err_exit "attempting to combine -c, -D and -g doesn't fail correctly" \ + "(returned exit status $status and output $(printf %q "$got"))" + # The declare builtin should work outside of functions unset foo declare foo=bar [[ $foo == bar ]] || err_exit "'declare' doesn't work outside of functions" -# TODO: Implement optional static scoping via a flag, for use in POSIX functions. +# Run all of the tests with 'command ' prefixes, as +# the underlying code treats 'typeset' and 'command typeset' +# differently (see sh_exec()). + +for command in "" "command"; do +# Beginning of non-indented loop + +unset prefix +[[ $command == command ]] && prefix="(using command(1)) " # Test 1: make dynamic $bar static with typeset(1) in ksh function tst=$tmp/tst.sh -cat > "$tst" << 'EOF' +cat > "$tst" << EOF function nxt { - echo $bar + echo \$bar } function foo { - local bar=1 + $command local bar=1 nxt - local bar=BAD1 + $command local bar=BAD1 function infun { - echo $bar + echo \$bar } # The current implementation does not make a seperate version of var for the static scope. # Rather, it changes foo()'s $bar variable back to a static scope, which prevents it from being # accessed by called functions as $bar is no longer in a dynamic scope. Consequently, both infun # and nxt are expected to print only a newline. - typeset bar=BAD2 + $command typeset bar=BAD2 infun nxt } foo -echo $bar +echo \$bar EOF exp=1 got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "Cannot switch from dynamic scope to static scope in ksh functions" \ +[[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from dynamic scope to static scope in ksh functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Test 2: make static $bar dynamic with local(1) in ksh function -cat > "$tst" << 'EOF' +cat > "$tst" << EOF function nxt { - echo $bar + echo \$bar } function foo { - typeset bar=BAD + $command typeset bar=BAD nxt - local bar=1 + $command local bar=1 function infun { - echo $bar + echo \$bar } infun } foo -echo ${bar}2 +echo \${bar}2 EOF exp=$'\n1\n2' got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "Cannot switch from static scope to dynamic scope in ksh functions" \ +[[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from static scope to dynamic scope in ksh functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Test 3: make dynamic $bar global with typeset(1) in POSIX function tst=$tmp/tst.sh -cat > "$tst" << 'EOF' +cat > "$tst" << EOF nxt() { - echo $bar + echo \$bar } foo() { - local bar=1 + $command local bar=1 nxt - typeset bar=3 - declare bar=2 + $command typeset bar=3 + $command declare bar=2 function infun { - echo $bar + echo \$bar } infun } foo -echo $bar +echo \$bar EOF exp=$'1\n2\n3' got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "Cannot switch from dynamic scope to global scope in POSIX functions" \ +[[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from dynamic scope to global scope in POSIX functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Test 4: make local duplicate of global bar created with typeset in POSIX function -cat > "$tst" << 'EOF' +cat > "$tst" << EOF nxt() { - echo $bar + echo \$bar } foo() { - typeset bar=1 + $command typeset bar=1 nxt - typeset bar=3 - local bar=2 + $command typeset bar=3 + $command local bar=2 function infun { - echo $bar + echo \$bar } infun } foo -echo $bar +echo \$bar EOF exp=$'1\n2\n3' got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "Cannot create local version of \$bar in POSIX functions" \ +[[ $exp == "$got" ]] || err_exit "${prefix}Cannot create local version of \$bar in POSIX functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Test 5: ensure local really works in POSIX functions -cat > "$tst" << 'EOF' +cat > "$tst" << EOF nxt() { - echo $bar + echo \$bar } foo() { - local bar=1 + $command local bar=1 nxt - local bar=2 + $command local bar=2 function infun { - echo $bar + echo \$bar } infun - local bar=BAD + $command local bar=BAD } foo -echo ${bar}3 +echo \${bar}3 EOF exp=$'1\n2\n3' got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "Cannot create local variables in POSIX functions" \ +[[ $exp == "$got" ]] || err_exit "${prefix}Cannot create local variables in POSIX functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Test 6: ensure typeset doesn't default to making static variables in POSIX functions -cat > "$tst" << 'EOF' +cat > "$tst" << EOF nxt() { - echo $bar + echo \$bar } foo() { - typeset bar=1 + $command typeset bar=1 nxt - typeset bar=2 + $command typeset bar=2 function infun { - echo $bar + echo \$bar } infun - typeset bar=3 + $command typeset bar=3 } foo -echo $bar +echo \$bar EOF exp=$'1\n2\n3' got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "Cannot create global variables with plain typeset invocation in POSIX functions" \ +[[ $exp == "$got" ]] || err_exit "${prefix}Cannot create global variables with plain typeset invocation in POSIX functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Test 7 -cat > "$tst" << 'EOF' +cat > "$tst" << EOF nxt() { - echo $bar + echo \$bar } foo() { - typeset bar=1 + $command typeset bar=1 nxt - typeset bar=2 + $command typeset bar=2 infun() { - echo $bar + echo \$bar } infun - typeset bar=3 + $command typeset bar=3 } foo -echo $bar +echo \$bar EOF exp=$'1\n2\n3' got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "Cannot create global variables with plain typeset invocation in POSIX functions with nested POSIX functions" \ +[[ $exp == "$got" ]] || err_exit "${prefix}Cannot create global variables with plain typeset invocation in POSIX functions with nested POSIX functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Test 8 -cat > "$tst" << 'EOF' +cat > "$tst" << EOF nxt() { - echo $bar + echo \$bar } foo() { - local bar=1 + $command local bar=1 nxt - local bar=2 + $command local bar=2 infun() { - echo $bar + echo \$bar } infun - local bar=BAD + $command local bar=BAD } foo -echo ${bar}3 +echo \${bar}3 EOF exp=$'1\n2\n3' got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "Cannot create local variables with local builtin in POSIX functions" \ +[[ $exp == "$got" ]] || err_exit "${prefix}Cannot create local variables with local builtin in POSIX functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Test 9 -cat > "$tst" << 'EOF' +cat > "$tst" << EOF nxt() { - echo $bar + echo \$bar } foo() { bar=1 nxt bar=2 function infun { - echo $bar + echo \$bar } infun bar=3 } foo -echo $bar +echo \$bar EOF exp=$'1\n2\n3' got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "Cannot create global variables in POSIX functions without direct typeset invocation" \ +[[ $exp == "$got" ]] || err_exit "${prefix}Cannot create global variables in POSIX functions without direct typeset invocation" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Test 10: Make static variable global in KornShell function tst=$tmp/tst.sh -cat > "$tst" << 'EOF' +cat > "$tst" << EOF function nxt { - echo ${bar}1 + echo \${bar}1 } function foo { - typeset bar=BAD + $command typeset bar=BAD nxt - typeset -g bar=2 + $command typeset -g bar=2 function infun { - echo $bar + echo \$bar } infun - typeset -g bar=3 + $command typeset -g bar=3 } foo -echo $bar +echo \$bar EOF exp=$'1\n2\n3' got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "Cannot switch from static scope to global scope in KornShell functions" \ +[[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from static scope to global scope in KornShell functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Test 11: Make dynamic variables global in KornShell functions tst=$tmp/tst.sh -cat > "$tst" << 'EOF' +cat > "$tst" << EOF function nxt { - echo ${bar} + echo \${bar} } function foo { - local bar=1 + $command local bar=1 nxt - local -g bar=3 - local bar=2 + $command local -g bar=3 + $command local bar=2 function infun { # The dynamic scope still applies, so the $bar value # from 'function foo' is inherited and used instead # of the global value. - echo $bar + echo \$bar } infun } foo # This will be '3' because of the earlier 'local -g' -echo $bar +echo \$bar EOF exp=$'1\n2\n3' got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "Cannot switch from dynamic scope to global scope in KornShell functions" \ +[[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from dynamic scope to global scope in KornShell functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Test 12: Variables shouldn't leak out of nested POSIX functions tst=$tmp/tst.sh -cat > "$tst" << 'EOF' +cat > "$tst" << EOF foo() { - local foo=foo + $command local foo=foo bar() { - local foo=bar + $command local foo=bar baz() { - local foo=baz - echo $foo + $command local foo=baz + echo \$foo } baz - echo $foo + echo \$foo } bar - echo $foo + echo \$foo } foo EOF exp=$'baz\nbar\nfoo' got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "Local variables from nested POSIX functions leak out into the parent functions" \ +[[ $exp == "$got" ]] || err_exit "${prefix}Local variables from nested POSIX functions leak out into the parent functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Test 13: Variables shouldn't leak out of nested KornShell functions tst=$tmp/tst.sh -cat > "$tst" << 'EOF' +cat > "$tst" << EOF function foo { - local foo=foo + $command local foo=foo function bar { - local foo=bar + $command local foo=bar function baz { - local foo=baz - echo $foo + $command local foo=baz + echo \$foo } baz - echo $foo + echo \$foo } bar - echo $foo + echo \$foo } foo EOF exp=$'baz\nbar\nfoo' got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "Local variables from nested KornShell functions leak out into the parent functions" \ +[[ $exp == "$got" ]] || err_exit "${prefix}Local variables from nested KornShell functions leak out into the parent functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Test 14: Variables shouldn't leak out into other POSIX functions tst=$tmp/tst.sh -cat > "$tst" << 'EOF' +cat > "$tst" << EOF baz() { - local foo=baz - echo $foo + $command local foo=baz + echo \$foo } bar() { - local foo=bar + $command local foo=bar baz - echo $foo + echo \$foo } foo() { - local foo=foo + $command local foo=foo bar - echo $foo + echo \$foo } foo EOF exp=$'baz\nbar\nfoo' got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "Local variables from POSIX functions leak out into other functions" \ +[[ $exp == "$got" ]] || err_exit "${prefix}Local variables from POSIX functions leak out into other functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Test 15: Variables shouldn't leak out into other KornShell functions tst=$tmp/tst.sh -cat > "$tst" << 'EOF' +cat > "$tst" << EOF function baz { - local foo=baz - echo $foo + $command local foo=baz + echo \$foo } function bar { - local foo=bar + $command local foo=bar baz - echo $foo + echo \$foo } function foo { - local foo=foo + $command local foo=foo bar - echo $foo + echo \$foo } foo EOF exp=$'baz\nbar\nfoo' got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "Local variables from KornShell functions leak out into other functions" \ +[[ $exp == "$got" ]] || err_exit "${prefix}Local variables from KornShell functions leak out into other functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + +# Test 16: 'typeset -c' should use static scoping in POSIX functions +tst=$tmp/tst.sh +cat > "$tst" << EOF +foo=global +bar() { + echo \$foo + $command typeset -c foo=static2 + echo \$foo +} +foo() { + $command typeset -c foo=static + echo \$foo + bar + echo \$foo +} +echo \$foo +foo +echo \$foo +EOF +exp=$'global\nstatic\nglobal\nstatic2\nstatic\nglobal' +got=$("$SHELL" "$tst") +[[ $exp == "$got" ]] || err_exit "${prefix}Cannot use static scoping in POSIX functions with 'local -c'" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + +# Test 17: 'local -c' should use static scoping in KornShell functions +tst=$tmp/tst.sh +cat > "$tst" << EOF +foo=global +function bar { + echo \$foo + $command local -c foo=static2 + echo \$foo +} +function foo { + $command local -c foo=static + echo \$foo + bar + echo \$foo +} +echo \$foo +foo +echo \$foo +EOF +exp=$'global\nstatic\nglobal\nstatic2\nstatic\nglobal' +got=$("$SHELL" "$tst") +[[ $exp == "$got" ]] || err_exit "${prefix}Cannot use static scoping in KornShell functions with 'local -c'" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" +# Test 18: Make static $bar dynamic in POSIX functions +cat > "$tst" << EOF +nxt() { + echo \$bar +} +foo() { + $command typeset -c bar=BAD + nxt + $command local bar=1 + function infun { + echo \$bar + } + infun +} +foo +echo \${bar}2 +EOF +exp=$'\n1\n2' +got=$("$SHELL" "$tst") +[[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from static scope to dynamic scope in ksh functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + +# Test 19: Make static $bar separate from global $bar in POSIX functions +cat > "$tst" << EOF +typeset bar=global +nxt() { + echo \$bar +} +foo() { + $command local -c bar=static + nxt + echo \$bar +} +echo \${bar} +foo +echo \${bar} +EOF +exp=$'global\nglobal\nstatic\nglobal' +got=$("$SHELL" "$tst") +[[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from static scope to dynamic scope in ksh functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + +done # End of non-indented loop + # ====== exit $((Errors<125?Errors:125)) diff --git a/src/cmd/ksh93/tests/types.sh b/src/cmd/ksh93/tests/types.sh index 83cf2b96e6be..448e500394cd 100755 --- a/src/cmd/ksh93/tests/types.sh +++ b/src/cmd/ksh93/tests/types.sh @@ -2,7 +2,7 @@ # # # This software is part of the ast package # # Copyright (c) 1982-2012 AT&T Intellectual Property # -# Copyright (c) 2020-2023 Contributors to ksh 93u+m # +# Copyright (c) 2020-2024 Contributors to ksh 93u+m # # and is licensed under the # # Eclipse Public License, Version 2.0 # # # From 83d132471cb9cb0afb6ab9f551f3327871a2a09f Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Tue, 16 Jan 2024 16:11:39 -0800 Subject: [PATCH 28/42] Improve comments in the test loop --- src/cmd/ksh93/tests/local.sh | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/cmd/ksh93/tests/local.sh b/src/cmd/ksh93/tests/local.sh index b33702734818..e50e13b186aa 100755 --- a/src/cmd/ksh93/tests/local.sh +++ b/src/cmd/ksh93/tests/local.sh @@ -107,7 +107,7 @@ for command in "" "command"; do unset prefix [[ $command == command ]] && prefix="(using command(1)) " -# Test 1: make dynamic $bar static with typeset(1) in ksh function +# Test 1: Make dynamic $bar static with typeset(1) in ksh functions tst=$tmp/tst.sh cat > "$tst" << EOF function nxt { @@ -136,7 +136,7 @@ got=$("$SHELL" "$tst") [[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from dynamic scope to static scope in ksh functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" -# Test 2: make static $bar dynamic with local(1) in ksh function +# Test 2: Make static $bar dynamic with local(1) in ksh functions cat > "$tst" << EOF function nxt { echo \$bar @@ -158,7 +158,7 @@ got=$("$SHELL" "$tst") [[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from static scope to dynamic scope in ksh functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" -# Test 3: make dynamic $bar global with typeset(1) in POSIX function +# Test 3: Make dynamic $bar global with typeset(1) in POSIX functions tst=$tmp/tst.sh cat > "$tst" << EOF nxt() { @@ -182,7 +182,7 @@ got=$("$SHELL" "$tst") [[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from dynamic scope to global scope in POSIX functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" -# Test 4: make local duplicate of global bar created with typeset in POSIX function +# Test 4: Make dynamic local bar separate from global bar created with typeset in POSIX functions cat > "$tst" << EOF nxt() { echo \$bar @@ -205,7 +205,7 @@ got=$("$SHELL" "$tst") [[ $exp == "$got" ]] || err_exit "${prefix}Cannot create local version of \$bar in POSIX functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" -# Test 5: ensure local really works in POSIX functions +# Test 5: Ensure local actually works in POSIX functions cat > "$tst" << EOF nxt() { echo \$bar @@ -228,7 +228,7 @@ got=$("$SHELL" "$tst") [[ $exp == "$got" ]] || err_exit "${prefix}Cannot create local variables in POSIX functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" -# Test 6: ensure typeset doesn't default to making static variables in POSIX functions +# Test 6: Ensure typeset doesn't default to making static variables in POSIX functions cat > "$tst" << EOF nxt() { echo \$bar @@ -251,7 +251,7 @@ got=$("$SHELL" "$tst") [[ $exp == "$got" ]] || err_exit "${prefix}Cannot create global variables with plain typeset invocation in POSIX functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" -# Test 7 +# Test 7: A plain typeset invocation should use the global scope for maximum backward compatibility cat > "$tst" << EOF nxt() { echo \$bar @@ -274,7 +274,7 @@ got=$("$SHELL" "$tst") [[ $exp == "$got" ]] || err_exit "${prefix}Cannot create global variables with plain typeset invocation in POSIX functions with nested POSIX functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" -# Test 8 +# Test 8: A plain invocation of local or declare should create a dynamic local variable cat > "$tst" << EOF nxt() { echo \$bar @@ -282,7 +282,7 @@ nxt() { foo() { $command local bar=1 nxt - $command local bar=2 + $command declare bar=2 infun() { echo \$bar } @@ -297,7 +297,7 @@ got=$("$SHELL" "$tst") [[ $exp == "$got" ]] || err_exit "${prefix}Cannot create local variables with local builtin in POSIX functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" -# Test 9 +# Test 9: Create global variables without directly invoking typeset cat > "$tst" << EOF nxt() { echo \$bar @@ -320,7 +320,7 @@ got=$("$SHELL" "$tst") [[ $exp == "$got" ]] || err_exit "${prefix}Cannot create global variables in POSIX functions without direct typeset invocation" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" -# Test 10: Make static variable global in KornShell function +# Test 10: Make static variables global in KornShell functions tst=$tmp/tst.sh cat > "$tst" << EOF function nxt { @@ -372,7 +372,7 @@ got=$("$SHELL" "$tst") [[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from dynamic scope to global scope in KornShell functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" -# Test 12: Variables shouldn't leak out of nested POSIX functions +# Test 12: Dynamic variables shouldn't leak out of nested POSIX functions tst=$tmp/tst.sh cat > "$tst" << EOF foo() { @@ -420,7 +420,7 @@ got=$("$SHELL" "$tst") [[ $exp == "$got" ]] || err_exit "${prefix}Local variables from nested KornShell functions leak out into the parent functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" -# Test 14: Variables shouldn't leak out into other POSIX functions +# Test 14: Dynamic variables shouldn't leak out into other POSIX functions tst=$tmp/tst.sh cat > "$tst" << EOF baz() { @@ -535,7 +535,7 @@ echo \${bar}2 EOF exp=$'\n1\n2' got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from static scope to dynamic scope in ksh functions" \ +[[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from static scope to dynamic scope in POSIX functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Test 19: Make static $bar separate from global $bar in POSIX functions @@ -555,7 +555,7 @@ echo \${bar} EOF exp=$'global\nglobal\nstatic\nglobal' got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from static scope to dynamic scope in ksh functions" \ +[[ $exp == "$got" ]] || err_exit "${prefix}Cannot separate static and global scopes in POSIX functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" done # End of non-indented loop From 189736a860e9870be52726edda5f4ee1bde87331 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Tue, 16 Jan 2024 16:41:35 -0800 Subject: [PATCH 29/42] Add regression test for functions run as dot scripts --- src/cmd/ksh93/tests/local.sh | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/cmd/ksh93/tests/local.sh b/src/cmd/ksh93/tests/local.sh index e50e13b186aa..be6f3c40451f 100755 --- a/src/cmd/ksh93/tests/local.sh +++ b/src/cmd/ksh93/tests/local.sh @@ -558,6 +558,30 @@ got=$("$SHELL" "$tst") [[ $exp == "$got" ]] || err_exit "${prefix}Cannot separate static and global scopes in POSIX functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" +# Test 20: Ksh functions run by '.' should not have any form of scoping +cat > "$tst" << EOF +function nxt { + echo \$bar +} +function foo { + $command typeset bar=1 + nxt + $command local foo=2 + function infun { + echo \$foo + } + infun + $command declare -c baz=3 + $command typeset -g bear=4 +} +. foo +print \$bar \$foo \$baz \$bear +EOF +exp=$'1\n2\n1 2 3 4' +got=$("$SHELL" "$tst") +[[ $exp == "$got" ]] || err_exit "${prefix}KornShell functions executed directly by '.' shouldn't have scoping" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + done # End of non-indented loop # ====== From c08f2bf48200d6d4afdcb38530021919befc20c2 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Wed, 17 Jan 2024 02:17:50 -0800 Subject: [PATCH 30/42] Fix compiler warning --- src/cmd/ksh93/sh/xec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index 03c35ccc3591..e1086ab17f52 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -3177,7 +3177,7 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) */ if(posix_fun) { - if(sh.st.self != &savst) + if(sh.st.self != savst) *sh.st.self = sh.st; /* Only restore the top Shscope_t portion for POSIX functions */ memcpy(&sh.st, prevscope, sizeof(Shscope_t)); From 31d2d240414565bb994a854069b50191c5bb506e Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Wed, 17 Jan 2024 02:55:08 -0800 Subject: [PATCH 31/42] Use sh.infunction more widely when applicable --- src/cmd/ksh93/bltins/cflow.c | 2 +- src/cmd/ksh93/bltins/trap.c | 2 +- src/cmd/ksh93/bltins/typeset.c | 2 +- src/cmd/ksh93/sh/macro.c | 2 +- src/cmd/ksh93/sh/xec.c | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/cmd/ksh93/bltins/cflow.c b/src/cmd/ksh93/bltins/cflow.c index b47fba64b6df..ef06dc3a4bb7 100644 --- a/src/cmd/ksh93/bltins/cflow.c +++ b/src/cmd/ksh93/bltins/cflow.c @@ -44,7 +44,7 @@ int b_return(int n, char *argv[],Shbltin_t *context) { /* 'return' outside of function, dotscript and profile behaves like 'exit' */ - char do_exit = **argv=='e' || sh.fn_depth==0 && sh.dot_depth==0 && !sh_isstate(SH_PROFILE); + char do_exit = **argv=='e' || !sh.infunction && !sh_isstate(SH_PROFILE); NOT_USED(context); while((n = optget(argv, **argv=='e' ? sh_optexit : sh_optreturn))) switch(n) { diff --git a/src/cmd/ksh93/bltins/trap.c b/src/cmd/ksh93/bltins/trap.c index 5ddd44686d9a..d86b86c17d8a 100644 --- a/src/cmd/ksh93/bltins/trap.c +++ b/src/cmd/ksh93/bltins/trap.c @@ -174,7 +174,7 @@ int b_trap(int argc,char *argv[],Shbltin_t *context) * Set a flag for sh_exec() to disable exec-without-fork optimizations if any trap is set and non-empty. * (In ksh functions, there may be parent scope traps, so do not reset to 0 if in a ksh function.) */ - if(sh.fn_depth==0) + if(sh.infunction!=FUN_KSH) sh.st.trapdontexec = 0; if(!sh.st.trapdontexec) { diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index 4d2a0a98c848..c48e19baff7e 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -546,7 +546,7 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) else if(!sh.typeinit) flag |= NV_STATIC|NV_IDENT; } - if(sh.fn_depth && !tdata.pflag) + if(sh.infunction < FUN_KSHDOT && !tdata.pflag) flag |= NV_NOSCOPE; if(tdata.help) tdata.help = sh_strdup(tdata.help); diff --git a/src/cmd/ksh93/sh/macro.c b/src/cmd/ksh93/sh/macro.c index 5a58a5c47313..ba85a12437b4 100644 --- a/src/cmd/ksh93/sh/macro.c +++ b/src/cmd/ksh93/sh/macro.c @@ -2856,7 +2856,7 @@ static char *special(int c) case '?': return ltos(sh.savexit); case 0: - if(sh_isstate(SH_PROFILE) || sh.fn_depth==0 || !sh.st.cmdname) + if(sh_isstate(SH_PROFILE) || sh.infunction!=FUN_KSH || !sh.st.cmdname) return sh.shname; else return sh.st.cmdname; diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index e1086ab17f52..f0b9fe67ad78 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -848,7 +848,7 @@ static Namval_t *enter_namespace(Namval_t *nsp) static int check_exec_optimization(int type, int execflg, int execflg2, struct ionod *iop) { if(type&(FAMP|FPOU) - || !(execflg && sh.fn_depth==0 || execflg2) + || !(execflg && sh.infunction != FUN_KSH || execflg2) || sh.st.trapdontexec || sh.subshell || ((struct checkpt*)sh.jmplist)->mode==SH_JMPEVAL @@ -1052,7 +1052,7 @@ int sh_exec(const Shnode_t *t, int flags) flgs |= NV_DYNAMIC; else if(sh.infunction==FUN_POSIX && !(flgs&(NV_DYNAMIC|NV_STATSCOPE))) flgs |= NV_GLOBAL; - if((sh.infunction==FUN_POSIX || sh.fn_depth) && !sh.prefix) + if(sh.infunction < FUN_KSHDOT && !sh.prefix) flgs |= NV_NOSCOPE; } else if(np==SYSEXPORT) From 2a1ec619514780ea3e76614ea68390bbc266bce7 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Wed, 17 Jan 2024 03:05:36 -0800 Subject: [PATCH 32/42] Parital revert to fix a test failure in functions.sh --- src/cmd/ksh93/bltins/trap.c | 2 +- src/cmd/ksh93/sh/macro.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cmd/ksh93/bltins/trap.c b/src/cmd/ksh93/bltins/trap.c index d86b86c17d8a..5ddd44686d9a 100644 --- a/src/cmd/ksh93/bltins/trap.c +++ b/src/cmd/ksh93/bltins/trap.c @@ -174,7 +174,7 @@ int b_trap(int argc,char *argv[],Shbltin_t *context) * Set a flag for sh_exec() to disable exec-without-fork optimizations if any trap is set and non-empty. * (In ksh functions, there may be parent scope traps, so do not reset to 0 if in a ksh function.) */ - if(sh.infunction!=FUN_KSH) + if(sh.fn_depth==0) sh.st.trapdontexec = 0; if(!sh.st.trapdontexec) { diff --git a/src/cmd/ksh93/sh/macro.c b/src/cmd/ksh93/sh/macro.c index ba85a12437b4..5a58a5c47313 100644 --- a/src/cmd/ksh93/sh/macro.c +++ b/src/cmd/ksh93/sh/macro.c @@ -2856,7 +2856,7 @@ static char *special(int c) case '?': return ltos(sh.savexit); case 0: - if(sh_isstate(SH_PROFILE) || sh.infunction!=FUN_KSH || !sh.st.cmdname) + if(sh_isstate(SH_PROFILE) || sh.fn_depth==0 || !sh.st.cmdname) return sh.shname; else return sh.st.cmdname; From 6d5b2fac3ea28a5ff9ff5f21ee4634a9c8ff25b5 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Wed, 17 Jan 2024 07:33:34 -0800 Subject: [PATCH 33/42] Indent the large for loop --- src/cmd/ksh93/tests/local.sh | 873 +++++++++++++++++------------------ 1 file changed, 435 insertions(+), 438 deletions(-) diff --git a/src/cmd/ksh93/tests/local.sh b/src/cmd/ksh93/tests/local.sh index be6f3c40451f..1358ed91a5ce 100755 --- a/src/cmd/ksh93/tests/local.sh +++ b/src/cmd/ksh93/tests/local.sh @@ -102,487 +102,484 @@ declare foo=bar # differently (see sh_exec()). for command in "" "command"; do -# Beginning of non-indented loop + unset prefix + [[ $command == command ]] && prefix="(using command(1)) " -unset prefix -[[ $command == command ]] && prefix="(using command(1)) " - -# Test 1: Make dynamic $bar static with typeset(1) in ksh functions -tst=$tmp/tst.sh -cat > "$tst" << EOF -function nxt { - echo \$bar -} -function foo { - $command local bar=1 - nxt - $command local bar=BAD1 - function infun { + # Test 1: Make dynamic $bar static with typeset(1) in ksh functions + tst=$tmp/tst.sh + cat > "$tst" <<-EOF + function nxt { echo \$bar } - # The current implementation does not make a seperate version of var for the static scope. - # Rather, it changes foo()'s $bar variable back to a static scope, which prevents it from being - # accessed by called functions as $bar is no longer in a dynamic scope. Consequently, both infun - # and nxt are expected to print only a newline. - $command typeset bar=BAD2 - infun - nxt -} -foo -echo \$bar -EOF -exp=1 -got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from dynamic scope to static scope in ksh functions" \ - "(expected $(printf %q "$exp"), got $(printf %q "$got"))" - -# Test 2: Make static $bar dynamic with local(1) in ksh functions -cat > "$tst" << EOF -function nxt { - echo \$bar -} -function foo { - $command typeset bar=BAD - nxt - $command local bar=1 - function infun { - echo \$bar + function foo { + $command local bar=1 + nxt + $command local bar=BAD1 + function infun { + echo \$bar + } + # The current implementation does not make a seperate version of var for the static scope. + # Rather, it changes foo()'s $bar variable back to a static scope, which prevents it from being + # accessed by called functions as $bar is no longer in a dynamic scope. Consequently, both infun + # and nxt are expected to print only a newline. + $command typeset bar=BAD2 + infun + nxt } - infun -} -foo -echo \${bar}2 -EOF -exp=$'\n1\n2' -got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from static scope to dynamic scope in ksh functions" \ - "(expected $(printf %q "$exp"), got $(printf %q "$got"))" - -# Test 3: Make dynamic $bar global with typeset(1) in POSIX functions -tst=$tmp/tst.sh -cat > "$tst" << EOF -nxt() { + foo echo \$bar -} -foo() { - $command local bar=1 - nxt - $command typeset bar=3 - $command declare bar=2 - function infun { + EOF + exp=1 + got=$("$SHELL" "$tst") + [[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from dynamic scope to static scope in ksh functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + + # Test 2: Make static $bar dynamic with local(1) in ksh functions + cat > "$tst" <<-EOF + function nxt { echo \$bar } - infun -} -foo -echo \$bar -EOF -exp=$'1\n2\n3' -got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from dynamic scope to global scope in POSIX functions" \ - "(expected $(printf %q "$exp"), got $(printf %q "$got"))" - -# Test 4: Make dynamic local bar separate from global bar created with typeset in POSIX functions -cat > "$tst" << EOF -nxt() { - echo \$bar -} -foo() { - $command typeset bar=1 - nxt - $command typeset bar=3 - $command local bar=2 - function infun { + function foo { + $command typeset bar=BAD + nxt + $command local bar=1 + function infun { + echo \$bar + } + infun + } + foo + echo \${bar}2 + EOF + exp=$'\n1\n2' + got=$("$SHELL" "$tst") + [[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from static scope to dynamic scope in ksh functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + + # Test 3: Make dynamic $bar global with typeset(1) in POSIX functions + tst=$tmp/tst.sh + cat > "$tst" <<-EOF + nxt() { echo \$bar } - infun -} -foo -echo \$bar -EOF -exp=$'1\n2\n3' -got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "${prefix}Cannot create local version of \$bar in POSIX functions" \ - "(expected $(printf %q "$exp"), got $(printf %q "$got"))" - -# Test 5: Ensure local actually works in POSIX functions -cat > "$tst" << EOF -nxt() { + foo() { + $command local bar=1 + nxt + $command typeset bar=3 + $command declare bar=2 + function infun { + echo \$bar + } + infun + } + foo echo \$bar -} -foo() { - $command local bar=1 - nxt - $command local bar=2 - function infun { + EOF + exp=$'1\n2\n3' + got=$("$SHELL" "$tst") + [[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from dynamic scope to global scope in POSIX functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + + # Test 4: Make dynamic local bar separate from global bar created with typeset in POSIX functions + cat > "$tst" <<-EOF + nxt() { echo \$bar } - infun - $command local bar=BAD -} -foo -echo \${bar}3 -EOF -exp=$'1\n2\n3' -got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "${prefix}Cannot create local variables in POSIX functions" \ - "(expected $(printf %q "$exp"), got $(printf %q "$got"))" - -# Test 6: Ensure typeset doesn't default to making static variables in POSIX functions -cat > "$tst" << EOF -nxt() { + foo() { + $command typeset bar=1 + nxt + $command typeset bar=3 + $command local bar=2 + function infun { + echo \$bar + } + infun + } + foo echo \$bar -} -foo() { - $command typeset bar=1 - nxt - $command typeset bar=2 - function infun { + EOF + exp=$'1\n2\n3' + got=$("$SHELL" "$tst") + [[ $exp == "$got" ]] || err_exit "${prefix}Cannot create local version of \$bar in POSIX functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + + # Test 5: Ensure local actually works in POSIX functions + cat > "$tst" <<-EOF + nxt() { echo \$bar } - infun - $command typeset bar=3 -} -foo -echo \$bar -EOF -exp=$'1\n2\n3' -got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "${prefix}Cannot create global variables with plain typeset invocation in POSIX functions" \ - "(expected $(printf %q "$exp"), got $(printf %q "$got"))" - -# Test 7: A plain typeset invocation should use the global scope for maximum backward compatibility -cat > "$tst" << EOF -nxt() { - echo \$bar -} -foo() { - $command typeset bar=1 - nxt - $command typeset bar=2 - infun() { + foo() { + $command local bar=1 + nxt + $command local bar=2 + function infun { + echo \$bar + } + infun + $command local bar=BAD + } + foo + echo \${bar}3 + EOF + exp=$'1\n2\n3' + got=$("$SHELL" "$tst") + [[ $exp == "$got" ]] || err_exit "${prefix}Cannot create local variables in POSIX functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + + # Test 6: Ensure typeset doesn't default to making static variables in POSIX functions + cat > "$tst" <<-EOF + nxt() { echo \$bar } - infun - $command typeset bar=3 -} -foo -echo \$bar -EOF -exp=$'1\n2\n3' -got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "${prefix}Cannot create global variables with plain typeset invocation in POSIX functions with nested POSIX functions" \ - "(expected $(printf %q "$exp"), got $(printf %q "$got"))" - -# Test 8: A plain invocation of local or declare should create a dynamic local variable -cat > "$tst" << EOF -nxt() { + foo() { + $command typeset bar=1 + nxt + $command typeset bar=2 + function infun { + echo \$bar + } + infun + $command typeset bar=3 + } + foo echo \$bar -} -foo() { - $command local bar=1 - nxt - $command declare bar=2 - infun() { + EOF + exp=$'1\n2\n3' + got=$("$SHELL" "$tst") + [[ $exp == "$got" ]] || err_exit "${prefix}Cannot create global variables with plain typeset invocation in POSIX functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + + # Test 7: A plain typeset invocation should use the global scope for maximum backward compatibility + cat > "$tst" <<-EOF + nxt() { echo \$bar } - infun - $command local bar=BAD -} -foo -echo \${bar}3 -EOF -exp=$'1\n2\n3' -got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "${prefix}Cannot create local variables with local builtin in POSIX functions" \ - "(expected $(printf %q "$exp"), got $(printf %q "$got"))" - -# Test 9: Create global variables without directly invoking typeset -cat > "$tst" << EOF -nxt() { + foo() { + $command typeset bar=1 + nxt + $command typeset bar=2 + infun() { + echo \$bar + } + infun + $command typeset bar=3 + } + foo echo \$bar -} -foo() { - bar=1 - nxt - bar=2 - function infun { + EOF + exp=$'1\n2\n3' + got=$("$SHELL" "$tst") + [[ $exp == "$got" ]] || err_exit "${prefix}Cannot create global variables with plain typeset invocation in POSIX functions with nested POSIX functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + + # Test 8: A plain invocation of local or declare should create a dynamic local variable + cat > "$tst" <<-EOF + nxt() { echo \$bar } - infun - bar=3 -} -foo -echo \$bar -EOF -exp=$'1\n2\n3' -got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "${prefix}Cannot create global variables in POSIX functions without direct typeset invocation" \ - "(expected $(printf %q "$exp"), got $(printf %q "$got"))" - -# Test 10: Make static variables global in KornShell functions -tst=$tmp/tst.sh -cat > "$tst" << EOF -function nxt { - echo \${bar}1 -} -function foo { - $command typeset bar=BAD - nxt - $command typeset -g bar=2 - function infun { - echo \$bar + foo() { + $command local bar=1 + nxt + $command declare bar=2 + infun() { + echo \$bar + } + infun + $command local bar=BAD } - infun - $command typeset -g bar=3 -} -foo -echo \$bar -EOF -exp=$'1\n2\n3' -got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from static scope to global scope in KornShell functions" \ - "(expected $(printf %q "$exp"), got $(printf %q "$got"))" - -# Test 11: Make dynamic variables global in KornShell functions -tst=$tmp/tst.sh -cat > "$tst" << EOF -function nxt { - echo \${bar} -} -function foo { - $command local bar=1 - nxt - $command local -g bar=3 - $command local bar=2 - function infun { - # The dynamic scope still applies, so the $bar value - # from 'function foo' is inherited and used instead - # of the global value. + foo + echo \${bar}3 + EOF + exp=$'1\n2\n3' + got=$("$SHELL" "$tst") + [[ $exp == "$got" ]] || err_exit "${prefix}Cannot create local variables with local builtin in POSIX functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + + # Test 9: Create global variables without directly invoking typeset + cat > "$tst" <<-EOF + nxt() { echo \$bar } - infun -} -foo -# This will be '3' because of the earlier 'local -g' -echo \$bar -EOF -exp=$'1\n2\n3' -got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from dynamic scope to global scope in KornShell functions" \ - "(expected $(printf %q "$exp"), got $(printf %q "$got"))" - -# Test 12: Dynamic variables shouldn't leak out of nested POSIX functions -tst=$tmp/tst.sh -cat > "$tst" << EOF -foo() { - $command local foo=foo - bar() { - $command local foo=bar - baz() { - $command local foo=baz + foo() { + bar=1 + nxt + bar=2 + function infun { + echo \$bar + } + infun + bar=3 + } + foo + echo \$bar + EOF + exp=$'1\n2\n3' + got=$("$SHELL" "$tst") + [[ $exp == "$got" ]] || err_exit "${prefix}Cannot create global variables in POSIX functions without direct typeset invocation" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + + # Test 10: Make static variables global in KornShell functions + tst=$tmp/tst.sh + cat > "$tst" <<-EOF + function nxt { + echo \${bar}1 + } + function foo { + $command typeset bar=BAD + nxt + $command typeset -g bar=2 + function infun { + echo \$bar + } + infun + $command typeset -g bar=3 + } + foo + echo \$bar + EOF + exp=$'1\n2\n3' + got=$("$SHELL" "$tst") + [[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from static scope to global scope in KornShell functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + + # Test 11: Make dynamic variables global in KornShell functions + tst=$tmp/tst.sh + cat > "$tst" <<-EOF + function nxt { + echo \${bar} + } + function foo { + $command local bar=1 + nxt + $command local -g bar=3 + $command local bar=2 + function infun { + # The dynamic scope still applies, so the $bar value + # from 'function foo' is inherited and used instead + # of the global value. + echo \$bar + } + infun + } + foo + # This will be '3' because of the earlier 'local -g' + echo \$bar + EOF + exp=$'1\n2\n3' + got=$("$SHELL" "$tst") + [[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from dynamic scope to global scope in KornShell functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + + # Test 12: Dynamic variables shouldn't leak out of nested POSIX functions + tst=$tmp/tst.sh + cat > "$tst" <<-EOF + foo() { + $command local foo=foo + bar() { + $command local foo=bar + baz() { + $command local foo=baz + echo \$foo + } + baz + echo \$foo + } + bar + echo \$foo + } + foo + EOF + exp=$'baz\nbar\nfoo' + got=$("$SHELL" "$tst") + [[ $exp == "$got" ]] || err_exit "${prefix}Local variables from nested POSIX functions leak out into the parent functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + + # Test 13: Variables shouldn't leak out of nested KornShell functions + tst=$tmp/tst.sh + cat > "$tst" <<-EOF + function foo { + $command local foo=foo + function bar { + $command local foo=bar + function baz { + $command local foo=baz + echo \$foo + } + baz echo \$foo } + bar + echo \$foo + } + foo + EOF + exp=$'baz\nbar\nfoo' + got=$("$SHELL" "$tst") + [[ $exp == "$got" ]] || err_exit "${prefix}Local variables from nested KornShell functions leak out into the parent functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + + # Test 14: Dynamic variables shouldn't leak out into other POSIX functions + tst=$tmp/tst.sh + cat > "$tst" <<-EOF + baz() { + $command local foo=baz + echo \$foo + } + bar() { + $command local foo=bar baz echo \$foo } - bar - echo \$foo -} -foo -EOF -exp=$'baz\nbar\nfoo' -got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "${prefix}Local variables from nested POSIX functions leak out into the parent functions" \ - "(expected $(printf %q "$exp"), got $(printf %q "$got"))" - -# Test 13: Variables shouldn't leak out of nested KornShell functions -tst=$tmp/tst.sh -cat > "$tst" << EOF -function foo { - $command local foo=foo + foo() { + $command local foo=foo + bar + echo \$foo + } + foo + EOF + exp=$'baz\nbar\nfoo' + got=$("$SHELL" "$tst") + [[ $exp == "$got" ]] || err_exit "${prefix}Local variables from POSIX functions leak out into other functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + + # Test 15: Variables shouldn't leak out into other KornShell functions + tst=$tmp/tst.sh + cat > "$tst" <<-EOF + function baz { + $command local foo=baz + echo \$foo + } function bar { $command local foo=bar - function baz { - $command local foo=baz - echo \$foo - } baz echo \$foo } - bar - echo \$foo -} -foo -EOF -exp=$'baz\nbar\nfoo' -got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "${prefix}Local variables from nested KornShell functions leak out into the parent functions" \ - "(expected $(printf %q "$exp"), got $(printf %q "$got"))" - -# Test 14: Dynamic variables shouldn't leak out into other POSIX functions -tst=$tmp/tst.sh -cat > "$tst" << EOF -baz() { - $command local foo=baz - echo \$foo -} -bar() { - $command local foo=bar - baz - echo \$foo -} -foo() { - $command local foo=foo - bar - echo \$foo -} -foo -EOF -exp=$'baz\nbar\nfoo' -got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "${prefix}Local variables from POSIX functions leak out into other functions" \ - "(expected $(printf %q "$exp"), got $(printf %q "$got"))" - -# Test 15: Variables shouldn't leak out into other KornShell functions -tst=$tmp/tst.sh -cat > "$tst" << EOF -function baz { - $command local foo=baz - echo \$foo -} -function bar { - $command local foo=bar - baz - echo \$foo -} -function foo { - $command local foo=foo - bar - echo \$foo -} -foo -EOF -exp=$'baz\nbar\nfoo' -got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "${prefix}Local variables from KornShell functions leak out into other functions" \ - "(expected $(printf %q "$exp"), got $(printf %q "$got"))" - -# Test 16: 'typeset -c' should use static scoping in POSIX functions -tst=$tmp/tst.sh -cat > "$tst" << EOF -foo=global -bar() { - echo \$foo - $command typeset -c foo=static2 - echo \$foo -} -foo() { - $command typeset -c foo=static - echo \$foo - bar - echo \$foo -} -echo \$foo -foo -echo \$foo -EOF -exp=$'global\nstatic\nglobal\nstatic2\nstatic\nglobal' -got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "${prefix}Cannot use static scoping in POSIX functions with 'local -c'" \ - "(expected $(printf %q "$exp"), got $(printf %q "$got"))" - -# Test 17: 'local -c' should use static scoping in KornShell functions -tst=$tmp/tst.sh -cat > "$tst" << EOF -foo=global -function bar { + function foo { + $command local foo=foo + bar + echo \$foo + } + foo + EOF + exp=$'baz\nbar\nfoo' + got=$("$SHELL" "$tst") + [[ $exp == "$got" ]] || err_exit "${prefix}Local variables from KornShell functions leak out into other functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + + # Test 16: 'typeset -c' should use static scoping in POSIX functions + tst=$tmp/tst.sh + cat > "$tst" <<-EOF + foo=global + bar() { + echo \$foo + $command typeset -c foo=static2 + echo \$foo + } + foo() { + $command typeset -c foo=static + echo \$foo + bar + echo \$foo + } echo \$foo - $command local -c foo=static2 + foo echo \$foo -} -function foo { - $command local -c foo=static + EOF + exp=$'global\nstatic\nglobal\nstatic2\nstatic\nglobal' + got=$("$SHELL" "$tst") + [[ $exp == "$got" ]] || err_exit "${prefix}Cannot use static scoping in POSIX functions with 'local -c'" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + + # Test 17: 'local -c' should use static scoping in KornShell functions + tst=$tmp/tst.sh + cat > "$tst" <<-EOF + foo=global + function bar { + echo \$foo + $command local -c foo=static2 + echo \$foo + } + function foo { + $command local -c foo=static + echo \$foo + bar + echo \$foo + } echo \$foo - bar + foo echo \$foo -} -echo \$foo -foo -echo \$foo -EOF -exp=$'global\nstatic\nglobal\nstatic2\nstatic\nglobal' -got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "${prefix}Cannot use static scoping in KornShell functions with 'local -c'" \ - "(expected $(printf %q "$exp"), got $(printf %q "$got"))" - -# Test 18: Make static $bar dynamic in POSIX functions -cat > "$tst" << EOF -nxt() { - echo \$bar -} -foo() { - $command typeset -c bar=BAD - nxt - $command local bar=1 - function infun { + EOF + exp=$'global\nstatic\nglobal\nstatic2\nstatic\nglobal' + got=$("$SHELL" "$tst") + [[ $exp == "$got" ]] || err_exit "${prefix}Cannot use static scoping in KornShell functions with 'local -c'" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + + # Test 18: Make static $bar dynamic in POSIX functions + cat > "$tst" <<-EOF + nxt() { echo \$bar } - infun -} -foo -echo \${bar}2 -EOF -exp=$'\n1\n2' -got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from static scope to dynamic scope in POSIX functions" \ - "(expected $(printf %q "$exp"), got $(printf %q "$got"))" - -# Test 19: Make static $bar separate from global $bar in POSIX functions -cat > "$tst" << EOF -typeset bar=global -nxt() { - echo \$bar -} -foo() { - $command local -c bar=static - nxt - echo \$bar -} -echo \${bar} -foo -echo \${bar} -EOF -exp=$'global\nglobal\nstatic\nglobal' -got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "${prefix}Cannot separate static and global scopes in POSIX functions" \ - "(expected $(printf %q "$exp"), got $(printf %q "$got"))" - -# Test 20: Ksh functions run by '.' should not have any form of scoping -cat > "$tst" << EOF -function nxt { - echo \$bar -} -function foo { - $command typeset bar=1 - nxt - $command local foo=2 - function infun { - echo \$foo + foo() { + $command typeset -c bar=BAD + nxt + $command local bar=1 + function infun { + echo \$bar + } + infun } - infun - $command declare -c baz=3 - $command typeset -g bear=4 -} -. foo -print \$bar \$foo \$baz \$bear -EOF -exp=$'1\n2\n1 2 3 4' -got=$("$SHELL" "$tst") -[[ $exp == "$got" ]] || err_exit "${prefix}KornShell functions executed directly by '.' shouldn't have scoping" \ - "(expected $(printf %q "$exp"), got $(printf %q "$got"))" - -done # End of non-indented loop + foo + echo \${bar}2 + EOF + exp=$'\n1\n2' + got=$("$SHELL" "$tst") + [[ $exp == "$got" ]] || err_exit "${prefix}Cannot switch from static scope to dynamic scope in POSIX functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + + # Test 19: Make static $bar separate from global $bar in POSIX functions + cat > "$tst" <<-EOF + typeset bar=global + nxt() { + echo \$bar + } + foo() { + $command local -c bar=static + nxt + echo \$bar + } + echo \${bar} + foo + echo \${bar} + EOF + exp=$'global\nglobal\nstatic\nglobal' + got=$("$SHELL" "$tst") + [[ $exp == "$got" ]] || err_exit "${prefix}Cannot separate static and global scopes in POSIX functions" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + + # Test 20: Ksh functions run by '.' should not have any form of scoping + cat > "$tst" <<-EOF + function nxt { + echo \$bar + } + function foo { + $command typeset bar=1 + nxt + $command local foo=2 + function infun { + echo \$foo + } + infun + $command declare -c baz=3 + $command typeset -g bear=4 + } + . foo + print \$bar \$foo \$baz \$bear + EOF + exp=$'1\n2\n1 2 3 4' + got=$("$SHELL" "$tst") + [[ $exp == "$got" ]] || err_exit "${prefix}KornShell functions executed directly by '.' shouldn't have scoping" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" +done # ====== exit $((Errors<125?Errors:125)) From 010b0e3b6d2d87d5835cf534e7cdd129b7511fd3 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Wed, 17 Jan 2024 08:37:57 -0800 Subject: [PATCH 34/42] Add preliminary support for -D to `typeset -p` TODO: Add regression tests for 'declare foo' and 'declare -l -i foo'. --- src/cmd/ksh93/sh/nvtree.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/cmd/ksh93/sh/nvtree.c b/src/cmd/ksh93/sh/nvtree.c index 9405a79f9fd1..76b20bd7f505 100644 --- a/src/cmd/ksh93/sh/nvtree.c +++ b/src/cmd/ksh93/sh/nvtree.c @@ -404,6 +404,8 @@ void nv_attribute(Namval_t *np,Sfio_t *out,char *prefix,int noname) else if((!np->nvalue.cp||np->nvalue.cp==Empty) && nv_isattr(np,~NV_NOFREE)==NV_MINIMAL && strcmp(np->nvname,"_")) sfputr(out,prefix,' '); } + if(np->dynscope) + sfprintf(out,"%s -D ",prefix); return; } @@ -431,7 +433,11 @@ void nv_attribute(Namval_t *np,Sfio_t *out,char *prefix,int noname) } } else if(prefix && *prefix) + { sfputr(out,prefix,' '); + if(np->dynscope) + sfprintf(out,"-D "); + } for(tp = shtab_attributes; *tp->sh_name;tp++) { val = tp->sh_number; From fe001555c8c970ed9bdd0ae36b78e005179ec2d7 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Wed, 17 Jan 2024 14:57:47 -0800 Subject: [PATCH 35/42] Add documentation for the new scoping behavior to ksh93(1) --- src/cmd/ksh93/sh.1 | 64 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/src/cmd/ksh93/sh.1 b/src/cmd/ksh93/sh.1 index b1d54c8be2a3..73cd21d25423 100644 --- a/src/cmd/ksh93/sh.1 +++ b/src/cmd/ksh93/sh.1 @@ -4032,7 +4032,8 @@ Functions defined by the syntax and called by name execute in the same process as the caller and share all files and present working directory with the -caller. +caller. Functions which use this syntax are called 'KornShell +functions'. Traps caught by the caller are reset to their default action inside the function. A trap condition that is not caught or ignored by the @@ -4052,9 +4053,8 @@ and the function. However, the .B typeset -special built-in command used within a function -defines local variables whose scope includes -the current function. +special built-in command used within a KornShell function +defines local variables within a static scope. They can be passed to functions that they call in the variable assignment list that precedes the call or as arguments passed as name references. @@ -6578,6 +6578,11 @@ is specified, then resume at the .IR n -th enclosing loop. .TP +\(dd \f3declare\fP \f2vname\fP\*(OK\f3=\fP\f2value\^\fP\*(CK .\|.\|. +Declares each \f2vname\fP within a dynamic local scope in functions. +The same as +.BR typeset\ \-D . +.TP \f3disown\fP \*(OK \f2job\^\fP.\|.\|. \*(CK Causes the shell not to send a HUP signal to each given @@ -7089,6 +7094,29 @@ Declares each \f2vname\fP to be a variable name reference. The same as .BR typeset\ \-n . .TP +\(dd \f3local\fP \f2vname\fP\*(OK\f3=\fP\f2value\^\fP\*(CK .\|.\|. +Declares each \f2vname\fP within a dynamic local scope in functions. +The same as +.BR typeset\ \-D +and +.BR declare , +with the exception that +.B local +cannot be used outside of functions. +Additionally, unlike other +implementations of +.BR local , +the +.B ksh93u+m +version is not a special builtin. +This design decision was required to avoid breaking old +.B ksh93u+ +scripts that assume +.B local +is a valid function name, alongside other use cases incompatible +with a special builtin version of +.BR local . +.TP \f3print\fP \*(OK \f3\-CRenprsv\^\fP \*(CK \*(OK \f3\-u\fP \f2unit \^\fP\*(CK \*(OK \f3\-f\fP \f2format\^\fP \*(CK \*(OK \f2arg\^\fP .\|.\|. \*(CK With no options or with option .B \- @@ -8412,7 +8440,7 @@ for infinite loops. The same as .BR whence\ \-v . .TP -\(dg\(dd \f3typeset\fP \*(OK \f3\(+-ACHSbflmnprstux\^\fP \*(CK \*(OK \f3\(+-EFLRXZi\*(OK\f2n\^\fP\*(CK \*(CK \*(OK \f3\+-M \*(OK \f2mapname\fP \*(CK \*(CK \*(OK \f3\-T \*(OK \f2tname\fP=(\f2assign_list\fP) \*(CK \*(CK \*(OK \f3\-h \f2str\fP \*(CK \*(OK \f3\-a\fP \*(OK \f2\*(OKtype\*(CK\fP \*(CK \*(CK \*(OK \f2vname\^\fP\*(OK\f3=\fP\f2value\^\fP \*(CK \^ \*(CK .\|.\|. +\(dg\(dd \f3typeset\fP \*(OK \f3\(+-ACDHSbcfglmnprstux\^\fP \*(CK \*(OK \f3\(+-EFLRXZi\*(OK\f2n\^\fP\*(CK \*(CK \*(OK \f3\+-M \*(OK \f2mapname\fP \*(CK \*(CK \*(OK \f3\-T \*(OK \f2tname\fP=(\f2assign_list\fP) \*(CK \*(CK \*(OK \f3\-h \f2str\fP \*(CK \*(OK \f3\-a\fP \*(OK \f2\*(OKtype\*(CK\fP \*(CK \*(CK \*(OK \f2vname\^\fP\*(OK\f3=\fP\f2value\^\fP \*(CK \^ \*(CK .\|.\|. Sets attributes and values for shell variables and functions. When invoked inside a function defined with the .B function @@ -8662,6 +8690,32 @@ above) or in a name space (see .I Name Spaces\^ above). .TP +.B \-c +Forces variables to be created or modified using static local +scoping, even when +.B typeset +is executed in a function defined by the +.IB name () +syntax. +.TP +.B \-D +Variables created or modified with this flag are placed into a +dynamic local scope, regardless of which type of syntax the +function uses. +Unlike static local scoping, variables given a +dynamic scope are made available to nested functions invoked +by the function the local variable is located in. +Do note however that, +like with statically scoped local variables, +unsetting the variable will only locally unset it in the function +it's scoped to. +Nested functions called after unset will not inherit +the locally unset state of the variable, +and will inherit the +variable of the same name from the global scope if it exists. +Dynamic local scoping is available regardless of whether the +function is defined with POSIX or KornShell syntax. +.TP .B \-h Used within type definitions to add information when generating information about the subvariable on the man page. From bae563d2c0aa0a49616be932c101400d27033a06 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Thu, 18 Jan 2024 07:13:25 -0800 Subject: [PATCH 36/42] Improve the older set of local/declare regression tests --- src/cmd/ksh93/tests/local.sh | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/cmd/ksh93/tests/local.sh b/src/cmd/ksh93/tests/local.sh index 1358ed91a5ce..7b7b693c368c 100755 --- a/src/cmd/ksh93/tests/local.sh +++ b/src/cmd/ksh93/tests/local.sh @@ -21,14 +21,20 @@ . "${SHTESTS_COMMON:-${0%/*}/_common}" # ====== -# This test must be run first due to the next test. +# These tests must run first for the subsequent tests to properly detect aberrant functionality +# in the buggy ksh93v-/ksh2020 local builtin. command local 2> /dev/null && err_exit "'local' works outside of functions" +local 2> /dev/null && err_exit "'local' works outside of functions" # local shouldn't suddenly work outside of functions after a function runs local. posix_dummy() { command local > /dev/null; } +posix_dummy_two() { local > /dev/null; } function ksh_dummy { command local > /dev/null; } +function ksh_dummy_two { local > /dev/null; } posix_dummy && command local 2> /dev/null && err_exit 'the local builtin works outside of functions after a POSIX function runs local' +posix_dummy_two && local 2> /dev/null && err_exit 'the local builtin works outside of functions after a POSIX function runs local' ksh_dummy && command local 2> /dev/null && err_exit 'the local builtin works outside of functions after a KornShell function runs local' +ksh_dummy_two && local 2> /dev/null && err_exit 'the local builtin works outside of functions after a KornShell function runs local' # ====== for i in declare local; do @@ -48,10 +54,18 @@ for i in declare local; do unset .sh.fun command $i foo=bar 2>&1 } - [[ $(ksh_function_nounset) ]] && err_exit "'$i' fails inside of KornShell functions" - [[ $(ksh_function_unset) ]] && err_exit "'$i' fails inside of KornShell functions when \${.sh.fun} is unset" - [[ $(posix_function_nounset) ]] && err_exit "'$i' fails inside of POSIX functions" - [[ $(posix_function_unset) ]] && err_exit "'$i' fails inside of POSIX functions when \${.sh.fun} is unset" + got=$(ksh_function_nounset) + [[ -n $got ]] && err_exit "'$i' fails inside of KornShell functions" \ + "(got ${ printf %q "$got" })" + got=$(ksh_function_unset) + [[ -n $got ]] && err_exit "'$i' fails inside of KornShell functions when \${.sh.fun} is unset" \ + "(got ${ printf %q "$got" })" + got=$(posix_function_nounset) + [[ -n $got ]] && err_exit "'$i' fails inside of POSIX functions" \ + "(got ${ printf %q "$got" })" + got=$(posix_function_unset) + [[ -n $got ]] && err_exit "'$i' fails inside of POSIX functions when \${.sh.fun} is unset" \ + "(got ${ printf %q "$got" })" # The local and declare builtins should have a dynamic scope # Tests for the scope of POSIX functions From b601a873b6863d46e593ce119ec0ab532fb06001 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Thu, 18 Jan 2024 08:18:16 -0800 Subject: [PATCH 37/42] Add regression test for typeset -p output and update some documentation - local.sh: Added tests for the presence of the -D flag in output. - sh.1: Removed this comment as this behavior is subject to change. - xec.c: Update comment (the ksh93v- code is something I'm trying to salvage, but it certainly does seem unsalvageable.) --- src/cmd/ksh93/sh.1 | 8 -------- src/cmd/ksh93/sh/xec.c | 8 ++++---- src/cmd/ksh93/tests/local.sh | 14 ++++++++++++++ 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/cmd/ksh93/sh.1 b/src/cmd/ksh93/sh.1 index 73cd21d25423..e273d0ad44ae 100644 --- a/src/cmd/ksh93/sh.1 +++ b/src/cmd/ksh93/sh.1 @@ -8705,14 +8705,6 @@ function uses. Unlike static local scoping, variables given a dynamic scope are made available to nested functions invoked by the function the local variable is located in. -Do note however that, -like with statically scoped local variables, -unsetting the variable will only locally unset it in the function -it's scoped to. -Nested functions called after unset will not inherit -the locally unset state of the variable, -and will inherit the -variable of the same name from the global scope if it exists. Dynamic local scoping is available regardless of whether the function is defined with POSIX or KornShell syntax. .TP diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index f0b9fe67ad78..0c3df484bd5a 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -3037,10 +3037,10 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) * * The old method ksh93v- uses for its bash mode creates a viewport between * sh.var_tree and sh.var_base with the help of a sh.st.var_local pointer. - * This could fake dynamic scoping convincingly for the bash mode, but in - * reality that only manages to create a global scope local to the top level - * function (i.e., nested functions have no scoping.) This design flaw in - * ksh93v- made its scoping code unsalvageable. + * This could fake dynamic scoping convincingly for the bash mode, but the + * ksh93v- code only manages to create a global scope local to the top level + * function (i.e., nested functions have no scoping at all and their variables + * leak into the calling function.) */ nv_scan(prevscope->save_tree, local_exports, NULL, 0, NV_EXPORT|NV_DYNAMIC|NV_NOSCOPE); } diff --git a/src/cmd/ksh93/tests/local.sh b/src/cmd/ksh93/tests/local.sh index 7b7b693c368c..b34eda1906ad 100755 --- a/src/cmd/ksh93/tests/local.sh +++ b/src/cmd/ksh93/tests/local.sh @@ -593,6 +593,20 @@ for command in "" "command"; do got=$("$SHELL" "$tst") [[ $exp == "$got" ]] || err_exit "${prefix}KornShell functions executed directly by '.' shouldn't have scoping" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + + # Test 21: typeset -p should print the -D flag when a variable has dynamic scoping applied to it + cat > "$tst" <<-EOF + function foo { + $command declare foo=foo + $command local -l -i bar=2 + $command typeset -p foo bar + } + foo + EOF + exp=$'typeset -D foo=foo\ntypeset -D -l -i bar=2' + got=$("$SHELL" "$tst") + [[ $exp == "$got" ]] || err_exit "${prefix}'typeset -p' cannot add the -D flag to output for variables given a dynamic scope" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" done # ====== From 532a6c4fd21f0fed586231d6f253fa682d058c4d Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Mon, 22 Jan 2024 19:59:30 -0800 Subject: [PATCH 38/42] Simplify some code related to traps and scoping --- src/cmd/ksh93/bltins/typeset.c | 8 ++++---- src/cmd/ksh93/sh/xec.c | 22 +++++++++++----------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index c48e19baff7e..cab554bbfeab 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -530,12 +530,12 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) errormsg(SH_DICT,ERROR_exit(2),"the scoping flags -c, -D and -g cannot be combined"); UNREACHABLE(); } - if(troot==sh.var_tree && !sh.mktype && sh.infunction) + if(troot==sh.var_tree && !sh.mktype && sh.infunction && !(flag&(NV_SCOPES))) { - if(sh.infunction==FUN_POSIX && !(local || declare) && !(flag&(NV_DYNAMIC|NV_STATSCOPE))) - flag |= NV_GLOBAL; - else if((local || declare) && !(flag&(NV_GLOBAL|NV_STATSCOPE))) + if(local || declare) flag |= NV_DYNAMIC; + else if(sh.infunction==FUN_POSIX) + flag |= NV_GLOBAL; } if(isfloat) flag |= NV_DOUBLE; diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index 0c3df484bd5a..531997f9987b 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -1048,10 +1048,13 @@ int sh_exec(const Shnode_t *t, int flags) sh.prefix = NV_CLASS; flgs |= NV_TYPE; } - if((np==SYSLOCAL || np==SYSDECLARE) && !(flgs&(NV_GLOBAL|NV_STATSCOPE))) - flgs |= NV_DYNAMIC; - else if(sh.infunction==FUN_POSIX && !(flgs&(NV_DYNAMIC|NV_STATSCOPE))) - flgs |= NV_GLOBAL; + if(!(flgs&(NV_SCOPES))) + { + if(np==SYSLOCAL || np==SYSDECLARE) + flgs |= NV_DYNAMIC; + else if(sh.infunction==FUN_POSIX) + flgs |= NV_GLOBAL; + } if(sh.infunction < FUN_KSHDOT && !sh.prefix) flgs |= NV_NOSCOPE; } @@ -3166,12 +3169,6 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) prevscope->dolc = sh.st.dolc; prevscope->dolv = sh.st.dolv; } - if(!posix_fun) - { - trap = sh.st.trapcom[0]; - sh.st.trapcom[0] = 0; - sh_sigreset(1); - } /* * POSIX function cleanup */ @@ -3185,11 +3182,14 @@ int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) nv_putval(SH_PATHNAMENOD,sh.st.filename,NV_NOFREE); if(jmpval && jmpval!=SH_JMPFUN) siglongjmp(*sh.jmplist,jmpval); - return sh.exitval; + return r; } /* * KornShell function cleanup */ + trap = sh.st.trapcom[0]; + sh.st.trapcom[0] = 0; + sh_sigreset(1); sh.st = *prevscope; sh.topscope = (Shscope_t*)prevscope; nv_getval(sh_scoped(IFSNOD)); From 560d73d5011fee7b1619d8423a3172e8d7bf2d3d Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Mon, 5 Feb 2024 18:15:24 -0800 Subject: [PATCH 39/42] Fix typo On a side note, I haven't had the chance develop local(1) further because of a lack of time. In any case the current scoping bug is evading a fix. The current approach needs significant changes, as it has no backward compatibility when POSIX functions are nested in ksh functions (the normal ksh93 behavior is to have POSIX functions inherit the ksh function's full scope and use it directly, which necessarily includes the ksh function's statically scoped local variables. The current approach doesn't allow for this as POSIX functions have their own scope for the local builtin. Backward compatibility is currently achieved by having typeset modify the global scope directly, but that's not really the correct thing to do. I have tried altering typeset to modify the parent ksh function's scope directly, but such changes cannot be seen in the POSIX function itself after the variable in the parent function was changed, as the change only happens in the parent scope and not the current local scope (which defeats the whole purpose). To attempt to fix that, I then tried linking the parent scope to the local scope, but so far that just breaks the local scope (the cdt API lends itself very poorly to dynamic scoping). --- src/cmd/ksh93/tests/local.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/ksh93/tests/local.sh b/src/cmd/ksh93/tests/local.sh index b34eda1906ad..6cd7fb471bf4 100755 --- a/src/cmd/ksh93/tests/local.sh +++ b/src/cmd/ksh93/tests/local.sh @@ -132,7 +132,7 @@ for command in "" "command"; do function infun { echo \$bar } - # The current implementation does not make a seperate version of var for the static scope. + # The current implementation does not make a separate version of var for the static scope. # Rather, it changes foo()'s $bar variable back to a static scope, which prevents it from being # accessed by called functions as $bar is no longer in a dynamic scope. Consequently, both infun # and nxt are expected to print only a newline. From 24757cd292f602c5e886aa5f8d355eef585196a6 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Wed, 14 Feb 2024 15:49:18 -0800 Subject: [PATCH 40/42] To avoid conflict with 93v- -c, rename static local option to -P --- src/cmd/ksh93/bltins/typeset.c | 2 +- src/cmd/ksh93/data/builtins.c | 2 +- src/cmd/ksh93/sh/xec.c | 2 +- src/cmd/ksh93/tests/local.sh | 26 +++++++++++++------------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index 4c92d4f3f347..1c3c3d0001ce 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -444,7 +444,7 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) flag |= NV_DYNAMIC; scoping_flags++; break; - case 'c': + case 'P': flag &= ~(NV_SCOPES); flag |= NV_STATSCOPE; scoping_flags++; diff --git a/src/cmd/ksh93/data/builtins.c b/src/cmd/ksh93/data/builtins.c index 6e257c8d97e3..6029ac86b51a 100644 --- a/src/cmd/ksh93/data/builtins.c +++ b/src/cmd/ksh93/data/builtins.c @@ -1971,7 +1971,7 @@ const char sh_opttypeset[] = "[T]:?[tname?\atname\a is the name of a type name given to each \aname\a.]" "[Z]#?[n?Zero fill. If \an\a is given it represents the field width.]" "[D?Modifies variables using a dynamic local scope in functions.]" -"[c?Modifies variables using a static local scope in functions.]" +"[P?Modifies variables using a static local scope in functions.]" "\n" "\n[name[=value]...]\n" " -f [-tu] [name...]\n" diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index 6a65945af58c..243b778f78f5 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -1022,7 +1022,7 @@ int sh_exec(const Shnode_t *t, int flags) flgs |= NV_GLOBAL; if(checkopt(com,'D')) flgs |= NV_DYNAMIC; - if(checkopt(com,'c')) + if(checkopt(com,'P')) flgs |= NV_STATSCOPE; if(np==SYSNAMEREF || checkopt(com,'n')) flgs |= NV_NOREF; diff --git a/src/cmd/ksh93/tests/local.sh b/src/cmd/ksh93/tests/local.sh index 6cd7fb471bf4..77fc94b1c317 100755 --- a/src/cmd/ksh93/tests/local.sh +++ b/src/cmd/ksh93/tests/local.sh @@ -101,9 +101,9 @@ for i in declare local; do "(expected globalscope, got $(printf %q "$bar"))" done -got=$(command declare -cDg invalid=cannotset 2>&1) +got=$(command declare -PDg invalid=cannotset 2>&1) status=$? -((status == 2)) || err_exit "attempting to combine -c, -D and -g doesn't fail correctly" \ +((status == 2)) || err_exit "attempting to combine -P, -D and -g doesn't fail correctly" \ "(returned exit status $status and output $(printf %q "$got"))" # The declare builtin should work outside of functions @@ -480,17 +480,17 @@ for command in "" "command"; do [[ $exp == "$got" ]] || err_exit "${prefix}Local variables from KornShell functions leak out into other functions" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" - # Test 16: 'typeset -c' should use static scoping in POSIX functions + # Test 16: 'typeset -P' should use static scoping in POSIX functions tst=$tmp/tst.sh cat > "$tst" <<-EOF foo=global bar() { echo \$foo - $command typeset -c foo=static2 + $command typeset -P foo=static2 echo \$foo } foo() { - $command typeset -c foo=static + $command typeset -P foo=static echo \$foo bar echo \$foo @@ -501,20 +501,20 @@ for command in "" "command"; do EOF exp=$'global\nstatic\nglobal\nstatic2\nstatic\nglobal' got=$("$SHELL" "$tst") - [[ $exp == "$got" ]] || err_exit "${prefix}Cannot use static scoping in POSIX functions with 'local -c'" \ + [[ $exp == "$got" ]] || err_exit "${prefix}Cannot use static scoping in POSIX functions with 'local -P'" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" - # Test 17: 'local -c' should use static scoping in KornShell functions + # Test 17: 'local -P' should use static scoping in KornShell functions tst=$tmp/tst.sh cat > "$tst" <<-EOF foo=global function bar { echo \$foo - $command local -c foo=static2 + $command local -P foo=static2 echo \$foo } function foo { - $command local -c foo=static + $command local -P foo=static echo \$foo bar echo \$foo @@ -525,7 +525,7 @@ for command in "" "command"; do EOF exp=$'global\nstatic\nglobal\nstatic2\nstatic\nglobal' got=$("$SHELL" "$tst") - [[ $exp == "$got" ]] || err_exit "${prefix}Cannot use static scoping in KornShell functions with 'local -c'" \ + [[ $exp == "$got" ]] || err_exit "${prefix}Cannot use static scoping in KornShell functions with 'local -P'" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" # Test 18: Make static $bar dynamic in POSIX functions @@ -534,7 +534,7 @@ for command in "" "command"; do echo \$bar } foo() { - $command typeset -c bar=BAD + $command typeset -P bar=BAD nxt $command local bar=1 function infun { @@ -557,7 +557,7 @@ for command in "" "command"; do echo \$bar } foo() { - $command local -c bar=static + $command local -P bar=static nxt echo \$bar } @@ -583,7 +583,7 @@ for command in "" "command"; do echo \$foo } infun - $command declare -c baz=3 + $command declare -P baz=3 $command typeset -g bear=4 } . foo From a473068eea860260e1396cb855ef718be19d151d Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Wed, 14 Feb 2024 15:58:35 -0800 Subject: [PATCH 41/42] Add disabled regression test for https://github.com/ksh93/ksh/pull/703#issuecomment-1898680500 --- src/cmd/ksh93/tests/local.sh | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/cmd/ksh93/tests/local.sh b/src/cmd/ksh93/tests/local.sh index 77fc94b1c317..17f90c5ee53a 100755 --- a/src/cmd/ksh93/tests/local.sh +++ b/src/cmd/ksh93/tests/local.sh @@ -607,6 +607,25 @@ for command in "" "command"; do got=$("$SHELL" "$tst") [[ $exp == "$got" ]] || err_exit "${prefix}'typeset -p' cannot add the -D flag to output for variables given a dynamic scope" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + + : <<-'disabled' + cat > "$tst" <<-EOF + # Test 22: Set foo local to parent function from within a nested function (currently doesn't work). + parent() { + local foo=set_in_parent + nested + echo $foo + } + nested() { + foo=set_in_nested + } + parent + EOF + exp=set_in_nested + got=$("$SHELL" "$tst") + [[ $exp == "$got" ]] || err_exit "${prefix}cannot change variable local to parent function from within a nested function" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + disabled done # ====== From c04159d607f9fc875a326f075f868bc6c40ad91d Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Wed, 14 Feb 2024 16:03:16 -0800 Subject: [PATCH 42/42] Update documentation and error messages for -P change --- src/cmd/ksh93/bltins/typeset.c | 6 +++--- src/cmd/ksh93/data/builtins.c | 2 +- src/cmd/ksh93/sh.1 | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index 1c3c3d0001ce..0994d966fdfe 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -491,7 +491,7 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) } if((flag&NV_REF) && (flag&~(NV_REF|NV_IDENT|NV_ASSIGN|NV_SCOPES))) { - errormsg(SH_DICT,2,e_optincompat2,"-n","other options except -c, -D and -g"); + errormsg(SH_DICT,2,e_optincompat2,"-n","other options except -P, -D and -g"); error_info.errors++; } if((flag&NV_TYPE) && (flag&~(NV_TYPE|NV_VARNAME|NV_ASSIGN))) @@ -522,12 +522,12 @@ int b_typeset(int argc,char *argv[],Shbltin_t *context) } if((flag&NV_SCOPES) && sh.mktype) { - errormsg(SH_DICT,ERROR_exit(2),"type members cannot use the scoping flags -c, -D and -g"); + errormsg(SH_DICT,ERROR_exit(2),"type members cannot use the scoping flags -P, -D and -g"); UNREACHABLE(); } if(scoping_flags > 1) { - errormsg(SH_DICT,ERROR_exit(2),"the scoping flags -c, -D and -g cannot be combined"); + errormsg(SH_DICT,ERROR_exit(2),"the scoping flags -P, -D and -g cannot be combined"); UNREACHABLE(); } if(troot==sh.var_tree && !sh.mktype && sh.infunction && !(flag&(NV_SCOPES))) diff --git a/src/cmd/ksh93/data/builtins.c b/src/cmd/ksh93/data/builtins.c index 6029ac86b51a..7f40810b23da 100644 --- a/src/cmd/ksh93/data/builtins.c +++ b/src/cmd/ksh93/data/builtins.c @@ -1919,7 +1919,7 @@ const char sh_opttypeset[] = "[p?Causes the output to be in a format that can be used as input to the " "shell to recreate the attributes for variables. If this flag " "is used by \btypeset\b in a POSIX function without also passing " - "\b-D\b or \b-c\b, the local scope is ignored and \btypeset\b will " + "\b-D\b or \b-P\b, the local scope is ignored and \btypeset\b will " "only use the global scope.]" "[r?Enables readonly. Once enabled it cannot be disabled. See " "\breadonly\b(1).]" diff --git a/src/cmd/ksh93/sh.1 b/src/cmd/ksh93/sh.1 index 74781d7aaa12..8bc97bed9ed3 100644 --- a/src/cmd/ksh93/sh.1 +++ b/src/cmd/ksh93/sh.1 @@ -8485,7 +8485,7 @@ for infinite loops. The same as .BR whence\ \-v . .TP -\(dg\(dd \f3typeset\fP \*(OK \f3\(+-ACDHSbcfglmnprstux\^\fP \*(CK \*(OK \f3\(+-EFLRXZi\*(OK\f2n\^\fP\*(CK \*(CK \*(OK \f3\+-M \*(OK \f2mapname\fP \*(CK \*(CK \*(OK \f3\-T \*(OK \f2tname\fP=(\f2assign_list\fP) \*(CK \*(CK \*(OK \f3\-h \f2str\fP \*(CK \*(OK \f3\-a\fP \*(OK \f2\*(OKtype\*(CK\fP \*(CK \*(CK \*(OK \f2vname\^\fP\*(OK\f3=\fP\f2value\^\fP \*(CK \^ \*(CK .\|.\|. +\(dg\(dd \f3typeset\fP \*(OK \f3\(+-ACDHPSbfglmnprstux\^\fP \*(CK \*(OK \f3\(+-EFLRXZi\*(OK\f2n\^\fP\*(CK \*(CK \*(OK \f3\+-M \*(OK \f2mapname\fP \*(CK \*(CK \*(OK \f3\-T \*(OK \f2tname\fP=(\f2assign_list\fP) \*(CK \*(CK \*(OK \f3\-h \f2str\fP \*(CK \*(OK \f3\-a\fP \*(OK \f2\*(OKtype\*(CK\fP \*(CK \*(CK \*(OK \f2vname\^\fP\*(OK\f3=\fP\f2value\^\fP \*(CK \^ \*(CK .\|.\|. Sets attributes and values for shell variables and functions. When invoked inside a function defined with the .B function @@ -8735,7 +8735,7 @@ above) or in a name space (see .I Name Spaces\^ above). .TP -.B \-c +.B \-P Forces variables to be created or modified using static local scoping, even when .B typeset