mirror of
https://git.code.sf.net/p/zsh/code
synced 2026-04-18 06:53:35 -04:00
54262: track and revert hidden references in chains across locallevel scopes
This commit is contained in:
committed by
Bart Schaefer
parent
d8d74ef682
commit
8edd5029de
@@ -1,5 +1,9 @@
|
||||
2026-03-31 Bart Schaefer <schaefer@zsh.org>
|
||||
|
||||
* Philippe: 54262: Src/params.c, Test/K01nameref.ztst: track
|
||||
and revert hidden references in chains that extend across
|
||||
locallevel scopes
|
||||
|
||||
* Philippe: 54261: Src/builtin.c, Src/params.c, Src/zsh.h,
|
||||
Test/K01nameref.ztst, Test/V10private.ztst: `unset -n` removes
|
||||
named-reference-ness of a parameter when removing the referent.
|
||||
|
||||
57
Src/params.c
57
Src/params.c
@@ -485,6 +485,24 @@ static initparam argvparam_pm = IPDEF9("", &pparams, NULL, \
|
||||
|
||||
static Param argvparam;
|
||||
|
||||
/*
|
||||
* Lists of references to nested variables ("Param" instances) indexed
|
||||
* by scope. Whenever the "base" scope of a named reference is set to
|
||||
* refer to a variable more deeply nested than the reference itself
|
||||
* ("base > level"), the "base" scope has to be updated once the
|
||||
* "base" scope ends. The "scoperefs" lists keep track of these
|
||||
* references. Since "Param" instances get reused when variables with
|
||||
* the same name are redefined in the same scope, listed "Param"
|
||||
* instances may no longer be references when the scope ends or may
|
||||
* refer to a different "base" scope. A given "Param" instance may
|
||||
* also be included in multiple lists at the same time or multiple
|
||||
* times in the same list. Non of that is harmful as long as only
|
||||
* instances that are still references referring to the ending scope
|
||||
* are updated when the scope ends.
|
||||
*/
|
||||
static LinkList *scoperefs = NULL;
|
||||
static int scoperefs_num = 0;
|
||||
|
||||
/* "parameter table" - hash table containing the parameters
|
||||
*
|
||||
* realparamtab always points to the shell's global table. paramtab is sometimes
|
||||
@@ -5855,6 +5873,7 @@ static int lc_update_needed;
|
||||
mod_export void
|
||||
endparamscope(void)
|
||||
{
|
||||
LinkList refs = locallevel < scoperefs_num ? scoperefs[locallevel] : NULL;
|
||||
queue_signals();
|
||||
locallevel--;
|
||||
/* This pops anything from a higher locallevel */
|
||||
@@ -5882,6 +5901,13 @@ endparamscope(void)
|
||||
clear_mbstate(); /* LC_CTYPE may have changed */
|
||||
}
|
||||
#endif /* USE_LOCALE */
|
||||
/* Reset scope of namerefs that refer to dead variables */
|
||||
for (Param pm; refs && (pm = (Param)getlinknode(refs));) {
|
||||
if ((pm->node.flags & PM_NAMEREF) && !(pm->node.flags & PM_UNSET) &&
|
||||
!(pm->node.flags & PM_UPPER) && pm->base > locallevel) {
|
||||
setscope_base(pm, locallevel);
|
||||
}
|
||||
}
|
||||
unqueue_signals();
|
||||
}
|
||||
|
||||
@@ -5890,9 +5916,7 @@ 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
|
||||
@@ -5956,14 +5980,6 @@ scanendscope(HashNode hn, UNUSED(int flags))
|
||||
export_param(pm);
|
||||
} else
|
||||
unsetparam_pm(pm, 0, 0);
|
||||
pm = NULL;
|
||||
}
|
||||
if (hidden)
|
||||
pm = hidden;
|
||||
if (pm && (pm->node.flags & PM_NAMEREF) &&
|
||||
pm->base >= pm->level && pm->base >= locallevel) {
|
||||
/* Should never get here for a -u reference */
|
||||
pm->base = locallevel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6405,7 +6421,7 @@ setscope(Param pm)
|
||||
(basepm = (Param)gethashnode2(realparamtab, refname)) &&
|
||||
(basepm = (Param)loadparamnode(realparamtab, basepm, refname)) &&
|
||||
(basepm != pm || !basepm->old || (basepm = basepm->old))) {
|
||||
pm->base = basepm->level;
|
||||
setscope_base(pm, basepm->level);
|
||||
}
|
||||
if (pm->base > pm->level) {
|
||||
if (EMULATION(EMULATE_KSH)) {
|
||||
@@ -6431,6 +6447,25 @@ setscope(Param pm)
|
||||
unqueue_signals();
|
||||
}
|
||||
|
||||
/**/
|
||||
static void
|
||||
setscope_base(Param pm, int base)
|
||||
{
|
||||
if ((pm->base = base) > pm->level) {
|
||||
LinkList refs;
|
||||
if (base >= scoperefs_num) {
|
||||
int old_num = scoperefs_num;
|
||||
int new_num = scoperefs_num = MAX(2 * base, 8);
|
||||
scoperefs = zrealloc(scoperefs, new_num * sizeof(refs));
|
||||
memset(scoperefs + old_num, 0, (new_num - old_num) * sizeof(refs));
|
||||
}
|
||||
refs = scoperefs[base];
|
||||
if (!refs)
|
||||
refs = scoperefs[base] = znewlinklist();
|
||||
zpushnode(refs, pm);
|
||||
}
|
||||
}
|
||||
|
||||
/**/
|
||||
static Param
|
||||
upscope(Param pm, const Param ref)
|
||||
|
||||
@@ -1247,8 +1247,8 @@ F:previously this could create an infinite recursion and crash
|
||||
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
|
||||
>g5: ref1=f3 ref2=XX ref3=f3
|
||||
>g4: ref1=f3 ref2=XX ref3=f3
|
||||
>f3: ref1=f3 ref2=XX ref3=f3
|
||||
>f2: ref1=f1 ref2=XX ref3=f1
|
||||
>f1: ref1=f1 ref2=f1 ref3=f1
|
||||
@@ -1885,4 +1885,87 @@ F:converting from association/array to string should work here too
|
||||
># d:reference to not-yet-defined - local - ref1
|
||||
>typeset -i var=42
|
||||
|
||||
typeset -n ref1
|
||||
typeset -n ref2
|
||||
typeset -n ref3=ref2
|
||||
typeset var=aaa
|
||||
() {
|
||||
typeset -i ref2=123 # Hides the reference ref2 in this scope and nested scopes
|
||||
typeset var=bbb
|
||||
() {
|
||||
typeset var=ccc
|
||||
ref1=var
|
||||
ref3=var # From now on ref1 and ref3 should always refer to the same variable
|
||||
echo A:ref1=$ref1 ref2=$ref2 ref3=$ref3
|
||||
} # Both top-level references ref1 and ref2 should be rebound
|
||||
echo B:ref1=$ref1 ref2=$ref2 ref3=$ref3
|
||||
() {
|
||||
typeset var=ddd # No reference should refer to this variable
|
||||
echo C:ref1=$ref1 ref2=$ref2 ref3=$ref3
|
||||
}
|
||||
echo D:ref1=$ref1 ref2=$ref2 ref3=$ref3
|
||||
} # Both top-level references ref1 and ref2 should be rebound
|
||||
echo E:ref1=$ref1 ref2=$ref2 ref3=$ref3
|
||||
() {
|
||||
typeset var=eee # No reference should refer to this variable
|
||||
echo F:ref1=$ref1 ref2=$ref2 ref3=$ref3
|
||||
}
|
||||
echo G:ref1=$ref1 ref2=$ref2 ref3=$ref3
|
||||
0:hidden reference refers to a nested variable
|
||||
>A:ref1=ccc ref2=123 ref3=ccc
|
||||
>B:ref1=bbb ref2=123 ref3=bbb
|
||||
>C:ref1=bbb ref2=123 ref3=bbb
|
||||
>D:ref1=bbb ref2=123 ref3=bbb
|
||||
>E:ref1=aaa ref2=aaa ref3=aaa
|
||||
>F:ref1=aaa ref2=aaa ref3=aaa
|
||||
>G:ref1=aaa ref2=aaa ref3=aaa
|
||||
|
||||
typeset ref
|
||||
typeset var1=var1@scope1
|
||||
typeset var2=var2@scope1
|
||||
() { # enter scope 2
|
||||
typeset var1=var1@scope2
|
||||
typeset var2=var2@scope2
|
||||
typeset -g -n ref=var1; echo A:$ref # ref added to scope 2
|
||||
typeset -g -n ref=var2; echo B:$ref # ref added to scope 2
|
||||
() { # enter scope 3
|
||||
typeset var1=var1@scope3
|
||||
typeset -g -n ref=var1; echo C:$ref # ref added to scope 3
|
||||
() { # enter scope 4
|
||||
typeset var1=var1@scope4
|
||||
typeset var2=var2@scope4
|
||||
typeset -g -n ref=var1; echo D:$ref # ref added to scope 4
|
||||
typeset -g -n ref=var2; echo E:$ref # ref added to scope 4
|
||||
} # leave scope 4: ref rebound to var2 in scope 2 and added to scope 2
|
||||
echo F:$ref
|
||||
} # leave scope 3: ref remains bound to var2 in scope 2
|
||||
echo G:$ref
|
||||
unset -n ref # ref is unset
|
||||
echo H:$ref
|
||||
} # leave scope 2: ref remains unset
|
||||
echo I:$ref
|
||||
0:reference refers successively to multiple variables in multiple nested scopes
|
||||
>A:var1@scope2
|
||||
>B:var2@scope2
|
||||
>C:var1@scope3
|
||||
>D:var1@scope4
|
||||
>E:var2@scope4
|
||||
>F:var2@scope2
|
||||
>G:var2@scope2
|
||||
>H:
|
||||
>I:
|
||||
|
||||
typeset ref
|
||||
() { # enter scope 2
|
||||
typeset var=var
|
||||
typeset -g -n ref=var; echo A:$ref # ref added to scope 2
|
||||
unset -n ref
|
||||
typeset -g -i16 ref=255; echo B:$ref # ref becomes an integer in base 16
|
||||
} # leave scope 2: ref remains an integer in base 16
|
||||
echo C:$ref
|
||||
0:reference referring to a nested variable becomes an integer
|
||||
>A:var
|
||||
>B:16#FF
|
||||
>C:16#FF
|
||||
|
||||
%clean
|
||||
|
||||
Reference in New Issue
Block a user