Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
Re: A solution to fix hidden references in reference chains
- X-seq: zsh-workers 54196
- From: Philippe Altherr <philippe.altherr@xxxxxxxxx>
- To: Zsh hackers list <zsh-workers@xxxxxxx>
- Subject: Re: A solution to fix hidden references in reference chains
- Date: Tue, 17 Feb 2026 22:50:56 +0100
- Arc-authentication-results: i=1; mx.google.com; arc=none
- Arc-message-signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=to:subject:message-id:date:from:in-reply-to:references:mime-version :dkim-signature; bh=1gf7deUCwM7PWRdEct6zb6OQGzoqFCNSMXf8Tg7HaUI=; fh=BgAYDYpL6Ne/A5nWEMVJiHiBtrz8Imz3uf26RDwgQX4=; b=T7au4ZI4ZmFV5VPeuyFVK1rIjb+yad3o9iGxc8mSMQGGXSSfLxxaeqnjP0JVctobBl 261fG6txW6Xq2BNumW7ujRYFYt9JdBHD5hHgvdxa486mnGlkdGchKR5pEpLvlU7jmnPy XeVVxEpa+/8MZMs7U9ziZoouNR9PYnIM0LJ0Lbd0WHyds53gutOIllyp162EZ80/754Z 2IkXfRKHQPHdxWbntY5UfIIz6ssA+Tks4CLvNbM/YQBlDirm8aVyIJ38WrqSwEWzYFX0 USSofSMQObgRc/l/0sQ+br+wXLJWJd7AbG70yspdU9YAeG17rzyeumRPp9gFDUdguVaq hi8Q==; darn=zsh.org
- Arc-seal: i=1; a=rsa-sha256; t=1771365070; cv=none; d=google.com; s=arc-20240605; b=BxVWb50Uk47MDMaUKgv/Jb6xBx/dqMedca0axAUtXQqYjlWnkOG1PXxjQY5K0j5smF 360GE8ZsgqApgBXVFYoUADmw9zijQyy1gRNL0ovoaIShS2YiCAUw9Ozi0LajflfZsExH MdHYy85K3mD5ZvIlscP5ZwL1a3XODwyaiihB3P5sbhq//kF7ldxUXbrqeemhcyxzS5FE IZQC6FxsmQuM7DJ2Y/MpQSLNfndRhxNndBnZxh/4xWUEt/US3fF9k9PJqxS19C8vzAWR NHNCcxuse9vFs2+37qCHLckhd8tH6Ax5UHBYSdvm93+HKw0nrrLRpfZBC4x/nlnVk4gv MAwA==
- Archived-at: <https://zsh.org/workers/54196>
- In-reply-to: <CAGdYchuNP=3pfWYRX7iq3wqTf1tzwuUA+txdpPiKrLAcATMLdg@mail.gmail.com>
- List-id: <zsh-workers.zsh.org>
- References: <CAGdYchu3rxH9MBanWDLTuWcaUNh+g1suQD1bRmC+Pbcj+QJY1A@mail.gmail.com> <CAGdYchvU3=Zzk8q-V36EEf9WDHd6Y9Mjgg=mkKFVRn0BbGuAcA@mail.gmail.com> <CAGdYchuNP=3pfWYRX7iq3wqTf1tzwuUA+txdpPiKrLAcATMLdg@mail.gmail.com>
Here is an updated patch obtained after rebasing onto the latest head.
Here is an updated patch. I slightly changed the code and rebased onto the latest head.
Philippe
Whenever the "base" field of a "Param" is set, if "base" is strictly greater than the "level" field, we know the "Param" refers to a nested variable. We also know that its "base" will have to be updated when the "base" scope is exited. We can therefore add the "Param" to a list linked to the "base" scope.
Whenever a scope is exited, first check whether any "Param"s were added to its list. If yes, the "base" fields of these are updated (if the "Param"s are still named references). Then call a "scanendscope" that doesn't do anything special for named references. Or do it in the opposite order?
This assumes that "Params" are only ever freed by "scanendscope". If that's not the case, things get more complicated as one would also sometimes have to remove "Param"s from the scope lists.
I could try to prepare a patch that implements this but I would first need advice on the preferred way to implement the lists of "Param"s (linked list, new "Param" field, ...) and where/how to store them (global array, under special names in the parameter table, ...).
As a reminder, this would fix "ref2" in
this test such that the output for "ref3" is the same as the one for "ref1".
Philippe
diff --git a/Src/params.c b/Src/params.c
index 3199fd17b..d94e32eb0 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
@@ -5845,6 +5863,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 */
@@ -5872,6 +5891,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_UPPER) &&
+ pm->base > locallevel) {
+ setscope_base(pm, locallevel);
+ }
+ }
unqueue_signals();
}
@@ -5880,9 +5906,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
@@ -5946,14 +5970,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;
}
}
@@ -6400,7 +6416,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)) {
@@ -6426,6 +6442,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)
diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst
index e0f7b1879..7d859c8bc 100644
--- a/Test/K01nameref.ztst
+++ b/Test/K01nameref.ztst
@@ -1183,8 +1183,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
Messages sorted by:
Reverse Date,
Date,
Thread,
Author