1
0
Fork 0
mirror of git://git.code.sf.net/p/zsh/code synced 2025-09-27 06:11:06 +02:00

53568: fix additional problems with reference scoping, update documentation

Scope exits could miss some scope updates in named reference chains.
References declared -u behave like any other reference upon scope exit.
This commit is contained in:
Bart Schaefer 2025-05-09 16:38:28 -07:00
parent d01a2870ea
commit abd541e18c
5 changed files with 128 additions and 18 deletions

View file

@ -1,3 +1,11 @@
2025-05-09 Bart Schaefer <schaefer@zsh.org>
* 53568: Doc/Zsh/expn.yo, Doc/Zsh/params.yo, Src/params.c,
Test/K01nameref.ztst: fix additional problems with references
whose referent goes out of scope, add corresponding tests, and
update documentation to better describe behavior and warn about
potentially confusing usage
2025-05-09 Peter Stephenson <p.stephenson@samsung.com> 2025-05-09 Peter Stephenson <p.stephenson@samsung.com>
* 53532: INSTALL, configure.ac: mark --enable-zsh-mem option * 53532: INSTALL, configure.ac: mark --enable-zsh-mem option

View file

@ -1560,7 +1560,14 @@ ifzman()
indent(tt(typeset -n )var(pname)tt(=)var(rname)) indent(tt(typeset -n )var(pname)tt(=)var(rname))
initializes a parameter var(pname) as a reference to a second initializes a parameter var(pname) as a reference to a second
parameter var(rname). With the few exceptions described here, when parameter var(rname). The var(rname) may also be omitted, in which
case var(pname) is a placeholder which, the first time it is assigned,
is initialized to an active reference to the assigned var(rname). See
ifzman(Named References in zmanref(zshparam))\
ifnzman(noderef(Named References) under noderef(Parameters))
for more about placeholders.
With the few exceptions described here, when
var(pname) is used in any of the expansion forms described above, the var(pname) is used in any of the expansion forms described above, the
parameter var(rname) is expanded instead. This is similar to the parameter var(rname) is expanded instead. This is similar to the
action of the `tt((P))' expansion flag, but when var(rname) has itself action of the `tt((P))' expansion flag, but when var(rname) has itself
@ -1577,11 +1584,11 @@ This includes arrays declared, but not initialized, when the option
tt(TYPESET_TO_UNSET) is in effect. The var(word) is substituted but tt(TYPESET_TO_UNSET) is in effect. The var(word) is substituted but
no assignment occurs. no assignment occurs.
Also unlike `tt((P))' named references always expand parameters at Also unlike `tt((P))' named references always expand parameters at the
the scope in which var(rname) existed when `tt(typeset -n)' was scope in which var(rname) exists when var(pname) is initialized. This
called. This can be used to expand or assign parameters from an can be used to expand or assign parameters from an earlier scope even
earlier scope even if a local of the same name has been declared at if a local of the same name has been declared at a later scope.
a later scope. Example: Example:
ifzman() ifzman()
example(tt(caller=OUTER) example(tt(caller=OUTER)
tt(func LPAR()RPAR() {) tt(func LPAR()RPAR() {)
@ -1600,12 +1607,13 @@ example(tt(before local: OUTER)
tt(by reference: OUTER) tt(by reference: OUTER)
tt(after func: RESULT)) tt(after func: RESULT))
To force a named reference to refer to the outer scope, even if a local To force a named reference to refer to the outer scope, even if a
has already been declared, add the tt(-u) option when declaring the local has already been declared, add the tt(-u) option when declaring
named reference. In this case var(rname) should already exist in the the named reference. In this case var(rname) should already exist in
outer scope, otherwise the behavior of assignment through var(pname) the outer scope before var(pname) is initialized, otherwise the
is not defined and may change the scope of the reference or fail with behavior of assignment through var(pname) is not defined and may
a status of 1. Example of correct usage: change the scope of the reference or fail with a status of 1. Example
of correct usage:
ifzman() ifzman()
example(tt(caller=OUTER) example(tt(caller=OUTER)
tt(func LPAR()RPAR() {) tt(func LPAR()RPAR() {)
@ -1623,6 +1631,13 @@ Note, however, that named references to em(special) parameters acquire
the behavior of the special parameter, regardless of the scope where the behavior of the special parameter, regardless of the scope where
the reference is declared. the reference is declared.
In the event that the local var(pname) goes out of scope (its declaring
function returns) before the reference var(rname) goes out of scope,
the reference may change to another parameter having the same name as
var(pname), or assignments may fail as described above. Keep the
declaration of var(rname) as close as possible to its initialization
to var(pname) to avoid confusion.
When var(rname) includes an array subscript, the subscript expression When var(rname) includes an array subscript, the subscript expression
is interpreted at the time tt(${)var(pname)tt(}) is expanded. Any is interpreted at the time tt(${)var(pname)tt(}) is expanded. Any
form of subscript is allowed, including those that select individual form of subscript is allowed, including those that select individual

View file

@ -694,6 +694,15 @@ assignments to, var(pname) act on the referenced parameter. This
is explained in the Named References section of is explained in the Named References section of
ifzman(zmanref(zshexpn))ifnzman(noderef(Parameter Expansion)). ifzman(zmanref(zshexpn))ifnzman(noderef(Parameter Expansion)).
A placeholder var(pname) in a calling function may be initialized in a
called function to reference a local parameter var(rname). In this
case, when the called function returns, var(pname) is no longer
associated with that local. However, the initializer var(rname)
continues to be substituted when `tt($)var(pname)' is used, and
therefore may become a reference to another parameter in the calling
function. It is recommended that placeholders be initialized soon
after they are declared, to make it clear what they reference.
texinode(Parameters Set By The Shell)(Parameters Used By The Shell)(Named References)(Parameters) texinode(Parameters Set By The Shell)(Parameters Used By The Shell)(Named References)(Parameters)
sect(Parameters Set By The Shell) sect(Parameters Set By The Shell)
In the parameter lists that follow, the mark `<S>' indicates that the In the parameter lists that follow, the mark `<S>' indicates that the

View file

@ -5828,7 +5828,9 @@ static void
scanendscope(HashNode hn, UNUSED(int flags)) scanendscope(HashNode hn, UNUSED(int flags))
{ {
Param pm = (Param)hn; Param pm = (Param)hn;
Param hidden = NULL;
if (pm->level > locallevel) { if (pm->level > locallevel) {
hidden = pm->old;
if ((pm->node.flags & (PM_SPECIAL|PM_REMOVABLE)) == PM_SPECIAL) { if ((pm->node.flags & (PM_SPECIAL|PM_REMOVABLE)) == PM_SPECIAL) {
/* /*
* Removable specials are normal in that they can be removed * Removable specials are normal in that they can be removed
@ -5891,9 +5893,15 @@ scanendscope(HashNode hn, UNUSED(int flags))
export_param(pm); export_param(pm);
} else } else
unsetparam_pm(pm, 0, 0); unsetparam_pm(pm, 0, 0);
} else if ((pm->node.flags & PM_NAMEREF) && }
pm->base > pm->level && pm->base > locallevel) if (hidden)
pm = hidden;
if (pm && (pm->node.flags & PM_NAMEREF) &&
pm->base >= pm->level && pm->base >= locallevel) {
pm->base = locallevel; pm->base = locallevel;
if (pm->level < locallevel && (pm->node.flags & PM_UPPER))
pm->node.flags &= ~PM_UPPER;
}
} }
@ -6404,14 +6412,14 @@ setscope(Param pm)
} else if (!pm->base) { } else if (!pm->base) {
pm->base = basepm->level; pm->base = basepm->level;
if ((pm->node.flags & PM_UPPER) && if ((pm->node.flags & PM_UPPER) &&
(basepm = upscope(basepm, -(locallevel-1)))) (basepm = upscope(basepm, -locallevel)))
pm->base = basepm->level; pm->base = basepm->level;
} }
} else if (pm->base < locallevel && refname && } else if (pm->base < locallevel && refname &&
(basepm = (Param)getparamnode(realparamtab, refname))) { (basepm = (Param)getparamnode(realparamtab, refname))) {
pm->base = basepm->level; pm->base = basepm->level;
if ((pm->node.flags & PM_UPPER) && if ((pm->node.flags & PM_UPPER) &&
(basepm = upscope(basepm, -(locallevel-1)))) (basepm = upscope(basepm, -locallevel)))
pm->base = basepm->level; pm->base = basepm->level;
} }
if (pm->base > pm->level) { if (pm->base > pm->level) {

View file

@ -77,6 +77,61 @@
local s=T a=(T); local s=T a=(T);
function f1() {
typeset var=$0;
typeset -n ref1 ref2; typeset -n ref3=ref2;
function f2() {
typeset ref2=XX;
function f3() {
typeset var=$0;
function f4() {
typeset var=$0;
ref1=var;
ref3=var; # Initializes ref2 to var
echo "$0: ref1=$ref1 ref2=$ref2 ref3=$ref3";
}
f4;
echo "$0: ref1=$ref1 ref2=$ref2 ref3=$ref3";
function g4() {
typeset var=$0;
function g5() {
typeset var=$0;
echo "$0: ref1=$ref1 ref2=$ref2 ref3=$ref3";
}
g5;
echo "$0: ref1=$ref1 ref2=$ref2 ref3=$ref3";
}
g4;
echo "$0: ref1=$ref1 ref2=$ref2 ref3=$ref3";
}
f3;
echo "$0: ref1=$ref1 ref2=$ref2 ref3=$ref3";
}
f2;
echo "$0: ref1=$ref1 ref2=$ref2 ref3=$ref3";
function h2() {
typeset var=$0;
function h3() {
typeset var=$0;
function h4() {
typeset var=$0;
function h5() {
typeset var=$0;
echo "$0: ref1=$ref1 ref2=$ref2 ref3=$ref3";
}
h5;
echo "$0: ref1=$ref1 ref2=$ref2 ref3=$ref3";
}
h4;
echo "$0: ref1=$ref1 ref2=$ref2 ref3=$ref3";
}
h3;
echo "$0: ref1=$ref1 ref2=$ref2 ref3=$ref3";
}
h2;
echo "$0: ref1=$ref1 ref2=$ref2 ref3=$ref3";
}
%test %test
typeset -n ptr typeset -n ptr
@ -1039,10 +1094,10 @@ F:previously this could create an infinite recursion and crash
>i:3: rs=h - ra=h - rs1=h - ra1=h >i:3: rs=h - ra=h - rs1=h - ra1=h
>k:1: rs=h - ra=h - rs1=h - ra1=h >k:1: rs=h - ra=h - rs1=h - ra1=h
>k:2: rs=h - ra=h - rs1=h - ra1=h >k:2: rs=h - ra=h - rs1=h - ra1=h
>h:3: rs=g - ra=g - rs1=g - ra1=g >h:3: rs=h - ra=h - rs1=h - ra1=h
>k:1: rs=h - ra=h - rs1=h - ra1=h >k:1: rs=h - ra=h - rs1=h - ra1=h
>k:2: rs=h - ra=h - rs1=h - ra1=h >k:2: rs=h - ra=h - rs1=h - ra1=h
>g:3: rs=f - ra=f - rs1=f - ra1=f >g:3: rs=g - ra=g - rs1=g - ra1=g
e '' 6 e '' 6
0:assignment at different scope than declaration, '' 6 0:assignment at different scope than declaration, '' 6
@ -1062,6 +1117,21 @@ F:previously this could create an infinite recursion and crash
>k:2: rs=h - ra=h - rs1=h - ra1=h >k:2: rs=h - ra=h - rs1=h - ra1=h
>g:3: rs=g - ra=g - rs1=g - ra1=g >g:3: rs=g - ra=g - rs1=g - ra1=g
f1
0:Transitive references with scoping changes
>f4: ref1=f4 ref2=XX ref3=f4
>f3: ref1=f3 ref2=XX ref3=f3
>g5: ref1=f3 ref2=XX ref3=g4
>g4: ref1=f3 ref2=XX ref3=g4
>f3: ref1=f3 ref2=XX ref3=f3
>f2: ref1=f1 ref2=XX ref3=f1
>f1: ref1=f1 ref2=f1 ref3=f1
>h5: ref1=f1 ref2=f1 ref3=f1
>h4: ref1=f1 ref2=f1 ref3=f1
>h3: ref1=f1 ref2=f1 ref3=f1
>h2: ref1=f1 ref2=f1 ref3=f1
>f1: ref1=f1 ref2=f1 ref3=f1
# #
# The following tests are run in interactive mode, using PS1 as an # The following tests are run in interactive mode, using PS1 as an
# assignable special with side-effects. This crashed at one time. # assignable special with side-effects. This crashed at one time.