diff options
| author | Philippe Altherr <philippe.altherr@gmail.com> | 2026-03-31 15:06:30 -0700 |
|---|---|---|
| committer | Bart Schaefer <schaefer@zsh.org> | 2026-03-31 18:32:26 -0700 |
| commit | 8edd5029de35ccb988902381511c4c3db6ee0604 (patch) | |
| tree | 4baa422d4138156ca48a5c6339ecf52e6e3e82dd | |
| parent | 54261: `unset -n` removes named-reference type as well as removes referent (diff) | |
| download | zsh-8edd5029de35ccb988902381511c4c3db6ee0604.tar zsh-8edd5029de35ccb988902381511c4c3db6ee0604.tar.gz zsh-8edd5029de35ccb988902381511c4c3db6ee0604.tar.bz2 zsh-8edd5029de35ccb988902381511c4c3db6ee0604.tar.lz zsh-8edd5029de35ccb988902381511c4c3db6ee0604.tar.xz zsh-8edd5029de35ccb988902381511c4c3db6ee0604.tar.zst zsh-8edd5029de35ccb988902381511c4c3db6ee0604.zip | |
54262: track and revert hidden references in chains across locallevel scopes
| -rw-r--r-- | ChangeLog | 4 | ||||
| -rw-r--r-- | Src/params.c | 57 | ||||
| -rw-r--r-- | Test/K01nameref.ztst | 87 |
3 files changed, 135 insertions, 13 deletions
@@ -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. diff --git a/Src/params.c b/Src/params.c index afc67eb14..4f5454abb 100644 --- a/Src/params.c +++ b/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)) { @@ -6432,6 +6448,25 @@ setscope(Param pm) } /**/ +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) { diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst index fb27e7261..82ccbfb89 100644 --- a/Test/K01nameref.ztst +++ b/Test/K01nameref.ztst @@ -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 |
