hush: deal with FIXMEs for corner cases in parameter expansion

function                                             old     new   delta
expand_one_var                                      2323    2344     +21

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Denys Vlasenko 2020-12-21 10:09:48 +01:00
parent 901d2c3862
commit 07abc7c6f7

View File

@ -6506,9 +6506,11 @@ static NOINLINE int expand_one_var(o_string *output, int n,
* Word is expanded to produce a glob pattern. * Word is expanded to produce a glob pattern.
* Then var's value is matched to it and matching part removed. * Then var's value is matched to it and matching part removed.
*/ */
//FIXME: ${x#...${...}...} /* bash compat: if x is "" and no shrinking of it is possible,
//should evaluate inner ${...} even if x is "" and no shrinking of it is possible - * inner ${...} is not evaluated. Example:
//inner ${...} may have side effects! * unset b; : ${a%${b=B}}; echo $b
* assignment b=B only happens if $a is not "".
*/
if (val && val[0]) { if (val && val[0]) {
char *t; char *t;
char *exp_exp_word; char *exp_exp_word;
@ -6547,7 +6549,12 @@ static NOINLINE int expand_one_var(o_string *output, int n,
* and if // is used, it is encoded as \: * and if // is used, it is encoded as \:
* var\pattern<SPECIAL_VAR_SYMBOL>repl<SPECIAL_VAR_SYMBOL> * var\pattern<SPECIAL_VAR_SYMBOL>repl<SPECIAL_VAR_SYMBOL>
*/ */
if (val && val[0]) { /* bash compat: if var is "", both pattern and repl
* are still evaluated, if it is unset, then not:
* unset b; a=; : ${a/z/${b=3}}; echo $b # b=3
* unset b; unset a; : ${a/z/${b=3}}; echo $b # b not set
*/
if (val /*&& val[0]*/) {
/* pattern uses non-standard expansion. /* pattern uses non-standard expansion.
* repl should be unbackslashed and globbed * repl should be unbackslashed and globbed
* by the usual expansion rules: * by the usual expansion rules:
@ -6583,8 +6590,9 @@ static NOINLINE int expand_one_var(o_string *output, int n,
free(pattern); free(pattern);
free(repl); free(repl);
} else { } else {
/* Empty variable always gives nothing */ /* Unset variable always gives nothing */
// "v=''; echo ${v/*/w}" prints "", not "w" // a=; echo ${a/*/w} # "w"
// unset a; echo ${a/*/w} # ""
/* Just skip "replace" part */ /* Just skip "replace" part */
*p++ = SPECIAL_VAR_SYMBOL; *p++ = SPECIAL_VAR_SYMBOL;
p = strchr(p, SPECIAL_VAR_SYMBOL); p = strchr(p, SPECIAL_VAR_SYMBOL);
@ -6599,40 +6607,48 @@ static NOINLINE int expand_one_var(o_string *output, int n,
* var:N<SPECIAL_VAR_SYMBOL>M<SPECIAL_VAR_SYMBOL> * var:N<SPECIAL_VAR_SYMBOL>M<SPECIAL_VAR_SYMBOL>
*/ */
arith_t beg, len; arith_t beg, len;
unsigned vallen;
const char *errmsg; const char *errmsg;
beg = expand_and_evaluate_arith(exp_word, &errmsg); beg = expand_and_evaluate_arith(exp_word, &errmsg);
if (errmsg) if (errmsg)
goto arith_err; goto empty_result;
debug_printf_varexp("beg:'%s'=%lld\n", exp_word, (long long)beg); debug_printf_varexp("beg:'%s'=%lld\n", exp_word, (long long)beg);
*p++ = SPECIAL_VAR_SYMBOL; *p++ = SPECIAL_VAR_SYMBOL;
exp_word = p; exp_word = p;
p = strchr(p, SPECIAL_VAR_SYMBOL); p = strchr(p, SPECIAL_VAR_SYMBOL);
*p = '\0'; *p = '\0';
len = expand_and_evaluate_arith(exp_word, &errmsg); vallen = strlen(val);
if (errmsg)
goto arith_err;
debug_printf_varexp("len:'%s'=%lld\n", exp_word, (long long)len);
if (beg < 0) { if (beg < 0) {
/* negative beg counts from the end */ /* negative beg counts from the end */
beg = (arith_t)strlen(val) + beg; beg = (arith_t)vallen + beg;
if (beg < 0) /* ${v: -999999} is "" */
beg = len = 0;
} }
/* If expansion will be empty, do not even evaluate len */
if (!val || beg < 0 || beg > vallen) {
/* Why > vallen, not >=? bash:
* unset b; a=ab; : ${a:2:${b=3}}; echo $b # "", b=3 (!!!)
* unset b; a=a; : ${a:2:${b=3}}; echo $b # "", b not set
*/
goto empty_result;
}
len = expand_and_evaluate_arith(exp_word, &errmsg);
if (errmsg)
goto empty_result;
debug_printf_varexp("len:'%s'=%lld\n", exp_word, (long long)len);
debug_printf_varexp("from val:'%s'\n", val); debug_printf_varexp("from val:'%s'\n", val);
if (len < 0) { if (len < 0) {
/* in bash, len=-n means strlen()-n */ /* in bash, len=-n means strlen()-n */
len = (arith_t)strlen(val) - beg + len; len = (arith_t)vallen - beg + len;
if (len < 0) /* bash compat */ if (len < 0) /* bash compat */
msg_and_die_if_script("%s: substring expression < 0", var); msg_and_die_if_script("%s: substring expression < 0", var);
} }
if (len <= 0 || !val || beg >= strlen(val)) { if (len <= 0 || !val /*|| beg >= vallen*/) {
arith_err: empty_result:
val = NULL; val = NULL;
} else { } else {
/* Paranoia. What if user entered 9999999999999 /* Paranoia. What if user entered 9999999999999
* which fits in arith_t but not int? */ * which fits in arith_t but not int? */
if (len >= INT_MAX) if (len > INT_MAX)
len = INT_MAX; len = INT_MAX;
val = to_be_freed = xstrndup(val + beg, len); val = to_be_freed = xstrndup(val + beg, len);
} }