52513: fixes and doc for using nofork substitutions with private parameters

Also fixes a crash bug with {fd}>&N redirections and private parameters
master
Bart Schaefer 4 months ago
parent 18400b68e4
commit 8801665e5b

@ -1,3 +1,10 @@
2024-02-03 Bart Schaefer <schaefer@zsh.org>
* 52513: Doc/Zsh/mod_private.yo, Src/Modules/param_private.c,
Src/params.c, Test/v10private.ztst: nofork substitutions can
use private parameters; fix crash bug on {privateFD}>&N; add
tests and documentation
2024-01-28 Bart Schaefer <schaefer@zsh.org>
* 52510: Doc/Zsh/expn.yo, Doc/Zsh/mod_private.yo: document how

@ -84,9 +84,14 @@ created outside the local scope when it was not previously declared.)
itemiz(An exported private remains in the environment of inner scopes but
appears unset for the current shell in those scopes. Generally, exporting
private parameters should be avoided.)
itemiz(Current shell command substitutions such as `tt(${|)...tt(})',
`tt(${|)var(var)tt(|)...tt(})' and `tt(${ )...tt( })' may read and assign
private parameters from the enclosing function.)
itemiz(Declaring a private parameter in a current shell command substitution
such as `tt(${ )...tt( })' limits the parameter to the scope of the command
substitution, just as if the parameter were declared in a function.)
limits that parameter to the scope of the command substitution, just as if
the parameter were declared in a function. This also prevents access by
any enclosed current shell command substitutions, but other substitutions
may use the private parameter because those have the same calling scope.)
enditemize()
Note that this differs from the static scope defined by compiled languages

@ -298,7 +298,7 @@ pps_setfn(Param pm, char *x)
{
struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.s);
GsuScalar gsu = (GsuScalar)(c->g);
if (locallevel == pm->level)
if (locallevel == pm->level || locallevel > private_wraplevel)
gsu->setfn(pm, x);
else
setfn_error(pm);
@ -338,7 +338,7 @@ ppi_setfn(Param pm, zlong x)
{
struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.i);
GsuInteger gsu = (GsuInteger)(c->g);
if (locallevel == pm->level)
if (locallevel == pm->level || locallevel > private_wraplevel)
gsu->setfn(pm, x);
else
setfn_error(pm);
@ -378,7 +378,7 @@ ppf_setfn(Param pm, double x)
{
struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.f);
GsuFloat gsu = (GsuFloat)(c->g);
if (locallevel == pm->level)
if (locallevel == pm->level || locallevel > private_wraplevel)
gsu->setfn(pm, x);
else
setfn_error(pm);
@ -419,7 +419,7 @@ ppa_setfn(Param pm, char **x)
{
struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.a);
GsuArray gsu = (GsuArray)(c->g);
if (locallevel == pm->level)
if (locallevel == pm->level || locallevel > private_wraplevel)
gsu->setfn(pm, x);
else
setfn_error(pm);
@ -461,7 +461,7 @@ pph_setfn(Param pm, HashTable x)
{
struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.h);
GsuHash gsu = (GsuHash)(c->g);
if (locallevel == pm->level)
if (locallevel == pm->level || locallevel > private_wraplevel)
gsu->setfn(pm, x);
else
setfn_error(pm);
@ -539,19 +539,20 @@ static struct funcwrap wrapper[] = {
WRAPDEF(wrap_private)
};
/**/
static int private_wraplevel = 0;
/**/
static int
wrap_private(Eprog prog, FuncWrap w, char *name)
{
static int wraplevel = 0;
if (wraplevel < locallevel /* && strcmp(name, "(anon)") != 0 */) {
int owl = wraplevel;
wraplevel = locallevel;
if (private_wraplevel < locallevel /* && strcmp(name, "(anon)") != 0 */) {
int owl = private_wraplevel;
private_wraplevel = locallevel;
scanhashtable(paramtab, 0, 0, 0, scopeprivate, PM_UNSET);
runshfunc(prog, w, name);
scanhashtable(paramtab, 0, 0, 0, scopeprivate, 0);
wraplevel = owl;
private_wraplevel = owl;
return 0;
}
return 1;
@ -573,22 +574,32 @@ getprivatenode(HashTable ht, const char *nam)
pm = (Param) hn;
/* how would an autoloaded private behave? return here? */
}
while (!fakelevel && pm && locallevel > pm->level && is_private(pm)) {
while (!fakelevel && pm && is_private(pm) && locallevel > pm->level) {
if (pm->level == private_wraplevel + 1) {
/* Variable is in the current function scope */
break;
}
#if 0
if (!(pm->node.flags & PM_UNSET)) {
/*
* private parameters are always marked PM_UNSET before we
* increment locallevel, so the only way we get here is
* when createparam() wants a new parameter that is not at
* the current locallevel and it has therefore cleared the
* PM_UNSET flag.
* increment locallevel, so there are three possible ways
* to get here:
* 1) createparam() wants a new parameter that is not at
* the current locallevel and it has therefore cleared the
* PM_UNSET flag
* 2) locallevel has been incremented (startparamscope())
* outside the usual function call stack (private_wraplevel)
* 3) dynamic scoping is fetching a value from a surrounding
* scope, we don't know if that's for assign or just expand
* The first of those is now caught in createparam() when
* testing PM_RO_BY_DESIGN and the second occurs only in
* nofork substitution or handling of ZLE specials. If the
* third is an assignment, the GSU setfn rejects it.
*/
DPUTS(pm->old, "BUG: PM_UNSET cleared in wrong scope");
setfn_error(pm);
/*
* TODO: instead of throwing an error here, create a global
* parameter, insert as pm->old, handle WARN_CREATE_GLOBAL.
*/
}
#endif
pm = pm->old;
}

@ -1049,7 +1049,7 @@ createparam(char *name, int flags)
/* POSIXBUILTINS horror: we need to retain 'export' flags */
(isset(POSIXBUILTINS) && (oldpm->node.flags & PM_EXPORTED))) {
if (oldpm->node.flags & PM_RO_BY_DESIGN) {
zerr("%s: can't change parameter attribute",
zerr("%s: can't modify read-only parameter",
name);
return NULL;
}
@ -3615,9 +3615,18 @@ assignnparam(char *s, mnumber val, int flags)
pm = createparam(t, ss ? PM_ARRAY :
isset(POSIXIDENTIFIERS) ? PM_SCALAR :
(val.type & MN_INTEGER) ? PM_INTEGER : PM_FFLOAT);
if (!pm)
pm = (Param) paramtab->getnode(paramtab, t);
DPUTS(!pm, "BUG: parameter not created");
if (errflag) {
/* assume error message already output */
unqueue_signals();
return NULL;
}
if (!pm && !(pm = (Param) paramtab->getnode(paramtab, t))) {
DPUTS(!pm, "BUG: parameter not created");
if (!errflag)
zerr("%s: parameter not found", t);
unqueue_signals();
return NULL;
}
if (ss) {
*ss = '[';
} else if (val.type & MN_INTEGER) {

@ -10,6 +10,8 @@
sed -e 's,# test_zsh_param_private,zmodload zsh/param/private,' < $ZTST_srcdir/B02typeset.ztst > private.TMP/B02
fi
setopt TYPESET_TO_UNSET
%test
(zmodload -u zsh/param/private && zmodload zsh/param/private)
@ -246,7 +248,7 @@ F:note "typeset" rather than "private" in output from outer
1:privates are not visible in anonymous functions, part 3
>X top level
>array_test not set
?(anon):4: array_test: can't change parameter attribute
?(anon):4: array_test: can't modify read-only parameter
F:future revision will create a global with this assignment
typeset -a array_test
@ -311,11 +313,30 @@ F:future revision will create a global with this assignment
() {
typeset -n ptr1=ptr2
private -n ptr2
private -n ptr2 # TYPESET_TO_UNSET makes this not a "placeholder"
typeset -p ptr1 ptr2
typeset val=LOCAL
() {
ptr1=val
ptr1=val # Test dies here as ptr2 is private and unset
typeset -n
printf "%s=%s\n" ptr1 "$ptr1" ptr2 "$ptr2"
}
typeset -p ptr1 ptr2
}
typeset -p ptr2
1:up-reference for private namerefs, end unset and not in scope
F:See K01nameref.ztst up-reference part 5
F:Here ptr1 finds private ptr2 by scope mismatch
>typeset -n ptr1=ptr2
*?*read-only variable: ptr2
() {
typeset -n ptr1=ptr2
private -n ptr2= # Assignment makes this a placeholder, not unset
typeset -p ptr1 ptr2
typeset val=LOCAL
() {
ptr1=val # This is a silent no-op, why?
typeset -n
printf "%s=%s\n" ptr1 "$ptr1" ptr2 "$ptr2"
}
@ -323,8 +344,9 @@ F:future revision will create a global with this assignment
}
typeset -p ptr2
1:up-reference for private namerefs, end not in scope
F:See K01typeset.ztst up-reference part 5
F:Here ptr1 finds private ptr2 by scope mismatch, assignment silently fails
F:See K01nameref.ztst up-reference part 5
F:Here ptr1 finds private ptr2 by scope mismatch
F:Assignment silently fails, is that correct?
>typeset -n ptr1=ptr2
>ptr1=ptr2
>ptr1=
@ -335,11 +357,11 @@ F:Here ptr1 finds private ptr2 by scope mismatch, assignment silently fails
typeset ptr2
() {
typeset -n ptr1=ptr2
private -n ptr2
private -n ptr2 # Set/unset is irrelevant, not referenced
typeset -p ptr1 ptr2
typeset val=LOCAL
() {
ptr1=val;
ptr1=val
typeset -n
printf "%s=%s\n" ptr1 "$ptr1" ptr2 "$ptr2"
}
@ -401,6 +423,88 @@ F:Should we allow "public" namerefs to private parameters?
1:private may not change parameter type
?(anon):private:2: can't change type of private param: x
() {
private fd1 fd2
exec {fd1}>&1
print OK
() { exec {fd2}>&2 }
print BAD $fd2
}
1:redirection cannot assign private in wrong scope
F:Better if caught in checkclobberparam() but exec.c doesn't know scope
>OK
?(anon): fd2: can't modify read-only parameter
() {
private z=outer
print ${(t)z} $z
print ${|
print ${(t)z} $z
REPLY=$z
}
}
0:nofork may read private in calling function
>scalar-local-hide-special outer
>scalar-local-hide-special outer
>outer
() {
private z=outer
print ${(t)z} $z
print ${| REPLY=${|z| z=nofork} }
print ${(t)z} $z
}
0:nofork may write to private in calling function
>scalar-local-hide-special outer
>nofork
>scalar-local-hide-special nofork
() {
local q=outer
print ${|
private q=nofork
REPLY=${| REPLY=$q}
}
}
0:nofork cannot see private in surrounding nofork
>outer
() {
private z=outer
print ${(t)z} $z
print ${|z|
private q
z=${|q| q=nofork}
}
print ${(t)z} $z
}
1:nofork may not change private in surrounding nofork
>scalar-local-hide-special outer
*?*: q: can't modify read-only parameter
() {
private q=outer
print ${|
() { REPLY="{$q}" }
}
print ${|q|
() { q=nofork }
}
}
1:function may not access private from inside nofork
>{}
*?*: q: can't modify read-only parameter
() {
print ${|
private q
() { q=nofork }
}
}
1:function may not access private declared in nofork
*?*: q: can't modify read-only parameter
%clean
unsetopt TYPESET_TO_UNSET
rm -r private.TMP

Loading…
Cancel
Save