1
0
Fork 0
mirror of git://git.code.sf.net/p/cdesktopenv/code synced 2025-03-09 15:50:02 +00:00

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 <http://kornshell.com/doc/faq.html>), 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
This commit is contained in:
Martijn Dekker 2022-06-01 17:14:51 +01:00
parent f73b8617dd
commit ea300089a1
11 changed files with 167 additions and 8 deletions

View file

@ -56,6 +56,9 @@ New features in built-in commands:
Note that to use these options the operating system must support the Note that to use these options the operating system must support the
corresponding resource limit. 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: New command line editor features:
- Various keys on extended PC keyboards are now handled as expected in the - Various keys on extended PC keyboards are now handled as expected in the

8
NEWS
View file

@ -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. 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: 2022-05-29:
- Fixed a bug causing an inconsistent state after a special built-in command - Fixed a bug causing an inconsistent state after a special built-in command

View file

@ -424,6 +424,9 @@ int b_typeset(int argc,register char *argv[],Shbltin_t *context)
flag &= ~NV_VARNAME; flag &= ~NV_VARNAME;
flag |= (NV_EXPORT|NV_IDENT); flag |= (NV_EXPORT|NV_IDENT);
break; break;
case 'g':
flag |= NV_GLOBAL;
break;
case ':': case ':':
errormsg(SH_DICT,2, "%s", opt_info.arg); errormsg(SH_DICT,2, "%s", opt_info.arg);
break; break;
@ -458,9 +461,9 @@ endargs:
errormsg(SH_DICT,2,e_optincompat1,"-m"); errormsg(SH_DICT,2,e_optincompat1,"-m");
error_info.errors++; 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++; error_info.errors++;
} }
if((flag&NV_TYPE) && (flag&~(NV_TYPE|NV_VARNAME|NV_ASSIGN))) 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); errormsg(SH_DICT,ERROR_exit(2),"option argument cannot be greater than %d",SHRT_MAX);
UNREACHABLE(); UNREACHABLE();
} }
if((flag&NV_GLOBAL) && sh.mktype)
{
errormsg(SH_DICT,ERROR_exit(2),"-g: type members cannot be global");
UNREACHABLE();
}
if(isfloat) if(isfloat)
flag |= NV_DOUBLE; flag |= NV_DOUBLE;
if(sflag) if(sflag)
@ -634,6 +642,17 @@ static int setall(char **argv,register int flag,Dt_t *troot,struct tdata *tp
char *last = 0; 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));
int r=0, ref=0, comvar=(flag&NV_COMVAR),iarray=(flag&NV_IARRAY); 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(!sh.prefix)
{ {
if(!tp->pflag) if(!tp->pflag)
@ -1005,6 +1024,13 @@ static int setall(char **argv,register int flag,Dt_t *troot,struct tdata *tp
if(r==0) if(r==0)
r = 1; /* ensure the exit status is at least 1 */ 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); return(r);
} }

View file

@ -1776,7 +1776,7 @@ const char sh_opttrap[] =
; ;
const char sh_opttypeset[] = 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 "]" "[--catalog?" SH_DICT "]"
"[+NAME?typeset - declare or display variables with attributes]" "[+NAME?typeset - declare or display variables with attributes]"
"[+DESCRIPTION?Without the \b-f\b option, \btypeset\b sets, unsets, " "[+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 " "encoding of the data. This option can be used with \b-Z\b to "
"specify fixed-size fields.]" "specify fixed-size fields.]"
"[f?Each of the options and \aname\as refers to a function.]" "[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 " "[i]#?[base:=10?An integer. \abase\a represents the arithmetic base "
"from 2 to 64.]" "from 2 to 64.]"
"[l?Without \b-i\b, sets character mapping to \btolower\b. When used " "[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" "\n[name[=value]...]\n"
" -f [name...]\n" " -f [name...]\n"
" -m [name=name...]\n" " -m [name=name...]\n"
" -n [name=name...]\n" " -n [-g] [name=name...]\n"
" -T [tname[=(type definition)]...]\n" " -T [tname[=(type definition)]...]\n"
"\n" "\n"
"[+EXIT STATUS?]{" "[+EXIT STATUS?]{"

View file

@ -184,6 +184,7 @@ struct Namval
#define NV_NOSCOPE 0x80000 /* look only in current scope */ #define NV_NOSCOPE 0x80000 /* look only in current scope */
#define NV_NOFAIL 0x100000 /* return 0 on failure, no msg */ #define NV_NOFAIL 0x100000 /* return 0 on failure, no msg */
#define NV_NODISC NV_IDENT /* ignore disciplines */ #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_FUNCT NV_IDENT /* option for nv_create */
#define NV_BLTINOPT NV_ZFILL /* mark builtins in libcmd */ #define NV_BLTINOPT NV_ZFILL /* mark builtins in libcmd */

View file

@ -21,7 +21,7 @@
#define SH_RELEASE_FORK "93u+m" /* only change if you develop a new ksh93 fork */ #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_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 #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. */ /* Scripts sometimes field-split ${.sh.version}, so don't change amount of whitespace. */

View file

@ -8323,6 +8323,19 @@ format
can be used to output the actual data in this buffer instead can be used to output the actual data in this buffer instead
of the base64 encoding of the data. of the base64 encoding of the data.
.TP .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 .B \-h
Used within type definitions to add information when generating Used within type definitions to add information when generating
information about the subvariable on the man page. information about the subvariable on the man page.
@ -8359,7 +8372,7 @@ Equivalent to
.B "\-M tolower" . .B "\-M tolower" .
.TP .TP
.B \-m .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 The value is the name of a variable whose value will be moved to
.IR vname\^ . .IR vname\^ .
The original variable will be unset. The original variable will be unset.
@ -8373,7 +8386,8 @@ defined by the value of variable
.IR vname . .IR vname .
This is usually used to reference a variable inside This is usually used to reference a variable inside
a function whose name has been passed as an argument. 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 .TP
.B \-p .B \-p
The name, attributes and values for the given The name, attributes and values for the given

View file

@ -273,6 +273,19 @@ void nv_setlist(register struct argnod *arg,register int flags, Namval_t *typ)
struct Namref nr; struct Namref nr;
int maketype = flags&NV_TYPE; /* make a 'typeset -T' type definition command */ int maketype = flags&NV_TYPE; /* make a 'typeset -T' type definition command */
struct sh_type shtp; 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) if(maketype)
{ {
shtp.previous = sh.mktype; shtp.previous = sh.mktype;
@ -646,6 +659,13 @@ void nv_setlist(register struct argnod *arg,register int flags, Namval_t *typ)
} }
/* continue loop */ /* continue loop */
} }
if(flags&NV_GLOBAL)
{
sh.var_tree = save_vartree;
#if SHOPT_NAMESPACE
sh.namespace = save_namespace;
#endif
}
} }
/* /*

View file

@ -1068,6 +1068,8 @@ int sh_exec(register const Shnode_t *t, int flags)
flgs |= NV_STATIC; flgs |= NV_STATIC;
if(checkopt(com,'m')) if(checkopt(com,'m'))
flgs |= NV_MOVE; flgs |= NV_MOVE;
if(checkopt(com,'g'))
flgs |= NV_GLOBAL;
if(np==SYSNAMEREF || checkopt(com,'n')) if(np==SYSNAMEREF || checkopt(com,'n'))
flgs |= NV_NOREF; flgs |= NV_NOREF;
else if(argn>=3 && checkopt(com,'T')) else if(argn>=3 && checkopt(com,'T'))

View file

@ -1341,5 +1341,44 @@ got=$( { "$SHELL" "$tmp/crash_rhbz1117404.ksh"; } 2>&1)
((!(e = $?))) || err_exit 'crash while handling function-local trap' \ ((!(e = $?))) || err_exit 'crash while handling function-local trap' \
"(got status $e$( ((e>128)) && print -n /SIG && kill -l "$e"), $(printf %q "$got"))" "(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)) exit $((Errors<125?Errors:125))

View file

@ -138,9 +138,54 @@ set +o allexport
# is not executed -- the zillionth bug with virtual subshells >:-/ # is not executed -- the zillionth bug with virtual subshells >:-/
exp=$'123 456\n789 456' exp=$'123 456\n789 456'
got=$(ulimit -t unlimited 2>/dev/null # workaround: force fork 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' \ [[ $got == "$exp" ]] || err_exit 'Parent scope not restored after special builtin throws error in namespace' \
"(expected $(printf %q "$exp"), got $(printf %q "$got"))" "(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)) exit $((Errors<125?Errors:125))