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
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

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.
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

View file

@ -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);
}

View file

@ -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?]{"

View file

@ -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 */

View file

@ -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. */

View file

@ -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

View file

@ -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
}
}
/*

View file

@ -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'))

View file

@ -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))

View file

@ -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))