diff options
author | Bart Schaefer <schaefer@zsh.org> | 2024-02-03 12:07:14 -0800 |
---|---|---|
committer | Bart Schaefer <schaefer@zsh.org> | 2024-02-03 12:07:14 -0800 |
commit | 8801665e5b241c3adac9c36b6135d057c5ab2a59 (patch) | |
tree | 84a2143d3b83d2201ab5dec00c7658eb8339fac9 | |
parent | 18400b68e49b242da55ca3a465ea496d26f47938 (diff) | |
download | zsh-8801665e5b241c3adac9c36b6135d057c5ab2a59.tar.gz zsh-8801665e5b241c3adac9c36b6135d057c5ab2a59.tar.xz zsh-8801665e5b241c3adac9c36b6135d057c5ab2a59.zip |
52513: fixes and doc for using nofork substitutions with private parameters
Also fixes a crash bug with {fd}>&N redirections and private parameters
-rw-r--r-- | ChangeLog | 7 | ||||
-rw-r--r-- | Doc/Zsh/mod_private.yo | 9 | ||||
-rw-r--r-- | Src/Modules/param_private.c | 53 | ||||
-rw-r--r-- | Src/params.c | 17 | ||||
-rw-r--r-- | Test/V10private.ztst | 118 |
5 files changed, 170 insertions, 34 deletions
diff --git a/ChangeLog b/ChangeLog index 46cd45647..fbfae8589 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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 diff --git a/Doc/Zsh/mod_private.yo b/Doc/Zsh/mod_private.yo index 69a5f58be..08ac4cafe 100644 --- a/Doc/Zsh/mod_private.yo +++ b/Doc/Zsh/mod_private.yo @@ -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 diff --git a/Src/Modules/param_private.c b/Src/Modules/param_private.c index 7ef6633da..5003d4627 100644 --- a/Src/Modules/param_private.c +++ b/Src/Modules/param_private.c @@ -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); @@ -540,18 +540,19 @@ static struct funcwrap wrapper[] = { }; /**/ +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; } diff --git a/Src/params.c b/Src/params.c index 9f0cbcd67..a722a20f6 100644 --- a/Src/params.c +++ b/Src/params.c @@ -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) { diff --git a/Test/V10private.ztst b/Test/V10private.ztst index d902cac56..9eeda0f47 100644 --- a/Test/V10private.ztst +++ b/Test/V10private.ztst @@ -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 |