From ea300089a115be3b1b637723a6d26513c5300888 Mon Sep 17 00:00:00 2001 From: Martijn Dekker Date: Wed, 1 Jun 2022 17:14:51 +0100 Subject: [PATCH] New feature: 'typeset -g' as in bash 4.2+ typeset -g allows directly manipulating the attributes of variables at the global level from any context. This feature already exists on bash 4.2 and later. mksh (R50+), yash and zsh have this flag as well, but it's slightly different: it ignores the current local scope, but a parent local scope from a calling function may still be used -- whereas on bash, '-g' always refers to the global scope. Since ksh93 uses static scoping (see III.Q28 at ), only the bash behaviour makes sense here. Note that the implementation needs to be done both in nv_setlist() (name.c) and in b_typeset() (typeset.c) because assignments are executed before the typeset built-in itself. Hence also the pre-parsing of typeset options in sh_exec(). src/cmd/ksh93/include/nval.h: - Add new NV_GLOBAL bit flag, using a previously unused bit that still falls within the 32-bit integer range. src/cmd/ksh93/sh/xec.c: sh_exec(): - When pre-parsing typeset flags, make -g pass the NV_GLOBAL flag to the nv_setlist() call that processes shell assignments prior to running the command. src/cmd/ksh93/sh/name.c: nv_setlist(): - When the NV_GLOBAL bit flag is passed, save the current variable tree pointer (sh.var_tree) as well as the current namespace (sh.namespace) and temporarily set the former to the global variable tree (sh.var_base) and the latter to NULL. This makes assignments global and ignores namesapces. src/cmd/ksh93/bltins/typeset.c: - b_typeset(): - Use NV_GLOBAL bit flag for -g. - Allow combining -n with -g, permitting 'typeset -gn var' or 'nameref -g var' to create a global nameref from a function. - Do not allow a nonsensical use of -g when using nested typeset to define member variables of 'typeset -T' types. (Type method functions can still use it as normal.) - setall(): - If NV_GLOBAL is passed, use sh.var_base and deactivate sh.namespace as in nv_setlist(). This allows attributes to be set correctly for global variables. src/cmd/ksh93/tests/{functions,namespace}.sh: - Add regression tests based on reproducers for problems found by @hyenias in preliminary versions of this feature. Resolves: https://github.com/ksh93/ksh/issues/479 --- ANNOUNCE | 3 ++ NEWS | 8 ++++++ src/cmd/ksh93/bltins/typeset.c | 30 ++++++++++++++++++-- src/cmd/ksh93/data/builtins.c | 5 ++-- src/cmd/ksh93/include/nval.h | 1 + src/cmd/ksh93/include/version.h | 2 +- src/cmd/ksh93/sh.1 | 18 ++++++++++-- src/cmd/ksh93/sh/name.c | 20 ++++++++++++++ src/cmd/ksh93/sh/xec.c | 2 ++ src/cmd/ksh93/tests/functions.sh | 39 ++++++++++++++++++++++++++ src/cmd/ksh93/tests/namespace.sh | 47 +++++++++++++++++++++++++++++++- 11 files changed, 167 insertions(+), 8 deletions(-) diff --git a/ANNOUNCE b/ANNOUNCE index 0183dec73..4b0f190b3 100644 --- a/ANNOUNCE +++ b/ANNOUNCE @@ -56,6 +56,9 @@ New features in built-in commands: Note that to use these options the operating system must support the corresponding resource limit. +- 'typeset' has a new '-g' flag that forces variables to be created or + modified at the global scope regardless of context, as on bash 4.2+. + New command line editor features: - Various keys on extended PC keyboards are now handled as expected in the diff --git a/NEWS b/NEWS index 5cec75821..1d807a759 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,14 @@ For full details, see the git log at: https://github.com/ksh93/ksh/tree/1.0 Any uppercase BUG_* names are modernish shell bug IDs. +2022-06-01: + +- New 'typeset' feature inspired by bash (and zsh and mksh): the -g flag + causes the specified variable(s) to be created globally, ignoring the + local scope of ksh functions or name spaces. For example, this allows + creating a global array or numeric variable from a ksh function. + Thanks to @ormaaj for the feature suggestion and to @hyenias for testing. + 2022-05-29: - Fixed a bug causing an inconsistent state after a special built-in command diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index e11319905..f85a72eb2 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -424,6 +424,9 @@ int b_typeset(int argc,register char *argv[],Shbltin_t *context) flag &= ~NV_VARNAME; flag |= (NV_EXPORT|NV_IDENT); break; + case 'g': + flag |= NV_GLOBAL; + break; case ':': errormsg(SH_DICT,2, "%s", opt_info.arg); break; @@ -458,9 +461,9 @@ endargs: errormsg(SH_DICT,2,e_optincompat1,"-m"); error_info.errors++; } - if((flag&NV_REF) && (flag&~(NV_REF|NV_IDENT|NV_ASSIGN))) + if((flag&NV_REF) && (flag&~(NV_REF|NV_IDENT|NV_ASSIGN|NV_GLOBAL))) { - errormsg(SH_DICT,2,e_optincompat1,"-n"); + errormsg(SH_DICT,2,e_optincompat2,"-n","other options except -g"); error_info.errors++; } if((flag&NV_TYPE) && (flag&~(NV_TYPE|NV_VARNAME|NV_ASSIGN))) @@ -489,6 +492,11 @@ endargs: errormsg(SH_DICT,ERROR_exit(2),"option argument cannot be greater than %d",SHRT_MAX); UNREACHABLE(); } + if((flag&NV_GLOBAL) && sh.mktype) + { + errormsg(SH_DICT,ERROR_exit(2),"-g: type members cannot be global"); + UNREACHABLE(); + } if(isfloat) flag |= NV_DOUBLE; if(sflag) @@ -634,6 +642,17 @@ static int setall(char **argv,register 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; + Namval_t *save_namespace; + if(flag&NV_GLOBAL) + { + save_vartree = sh.var_tree; + troot = sh.var_tree = sh.var_base; +#if SHOPT_NAMESPACE + save_namespace = sh.namespace; + sh.namespace = NIL(Namval_t*); +#endif + } if(!sh.prefix) { if(!tp->pflag) @@ -1005,6 +1024,13 @@ static int setall(char **argv,register 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.var_tree = save_vartree; +#if SHOPT_NAMESPACE + sh.namespace = save_namespace; +#endif + } return(r); } diff --git a/src/cmd/ksh93/data/builtins.c b/src/cmd/ksh93/data/builtins.c index bd49d9a9a..183a1b263 100644 --- a/src/cmd/ksh93/data/builtins.c +++ b/src/cmd/ksh93/data/builtins.c @@ -1776,7 +1776,7 @@ const char sh_opttrap[] = ; const char sh_opttypeset[] = -"+[-1c?\n@(#)$Id: typeset (ksh 93u+m) 2021-12-17 $\n]" +"+[-1c?\n@(#)$Id: typeset (ksh 93u+m) 2022-06-01 $\n]" "[--catalog?" SH_DICT "]" "[+NAME?typeset - declare or display variables with attributes]" "[+DESCRIPTION?Without the \b-f\b option, \btypeset\b sets, unsets, " @@ -1833,6 +1833,7 @@ const char sh_opttypeset[] = "encoding of the data. This option can be used with \b-Z\b to " "specify fixed-size fields.]" "[f?Each of the options and \aname\as refers to a function.]" +"[g?Forces variables to be created or modified at the global scope.]" "[i]#?[base:=10?An integer. \abase\a represents the arithmetic base " "from 2 to 64.]" "[l?Without \b-i\b, sets character mapping to \btolower\b. When used " @@ -1896,7 +1897,7 @@ const char sh_opttypeset[] = "\n[name[=value]...]\n" " -f [name...]\n" " -m [name=name...]\n" -" -n [name=name...]\n" +" -n [-g] [name=name...]\n" " -T [tname[=(type definition)]...]\n" "\n" "[+EXIT STATUS?]{" diff --git a/src/cmd/ksh93/include/nval.h b/src/cmd/ksh93/include/nval.h index d372980e2..e2238259d 100644 --- a/src/cmd/ksh93/include/nval.h +++ b/src/cmd/ksh93/include/nval.h @@ -184,6 +184,7 @@ struct Namval #define NV_NOSCOPE 0x80000 /* look only in current scope */ #define NV_NOFAIL 0x100000 /* return 0 on failure, no msg */ #define NV_NODISC NV_IDENT /* ignore disciplines */ +#define NV_GLOBAL 0x20000000 /* create global variable, ignoring local scope */ #define NV_FUNCT NV_IDENT /* option for nv_create */ #define NV_BLTINOPT NV_ZFILL /* mark builtins in libcmd */ diff --git a/src/cmd/ksh93/include/version.h b/src/cmd/ksh93/include/version.h index bd0267e09..7d597fae2 100644 --- a/src/cmd/ksh93/include/version.h +++ b/src/cmd/ksh93/include/version.h @@ -21,7 +21,7 @@ #define SH_RELEASE_FORK "93u+m" /* only change if you develop a new ksh93 fork */ #define SH_RELEASE_SVER "1.0.0-beta.2" /* semantic version number: https://semver.org */ -#define SH_RELEASE_DATE "2022-05-29" /* must be in this format for $((.sh.version)) */ +#define SH_RELEASE_DATE "2022-06-01" /* must be in this format for $((.sh.version)) */ #define SH_RELEASE_CPYR "(c) 2020-2022 Contributors to ksh " SH_RELEASE_FORK /* Scripts sometimes field-split ${.sh.version}, so don't change amount of whitespace. */ diff --git a/src/cmd/ksh93/sh.1 b/src/cmd/ksh93/sh.1 index 469dbca1d..e7fa955a7 100644 --- a/src/cmd/ksh93/sh.1 +++ b/src/cmd/ksh93/sh.1 @@ -8323,6 +8323,19 @@ format can be used to output the actual data in this buffer instead of the base64 encoding of the data. .TP +.B \-g +Forces variables to be created or modified at the global scope, +even when +.B typeset +is executed in a function defined by the +.B function +.I name +syntax (see +.I Functions\^ +above) or in a name space (see +.I Name Spaces\^ +above). +.TP .B \-h Used within type definitions to add information when generating information about the subvariable on the man page. @@ -8359,7 +8372,7 @@ Equivalent to .B "\-M tolower" . .TP .B \-m -moves or renames the variable. +Moves or renames the variable. The value is the name of a variable whose value will be moved to .IR vname\^ . The original variable will be unset. @@ -8373,7 +8386,8 @@ defined by the value of variable .IR vname . This is usually used to reference a variable inside a function whose name has been passed as an argument. -Cannot be used with any other options. +Cannot be used with other options except +.BR \-g . .TP .B \-p The name, attributes and values for the given diff --git a/src/cmd/ksh93/sh/name.c b/src/cmd/ksh93/sh/name.c index 0f418aa97..d2285830e 100644 --- a/src/cmd/ksh93/sh/name.c +++ b/src/cmd/ksh93/sh/name.c @@ -273,6 +273,19 @@ void nv_setlist(register struct argnod *arg,register 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 *save_vartree; +#if SHOPT_NAMESPACE + Namval_t *save_namespace; +#endif + if(flags&NV_GLOBAL) + { + save_vartree = sh.var_tree; + sh.var_tree = sh.var_base; +#if SHOPT_NAMESPACE + save_namespace = sh.namespace; + sh.namespace = NIL(Namval_t*); +#endif + } if(maketype) { shtp.previous = sh.mktype; @@ -646,6 +659,13 @@ void nv_setlist(register struct argnod *arg,register int flags, Namval_t *typ) } /* continue loop */ } + if(flags&NV_GLOBAL) + { + sh.var_tree = save_vartree; +#if SHOPT_NAMESPACE + sh.namespace = save_namespace; +#endif + } } /* diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index 6f18874af..805a4aee0 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -1068,6 +1068,8 @@ int sh_exec(register const Shnode_t *t, int flags) flgs |= NV_STATIC; if(checkopt(com,'m')) flgs |= NV_MOVE; + if(checkopt(com,'g')) + flgs |= NV_GLOBAL; if(np==SYSNAMEREF || checkopt(com,'n')) flgs |= NV_NOREF; else if(argn>=3 && checkopt(com,'T')) diff --git a/src/cmd/ksh93/tests/functions.sh b/src/cmd/ksh93/tests/functions.sh index b488b4986..b6c5c6391 100755 --- a/src/cmd/ksh93/tests/functions.sh +++ b/src/cmd/ksh93/tests/functions.sh @@ -1341,5 +1341,44 @@ got=$( { "$SHELL" "$tmp/crash_rhbz1117404.ksh"; } 2>&1) ((!(e = $?))) || err_exit 'crash while handling function-local trap' \ "(got status $e$( ((e>128)) && print -n /SIG && kill -l "$e"), $(printf %q "$got"))" +# ====== +# 'typeset -g' should cause the active function scope to be ignored. +# https://github.com/ksh93/ksh/issues/479#issuecomment-1140523291 +if ! command typeset -g x 2>/dev/null +then + warning "shell does not have 'typeset -g'; skipping those tests" +else + unset x arr a b c + # must specify the length for -X as the default is -X 24 on 32-bit systems and -X 32 on 64-bit systems + typeset -A exp=( + ['x=123; function f { typeset -g x=456; }; function g { typeset x=789; f; }; g; typeset -p x']='x=456' + ['x=123; function f { typeset -g -i x; }; f; typeset -p x']='typeset -i x=123' + ['x=123; function f { typeset -g -F x; }; f; typeset -p x']='typeset -F x=123.0000000000' + ['x=123; function f { typeset -g -E x; }; f; typeset -p x']='typeset -E x=123' + ['x=123; function f { typeset -g -X24 x; }; f; typeset -p x']='typeset -X 24 x=0x1.ec0000000000000000000000p+6' + ['x=aBc; function f { typeset -g -u x; }; f; typeset -p x']='typeset -u x=ABC' + ['x=aBc; function f { typeset -g -l x; }; f; typeset -p x']='typeset -l x=abc' + ['x=aBc; function f { typeset -g -L x; }; f; typeset -p x']='typeset -L 3 x=aBc' + ['x=aBc; function f { typeset -g -R x; }; f; typeset -p x']='typeset -R 3 x=aBc' + ['x=aBc; function f { typeset -g -Z x; }; f; typeset -p x']='typeset -Z 3 -R 3 x=aBc' + ['x=aBc; function f { typeset -g -L2 x; }; f; typeset -p x']='typeset -L 2 x=aB' + ['x=aBc; function f { typeset -g -R2 x; }; f; typeset -p x']='typeset -R 2 x=Bc' + ['x=aBc; function f { typeset -g -Z2 x; }; f; typeset -p x']='typeset -Z 2 -R 2 x=Bc' + ['x=8; function f { typeset -g -i2 x; }; f; typeset -p x']='typeset -i 2 x=2#1000' + ['x=8; function f { typeset -g -i8 x; }; f; typeset -p x']='typeset -i 8 x=8#10' + ['arr=(a b c); function f { typeset -g -i arr[1]=(1 2 3); }; f; typeset -p arr']='typeset -a -i arr=(0 (1 2 3) 0)' + ['arr=(a b c); function f { typeset -g -F arr[1]=(1 2 3); }; f; typeset -p arr']='typeset -a -F arr=(0.0000000000 (1.0000000000 2.0000000000 3.0000000000) 0.0000000000)' + ['arr=(a b c); function f { typeset -g -E arr[1]=(1 2 3); }; f; typeset -p arr']='typeset -a -E arr=(0 (1 2 3) 0)' + ['arr=(a b c); function f { typeset -g -X24 arr[1]=(1 2 3); }; f; typeset -p arr']='typeset -a -X 24 arr=(0x0.000000000000000000000000p+0 (0x1.000000000000000000000000p+0 0x1.000000000000000000000000p+1 0x1.800000000000000000000000p+1) 0x0.000000000000000000000000p+0)' + ) + for cmd in "${!exp[@]}" + do + got=$(set +x; eval "$cmd" 2>&1) + [[ $got == "${exp[$cmd]}" ]] || err_exit "typeset -g in $(printf %q "$cmd") failed to activate global" \ + "scope from ksh function (expected $(printf %q "${exp[$cmd]}"), got $(printf %q "$got"))" + done + unset exp +fi + # ====== exit $((Errors<125?Errors:125)) diff --git a/src/cmd/ksh93/tests/namespace.sh b/src/cmd/ksh93/tests/namespace.sh index e9adc88ab..5c5978345 100755 --- a/src/cmd/ksh93/tests/namespace.sh +++ b/src/cmd/ksh93/tests/namespace.sh @@ -138,9 +138,54 @@ set +o allexport # is not executed -- the zillionth bug with virtual subshells >:-/ exp=$'123 456\n789 456' got=$(ulimit -t unlimited 2>/dev/null # workaround: force fork - trap 'echo $x ${.test.x}; x=789; echo $x ${.test.x}' EXIT; x=123; namespace test { x=456; set -Q 2>/dev/null; }) + trap 'echo $x ${.test.x}; x=789; echo $x ${.test.x}' EXIT + x=123 + namespace test + { + x=456 + set --bad_option 2>/dev/null + } +) [[ $got == "$exp" ]] || err_exit 'Parent scope not restored after special builtin throws error in namespace' \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" +# ====== +# 'typeset -g' should cause the active name space to be ignored. +# https://github.com/ksh93/ksh/issues/479#issuecomment-1140523291 +if ! command typeset -g x 2>/dev/null +then + warning "shell does not have 'typeset -g'; skipping those tests" +else + unset x arr a b c + # must specify the length for -X as the default is -X 24 on 32-bit systems and -X 32 on 64-bit systems + typeset -A exp=( + ['x=123; namespace test { typeset -g -i x; }; typeset -p x']='typeset -i x=123' + ['x=123; namespace test { typeset -g -F x; }; typeset -p x']='typeset -F x=123.0000000000' + ['x=123; namespace test { typeset -g -E x; }; typeset -p x']='typeset -E x=123' + ['x=123; namespace test { typeset -g -X24 x; }; typeset -p x']='typeset -X 24 x=0x1.ec0000000000000000000000p+6' + ['x=aBc; namespace test { typeset -g -u x; }; typeset -p x']='typeset -u x=ABC' + ['x=aBc; namespace test { typeset -g -l x; }; typeset -p x']='typeset -l x=abc' + ['x=aBc; namespace test { typeset -g -L x; }; typeset -p x']='typeset -L 3 x=aBc' + ['x=aBc; namespace test { typeset -g -R x; }; typeset -p x']='typeset -R 3 x=aBc' + ['x=aBc; namespace test { typeset -g -Z x; }; typeset -p x']='typeset -Z 3 -R 3 x=aBc' + ['x=aBc; namespace test { typeset -g -L2 x; }; typeset -p x']='typeset -L 2 x=aB' + ['x=aBc; namespace test { typeset -g -R2 x; }; typeset -p x']='typeset -R 2 x=Bc' + ['x=aBc; namespace test { typeset -g -Z2 x; }; typeset -p x']='typeset -Z 2 -R 2 x=Bc' + ['x=8; namespace test { typeset -g -i2 x; }; typeset -p x']='typeset -i 2 x=2#1000' + ['x=8; namespace test { typeset -g -i8 x; }; typeset -p x']='typeset -i 8 x=8#10' + ['arr=(a b c); namespace test { typeset -g -i arr[1]=(1 2 3); }; typeset -p arr']='typeset -a -i arr=(0 (1 2 3) 0)' + ['arr=(a b c); namespace test { typeset -g -F arr[1]=(1 2 3); }; typeset -p arr']='typeset -a -F arr=(0.0000000000 (1.0000000000 2.0000000000 3.0000000000) 0.0000000000)' + ['arr=(a b c); namespace test { typeset -g -E arr[1]=(1 2 3); }; typeset -p arr']='typeset -a -E arr=(0 (1 2 3) 0)' + ['arr=(a b c); namespace test { typeset -g -X24 arr[1]=(1 2 3); }; typeset -p arr']='typeset -a -X 24 arr=(0x0.000000000000000000000000p+0 (0x1.000000000000000000000000p+0 0x1.000000000000000000000000p+1 0x1.800000000000000000000000p+1) 0x0.000000000000000000000000p+0)' + ) + for cmd in "${!exp[@]}" + do + got=$(set +x; eval "$cmd" 2>&1) + [[ $got == "${exp[$cmd]}" ]] || err_exit "typeset -g in $(printf %q "$cmd") failed to activate global" \ + "scope from name space (expected $(printf %q "${exp[$cmd]}"), got $(printf %q "$got"))" + done + unset exp +fi + # ====== exit $((Errors<125?Errors:125))