1
0
Fork 0
mirror of git://git.code.sf.net/p/zsh/code synced 2025-06-14 08:08:10 +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>
* 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))
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
parameter var(rname) is expanded instead. This is similar to the
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
no assignment occurs.
Also unlike `tt((P))' named references always expand parameters at
the scope in which var(rname) existed when `tt(typeset -n)' was
called. This can be used to expand or assign parameters from an
earlier scope even if a local of the same name has been declared at
a later scope. Example:
Also unlike `tt((P))' named references always expand parameters at the
scope in which var(rname) exists when var(pname) is initialized. This
can be used to expand or assign parameters from an earlier scope even
if a local of the same name has been declared at a later scope.
Example:
ifzman()
example(tt(caller=OUTER)
tt(func LPAR()RPAR() {)
@ -1600,12 +1607,13 @@ example(tt(before local: OUTER)
tt(by reference: OUTER)
tt(after func: RESULT))
To force a named reference to refer to the outer scope, even if a local
has already been declared, add the tt(-u) option when declaring the
named reference. In this case var(rname) should already exist in the
outer scope, otherwise the behavior of assignment through var(pname)
is not defined and may change the scope of the reference or fail with
a status of 1. Example of correct usage:
To force a named reference to refer to the outer scope, even if a
local has already been declared, add the tt(-u) option when declaring
the named reference. In this case var(rname) should already exist in
the outer scope before var(pname) is initialized, otherwise the
behavior of assignment through var(pname) is not defined and may
change the scope of the reference or fail with a status of 1. Example
of correct usage:
ifzman()
example(tt(caller=OUTER)
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 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
is interpreted at the time tt(${)var(pname)tt(}) is expanded. Any
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
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)
sect(Parameters Set By The Shell)
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))
{
Param pm = (Param)hn;
Param hidden = NULL;
if (pm->level > locallevel) {
hidden = pm->old;
if ((pm->node.flags & (PM_SPECIAL|PM_REMOVABLE)) == PM_SPECIAL) {
/*
* Removable specials are normal in that they can be removed
@ -5891,9 +5893,15 @@ scanendscope(HashNode hn, UNUSED(int flags))
export_param(pm);
} else
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;
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) {
pm->base = basepm->level;
if ((pm->node.flags & PM_UPPER) &&
(basepm = upscope(basepm, -(locallevel-1))))
(basepm = upscope(basepm, -locallevel)))
pm->base = basepm->level;
}
} else if (pm->base < locallevel && refname &&
(basepm = (Param)getparamnode(realparamtab, refname))) {
pm->base = basepm->level;
if ((pm->node.flags & PM_UPPER) &&
(basepm = upscope(basepm, -(locallevel-1))))
(basepm = upscope(basepm, -locallevel)))
pm->base = basepm->level;
}
if (pm->base > pm->level) {

View file

@ -77,6 +77,61 @@
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
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
>k:1: 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: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
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
>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
# assignable special with side-effects. This crashed at one time.