diff options
author | jilles <jilles@FreeBSD.org> | 2010-12-28 21:27:08 +0000 |
---|---|---|
committer | jilles <jilles@FreeBSD.org> | 2010-12-28 21:27:08 +0000 |
commit | 74d9b02bb00830c58e7572986e81ef97d2948ed6 (patch) | |
tree | 938df618f5e6962d48e61103d210b5c6d2101fa1 /bin/sh/expand.c | |
parent | d20b96d0f2ef605d9476d5c27ffe395c0e237253 (diff) | |
download | FreeBSD-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/expand.c')
-rw-r--r-- | bin/sh/expand.c | 72 |
1 files changed, 72 insertions, 0 deletions
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). */ |