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:
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>
|
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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
16
Src/params.c
16
Src/params.c
|
@ -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) {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in a new issue