ash: fix handling of duplicate "local"
authorDenys Vlasenko <vda.linux@googlemail.com>
Sat, 18 Apr 2015 17:36:38 +0000 (19:36 +0200)
committerDenys Vlasenko <vda.linux@googlemail.com>
Sat, 18 Apr 2015 17:36:38 +0000 (19:36 +0200)
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
shell/ash.c
shell/ash_test/ash-heredoc/heredoc1.right
shell/ash_test/ash-vars/var3.right [new file with mode: 0644]
shell/ash_test/ash-vars/var3.tests [new file with mode: 0755]

index b568013b425585973519dd08d7f834685a501f5e..697a64feaf22817610f4d4915c12904b11cc95cd 100644 (file)
@@ -2030,7 +2030,7 @@ varcmp(const char *p, const char *q)
        int c, d;
 
        while ((c = *p) == (d = *q)) {
-               if (!c || c == '=')
+               if (c == '\0' || c == '=')
                        goto out;
                p++;
                q++;
@@ -2247,7 +2247,7 @@ setvar(const char *name, const char *val, int flags)
 }
 
 static void FAST_FUNC
-setvar2(const char *name, const char *val)
+setvar0(const char *name, const char *val)
 {
        setvar(name, val, 0);
 }
@@ -2310,7 +2310,7 @@ unsetvar(const char *s)
                        free(vp);
                        INT_ON;
                } else {
-                       setvar2(s, 0);
+                       setvar0(s, NULL);
                        vp->flags &= ~VEXPORT;
                }
  ok:
@@ -5505,7 +5505,7 @@ ash_arith(const char *s)
        arith_t result;
 
        math_state.lookupvar = lookupvar;
-       math_state.setvar    = setvar2;
+       math_state.setvar    = setvar0;
        //math_state.endofname = endofname;
 
        INT_OFF;
@@ -6360,7 +6360,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
 
        switch (subtype) {
        case VSASSIGN:
-               setvar2(varname, startp);
+               setvar0(varname, startp);
                amount = startp - expdest;
                STADJUST(amount, expdest);
                return startp;
@@ -8591,7 +8591,7 @@ evalfor(union node *n, int flags)
        loopnest++;
        flags &= EV_TESTED;
        for (sp = arglist.list; sp; sp = sp->next) {
-               setvar2(n->nfor.var, sp->text);
+               setvar0(n->nfor.var, sp->text);
                evaltree(n->nfor.body, flags);
                if (evalskip) {
                        if (evalskip == SKIPCONT && --skipcount <= 0) {
@@ -8970,21 +8970,37 @@ mklocal(char *name)
        struct localvar *lvp;
        struct var **vpp;
        struct var *vp;
+       char *eq = strchr(name, '=');
 
        INT_OFF;
-       lvp = ckzalloc(sizeof(struct localvar));
+       /* Cater for duplicate "local". Examples:
+        * x=0; f() { local x=1; echo $x; local x; echo $x; }; f; echo $x
+        * x=0; f() { local x=1; echo $x; local x=2; echo $x; }; f; echo $x
+        */
+       lvp = localvars;
+       while (lvp) {
+               if (varcmp(lvp->vp->var_text, name) == 0) {
+                       if (eq)
+                               setvareq(name, 0);
+                       /* else:
+                        * it's a duplicate "local VAR" declaration, do nothing
+                        */
+                       return;
+               }
+               lvp = lvp->next;
+       }
+
+       lvp = ckzalloc(sizeof(*lvp));
        if (LONE_DASH(name)) {
                char *p;
                p = ckmalloc(sizeof(optlist));
                lvp->text = memcpy(p, optlist, sizeof(optlist));
                vp = NULL;
        } else {
-               char *eq;
-
                vpp = hashvar(name);
                vp = *findvar(vpp, name);
-               eq = strchr(name, '=');
                if (vp == NULL) {
+                       /* variable did not exist yet */
                        if (eq)
                                setvareq(name, VSTRFIXED);
                        else
@@ -8994,12 +9010,15 @@ mklocal(char *name)
                } else {
                        lvp->text = vp->var_text;
                        lvp->flags = vp->flags;
+                       /* make sure neither "struct var" nor string gets freed
+                        * during (un)setting:
+                        */
                        vp->flags |= VSTRFIXED|VTEXTFIXED;
                        if (eq)
                                setvareq(name, 0);
                        else
                                /* "local VAR" unsets VAR: */
-                               setvar(name, NULL, 0);
+                               setvar0(name, NULL);
                }
        }
        lvp->vp = vp;
@@ -9491,7 +9510,7 @@ evalcommand(union node *cmd, int flags)
                 * '_' in 'vi' command mode during line editing...
                 * However I implemented that within libedit itself.
                 */
-               setvar2("_", lastarg);
+               setvar0("_", lastarg);
        }
        popstackmark(&smark);
 }
@@ -12885,7 +12904,7 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
         * to jump out of it.
         */
        INT_OFF;
-       r = shell_builtin_read(setvar2,
+       r = shell_builtin_read(setvar0,
                argptr,
                bltinlookup("IFS"), /* can be NULL */
                read_flags,
@@ -13046,14 +13065,14 @@ init(void)
                        }
                }
 
-               setvar2("PPID", utoa(getppid()));
+               setvar0("PPID", utoa(getppid()));
 #if ENABLE_ASH_BASH_COMPAT
                p = lookupvar("SHLVL");
                setvar("SHLVL", utoa((p ? atoi(p) : 0) + 1), VEXPORT);
                if (!lookupvar("HOSTNAME")) {
                        struct utsname uts;
                        uname(&uts);
-                       setvar2("HOSTNAME", uts.nodename);
+                       setvar0("HOSTNAME", uts.nodename);
                }
 #endif
                p = lookupvar("PWD");
@@ -13309,7 +13328,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
                                hp = lookupvar("HOME");
                                if (hp) {
                                        hp = concat_path_file(hp, ".ash_history");
-                                       setvar2("HISTFILE", hp);
+                                       setvar0("HISTFILE", hp);
                                        free((char*)hp);
                                        hp = lookupvar("HISTFILE");
                                }
index 895f5ee801f05170cd9f44b38a78526dc65d602b..40aa5a5fe397caf0499f3e9e7886be1522673952 100644 (file)
@@ -1 +1 @@
-heredoc1.tests: line 3: syntax error: unexpected "then"
+./heredoc1.tests: line 3: syntax error: unexpected "then"
diff --git a/shell/ash_test/ash-vars/var3.right b/shell/ash_test/ash-vars/var3.right
new file mode 100644 (file)
index 0000000..8eb0e33
--- /dev/null
@@ -0,0 +1,5 @@
+1
+1
+
+
+0
diff --git a/shell/ash_test/ash-vars/var3.tests b/shell/ash_test/ash-vars/var3.tests
new file mode 100755 (executable)
index 0000000..97b102c
--- /dev/null
@@ -0,0 +1 @@
+x=0; f() { local x=1; echo $x; local x; echo $x; unset x; echo $x; local x; echo $x; }; f; echo $x