about summary refs log tree commit diff
diff options
context:
space:
mode:
authorBart Schaefer <schaefer@zsh.org>2024-02-23 09:51:06 -0800
committerBart Schaefer <schaefer@zsh.org>2024-02-23 09:51:06 -0800
commit4b9cd6b8bd5f67500e716f8485aebd31a9f7cf47 (patch)
tree5376037a7d85f0b43189308f2ea5c476fea74c55
parent40d5200c8b882708ecd411bd88760e20deec82fd (diff)
downloadzsh-4b9cd6b8bd5f67500e716f8485aebd31a9f7cf47.tar.gz
zsh-4b9cd6b8bd5f67500e716f8485aebd31a9f7cf47.tar.xz
zsh-4b9cd6b8bd5f67500e716f8485aebd31a9f7cf47.zip
52583: extra check for proper scope and existence of readonly specials
-rw-r--r--ChangeLog6
-rw-r--r--Src/params.c25
-rw-r--r--Test/V10private.ztst41
3 files changed, 70 insertions, 2 deletions
diff --git a/ChangeLog b/ChangeLog
index 91503f01d..d520ec8c5 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2024-02-23  Bart Schaefer  <schaefer@zsh.org>
+
+	* 52583: Src/params.c, Test/V10private.ztst: do an extra check
+	for proper scope and parameter existence when assigning to a
+	non-local name that resolves to a readonly special.
+
 2024-02-22  Oliver Kiddle  <opk@zsh.org>
 
 	* 52552: Completion/Unix/Command/_java: newer Java supports
diff --git a/Src/params.c b/Src/params.c
index b329d2079..225acb8a1 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -1004,8 +1004,29 @@ createparam(char *name, int flags)
 			 gethashnode2(paramtab, name) :
 			 paramtab->getnode(paramtab, name));
 
-	if (oldpm && (oldpm->node.flags & PM_NAMEREF) &&
-	    !(flags & PM_NAMEREF) && (oldpm = upscope(oldpm, oldpm->base))) {
+	if (oldpm && (oldpm->node.flags & PM_RO_BY_DESIGN)) {
+	    if (!(flags & PM_LOCAL)) {
+		/* Must call the API for namerefs and specials to work */
+		pm = (Param) paramtab->getnode2(paramtab, oldpm->node.nam);
+		if (!pm || ((pm->node.flags & PM_NAMEREF) &&
+			    pm->level != locallevel)) {
+		    zerr("%s: can't modify read-only parameter", name);
+		    return NULL;
+		}
+	    }
+	    /**
+	     * Implementation note: In the case of a readonly nameref,
+	     * the right thing might be to insert a new global into
+	     * the paramtab and point the local pm->old at it, rather
+	     * than error.  That is why gethashnode2() is called
+	     * first, to avoid skipping up the stack prematurely.
+	     **/
+	}
+
+	if (oldpm && !(flags & PM_NAMEREF) &&
+	    (!(oldpm->node.flags & PM_RO_BY_DESIGN) || !(flags & PM_LOCAL)) &&
+	    (oldpm->node.flags & PM_NAMEREF) &&
+	    (oldpm = upscope(oldpm, oldpm->base))) {
 	    Param lastpm;
 	    struct asgment stop;
 	    stop.flags = PM_NAMEREF | (flags & PM_LOCAL);
diff --git a/Test/V10private.ztst b/Test/V10private.ztst
index 4140d4e96..efa346002 100644
--- a/Test/V10private.ztst
+++ b/Test/V10private.ztst
@@ -312,6 +312,47 @@ F:future revision will create a global with this assignment
 >UP:
 
  () {
+   typeset -a ary
+   local -P -n ref=ary
+   {
+    (){
+     ref=XX	# Should be an error
+     typeset -p ary ref
+    }
+   } always {
+    TRY_BLOCK_ERROR=0
+    typeset -p ary ref
+   }
+ }
+ typeset -p ary
+1:assignment to private nameref in wrong scope, part 1
+>typeset -a ary
+>typeset -hn ref=ary
+*?*ref: can't modify read-only parameter
+*?*no such variable: ary
+
+ () {
+   typeset -a ary
+   local -P -n ref=ary
+   {
+    (){
+     typeset ref=XX	# Should create a local
+     typeset -p ary ref
+    }
+   } always {
+    TRY_BLOCK_ERROR=0
+    typeset -p ary ref
+   }
+ }
+ typeset -p ary
+1:assignment to private nameref in wrong scope, part 2
+>typeset -g -a ary
+>typeset ref=XX
+>typeset -a ary
+>typeset -hn ref=ary
+*?*no such variable: ary
+
+ () {
    typeset -n ptr1=ptr2
    private -n ptr2	# TYPESET_TO_UNSET makes this not a "placeholder"
    typeset -p ptr1 ptr2