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:
parent
d01a2870ea
commit
abd541e18c
5 changed files with 128 additions and 18 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
16
Src/params.c
16
Src/params.c
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue