[git commit] ash: fix handling of duplicate "local"

Denys Vlasenko vda.linux at googlemail.com
Sat Apr 18 17:36:38 UTC 2015


commit: http://git.busybox.net/busybox/commit/?id=0a0acb55db8d7c4dec445573f1b0528d126b9e1f
branch: http://git.busybox.net/busybox/commit/?id=refs/heads/master

Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
 shell/ash.c                               |   51 ++++++++++++++++++++---------
 shell/ash_test/ash-heredoc/heredoc1.right |    2 +-
 shell/ash_test/ash-vars/var3.right        |    5 +++
 shell/ash_test/ash-vars/var3.tests        |    1 +
 4 files changed, 42 insertions(+), 17 deletions(-)

diff --git a/shell/ash.c b/shell/ash.c
index b568013..697a64f 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -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");
 				}
diff --git a/shell/ash_test/ash-heredoc/heredoc1.right b/shell/ash_test/ash-heredoc/heredoc1.right
index 895f5ee..40aa5a5 100644
--- a/shell/ash_test/ash-heredoc/heredoc1.right
+++ b/shell/ash_test/ash-heredoc/heredoc1.right
@@ -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
index 0000000..8eb0e33
--- /dev/null
+++ b/shell/ash_test/ash-vars/var3.right
@@ -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
index 0000000..97b102c
--- /dev/null
+++ b/shell/ash_test/ash-vars/var3.tests
@@ -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


More information about the busybox-cvs mailing list