summaryrefslogtreecommitdiffstats
path: root/bin/sh
diff options
context:
space:
mode:
authorjilles <jilles@FreeBSD.org>2010-12-28 21:27:08 +0000
committerjilles <jilles@FreeBSD.org>2010-12-28 21:27:08 +0000
commit74d9b02bb00830c58e7572986e81ef97d2948ed6 (patch)
tree938df618f5e6962d48e61103d210b5c6d2101fa1 /bin/sh
parentd20b96d0f2ef605d9476d5c27ffe395c0e237253 (diff)
downloadFreeBSD-src-74d9b02bb00830c58e7572986e81ef97d2948ed6.zip
FreeBSD-src-74d9b02bb00830c58e7572986e81ef97d2948ed6.tar.gz
sh: Don't do optimized command substitution if expansions have side effects.
Before considering to execute a command substitution in the same process, check if any of the expansions may have a side effect; if so, execute it in a new process just like happens if it is not a single simple command. Although the check happens at run time, it is a static check that does not depend on current state. It is triggered by: - expanding $! (which may cause the job to be remembered) - ${var=value} default value assignment - assignment operators in arithmetic - parameter substitutions in arithmetic except ${#param}, $$, $# and $? - command substitutions in arithmetic This means that $((v+1)) does not prevent optimized command substitution, whereas $(($v+1)) does, because $v might expand to something containing assignment operators. Scripts should not depend on these exact details for correctness. It is also imaginable to have the shell fork if and when a side effect is encountered or to create a new temporary namespace for variables. Due to the $! change, the construct $(jobs $!) no longer works. The value of $! should be stored in a variable outside command substitution first.
Diffstat (limited to 'bin/sh')
-rw-r--r--bin/sh/eval.c16
-rw-r--r--bin/sh/expand.c72
-rw-r--r--bin/sh/expand.h1
3 files changed, 88 insertions, 1 deletions
diff --git a/bin/sh/eval.c b/bin/sh/eval.c
index 596a86c..903f783 100644
--- a/bin/sh/eval.c
+++ b/bin/sh/eval.c
@@ -94,6 +94,7 @@ static void evalsubshell(union node *, int);
static void evalredir(union node *, int);
static void expredir(union node *);
static void evalpipe(union node *);
+static int is_valid_fast_cmdsubst(union node *n);
static void evalcommand(union node *, int, struct backcmd *);
static void prehash(union node *);
@@ -565,6 +566,19 @@ evalpipe(union node *n)
+static int
+is_valid_fast_cmdsubst(union node *n)
+{
+ union node *argp;
+
+ if (n->type != NCMD)
+ return 0;
+ for (argp = n->ncmd.args ; argp ; argp = argp->narg.next)
+ if (expandhassideeffects(argp->narg.text))
+ return 0;
+ return 1;
+}
+
/*
* Execute a command inside back quotes. If it's a builtin command, we
* want to save its output in a block obtained from malloc. Otherwise
@@ -590,7 +604,7 @@ evalbackcmd(union node *n, struct backcmd *result)
exitstatus = 0;
goto out;
}
- if (n->type == NCMD) {
+ if (is_valid_fast_cmdsubst(n)) {
exitstatus = oexitstatus;
savehandler = handler;
if (setjmp(jmploc.loc)) {
diff --git a/bin/sh/expand.c b/bin/sh/expand.c
index b03cc3b..b2046cc 100644
--- a/bin/sh/expand.c
+++ b/bin/sh/expand.c
@@ -1570,6 +1570,78 @@ cvtnum(int num, char *buf)
}
/*
+ * Check statically if expanding a string may have side effects.
+ */
+int
+expandhassideeffects(const char *p)
+{
+ int c;
+ int arinest;
+
+ arinest = 0;
+ while ((c = *p++) != '\0') {
+ switch (c) {
+ case CTLESC:
+ p++;
+ break;
+ case CTLVAR:
+ c = *p++;
+ /* Expanding $! sets the job to remembered. */
+ if (*p == '!')
+ return 1;
+ if ((c & VSTYPE) == VSASSIGN)
+ return 1;
+ /*
+ * If we are in arithmetic, the parameter may contain
+ * '=' which may cause side effects. Exceptions are
+ * the length of a parameter and $$, $# and $? which
+ * are always numeric.
+ */
+ if ((c & VSTYPE) == VSLENGTH) {
+ while (*p != '=')
+ p++;
+ p++;
+ break;
+ }
+ if ((*p == '$' || *p == '#' || *p == '?') &&
+ p[1] == '=') {
+ p += 2;
+ break;
+ }
+ if (arinest > 0)
+ return 1;
+ break;
+ case CTLBACKQ:
+ case CTLBACKQ | CTLQUOTE:
+ if (arinest > 0)
+ return 1;
+ break;
+ case CTLARI:
+ arinest++;
+ break;
+ case CTLENDARI:
+ arinest--;
+ break;
+ case '=':
+ if (*p == '=') {
+ /* Allow '==' operator. */
+ p++;
+ continue;
+ }
+ if (arinest > 0)
+ return 1;
+ break;
+ case '!': case '<': case '>':
+ /* Allow '!=', '<=', '>=' operators. */
+ if (*p == '=')
+ p++;
+ break;
+ }
+ }
+ return 0;
+}
+
+/*
* Do most of the work for wordexp(3).
*/
diff --git a/bin/sh/expand.h b/bin/sh/expand.h
index a4bb198..be08dec 100644
--- a/bin/sh/expand.h
+++ b/bin/sh/expand.h
@@ -63,4 +63,5 @@ void expari(int);
int patmatch(const char *, const char *, int);
void rmescapes(char *);
int casematch(union node *, const char *);
+int expandhassideeffects(const char *);
int wordexpcmd(int, char **);
OpenPOWER on IntegriCloud