hush: move variable expansion into a separate function. No logic changes

function                                             old     new   delta
expand_one_var                                         -    1551   +1551
expand_vars_to_list                                 2833    1175   -1658
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 0/1 up/down: 1551/-1658)       Total: -107 bytes

Signed-off-by: Denys Vlasenko <dvlasenk@redhat.com>
This commit is contained in:
Denys Vlasenko 2010-09-05 14:48:11 +02:00
parent 36f774a0cd
commit f2dc20c2d5

View File

@ -2664,147 +2664,14 @@ static char *replace_pattern(char *val, const char *pattern, const char *repl, c
} }
#endif #endif
/* Expand all variable references in given string, adding words to list[] /* Helper:
* at n, n+1,... positions. Return updated n (so that list[n] is next one * Handles <SPECIAL_VAR_SYMBOL>varname...<SPECIAL_VAR_SYMBOL> construct.
* to be filled). This routine is extremely tricky: has to deal with
* variables/parameters with whitespace, $* and $@, and constructs like
* 'echo -$*-'. If you play here, you must run testsuite afterwards! */
static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
{
/* or_mask is either 0 (normal case) or 0x80 -
* expansion of right-hand side of assignment == 1-element expand.
* It will also do no globbing, and thus we must not backslash-quote!
*/ */
char ored_ch; static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, char **pp, char first_ch)
char *p; {
ored_ch = 0;
debug_printf_expand("expand_vars_to_list: arg:'%s' or_mask:%x\n", arg, or_mask);
debug_print_list("expand_vars_to_list", output, n);
n = o_save_ptr(output, n);
debug_print_list("expand_vars_to_list[0]", output, n);
while ((p = strchr(arg, SPECIAL_VAR_SYMBOL)) != NULL) {
char first_ch;
int i;
char *to_be_freed = NULL;
const char *val = NULL; const char *val = NULL;
#if ENABLE_HUSH_TICK char *to_be_freed = NULL;
o_string subst_result = NULL_O_STRING; char *p = *pp;
#endif
#if ENABLE_SH_MATH_SUPPORT
char arith_buf[sizeof(arith_t)*3 + 2];
#endif
o_addblock(output, arg, p - arg);
debug_print_list("expand_vars_to_list[1]", output, n);
arg = ++p;
p = strchr(p, SPECIAL_VAR_SYMBOL);
first_ch = arg[0] | or_mask; /* forced to "quoted" if or_mask = 0x80 */
/* "$@" is special. Even if quoted, it can still
* expand to nothing (not even an empty string) */
if ((first_ch & 0x7f) != '@')
ored_ch |= first_ch;
switch (first_ch & 0x7f) {
/* Highest bit in first_ch indicates that var is double-quoted */
case '*':
case '@':
i = 1;
if (!G.global_argv[i])
break;
ored_ch |= first_ch; /* do it for "$@" _now_, when we know it's not empty */
if (!(first_ch & 0x80)) { /* unquoted $* or $@ */
smallint sv = output->o_escape;
/* unquoted var's contents should be globbed, so don't escape */
output->o_escape = 0;
while (G.global_argv[i]) {
n = expand_on_ifs(output, n, G.global_argv[i]);
debug_printf_expand("expand_vars_to_list: argv %d (last %d)\n", i, G.global_argc - 1);
if (G.global_argv[i++][0] && G.global_argv[i]) {
/* this argv[] is not empty and not last:
* put terminating NUL, start new word */
o_addchr(output, '\0');
debug_print_list("expand_vars_to_list[2]", output, n);
n = o_save_ptr(output, n);
debug_print_list("expand_vars_to_list[3]", output, n);
}
}
output->o_escape = sv;
} else
/* If or_mask is nonzero, we handle assignment 'a=....$@.....'
* and in this case should treat it like '$*' - see 'else...' below */
if (first_ch == ('@'|0x80) && !or_mask) { /* quoted $@ */
while (1) {
o_addQstr(output, G.global_argv[i], strlen(G.global_argv[i]));
if (++i >= G.global_argc)
break;
o_addchr(output, '\0');
debug_print_list("expand_vars_to_list[4]", output, n);
n = o_save_ptr(output, n);
}
} else { /* quoted $*: add as one word */
while (1) {
o_addQstr(output, G.global_argv[i], strlen(G.global_argv[i]));
if (!G.global_argv[++i])
break;
if (G.ifs[0])
o_addchr(output, G.ifs[0]);
}
}
break;
case SPECIAL_VAR_SYMBOL: /* <SPECIAL_VAR_SYMBOL><SPECIAL_VAR_SYMBOL> */
/* "Empty variable", used to make "" etc to not disappear */
arg++;
ored_ch = 0x80;
break;
#if ENABLE_HUSH_TICK
case '`': /* <SPECIAL_VAR_SYMBOL>`cmd<SPECIAL_VAR_SYMBOL> */
*p = '\0';
arg++;
/* Can't just stuff it into output o_string,
* expanded result may need to be globbed
* and $IFS-splitted */
debug_printf_subst("SUBST '%s' first_ch %x\n", arg, first_ch);
G.last_exitcode = process_command_subs(&subst_result, arg);
debug_printf_subst("SUBST RES:%d '%s'\n", G.last_exitcode, subst_result.data);
val = subst_result.data;
goto store_val;
#endif
#if ENABLE_SH_MATH_SUPPORT
case '+': { /* <SPECIAL_VAR_SYMBOL>+cmd<SPECIAL_VAR_SYMBOL> */
arith_t res;
int errcode;
arg++; /* skip '+' */
*p = '\0'; /* replace trailing <SPECIAL_VAR_SYMBOL> */
debug_printf_subst("ARITH '%s' first_ch %x\n", arg, first_ch);
res = expand_and_evaluate_arith(arg, &errcode);
if (errcode < 0) {
const char *msg = "error in arithmetic";
switch (errcode) {
case -3:
msg = "exponent less than 0";
break;
case -2:
msg = "divide by 0";
break;
case -5:
msg = "expression recursion loop detected";
break;
}
die_if_script(msg);
}
debug_printf_subst("ARITH RES '"arith_t_fmt"'\n", res);
sprintf(arith_buf, arith_t_fmt, res);
val = arith_buf;
break;
}
#endif
default: { /* <SPECIAL_VAR_SYMBOL>varname<SPECIAL_VAR_SYMBOL> */
//TODO: move to a subroutine?
char *var; char *var;
char first_char; char first_char;
char exp_op; char exp_op;
@ -2853,9 +2720,9 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
/* lookup the variable in question */ /* lookup the variable in question */
if (isdigit(var[0])) { if (isdigit(var[0])) {
/* parse_dollar() should have vetted var for us */ /* parse_dollar() should have vetted var for us */
i = xatoi_positive(var); int n = xatoi_positive(var);
if (i < G.global_argc) if (n < G.global_argc)
val = G.global_argv[i]; val = G.global_argv[n];
/* else val remains NULL: $N with too big N */ /* else val remains NULL: $N with too big N */
} else { } else {
switch (var[0]) { switch (var[0]) {
@ -2876,7 +2743,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
} }
} }
/* handle any expansions */ /* Handle any expansions */
if (exp_op == 'L') { if (exp_op == 'L') {
debug_printf_expand("expand: length(%s)=", val); debug_printf_expand("expand: length(%s)=", val);
val = utoa(val ? strlen(val) : 0); val = utoa(val ? strlen(val) : 0);
@ -2892,12 +2759,14 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
* 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.
*/ */
if (val) { if (val && val[0]) {
char *exp_exp_word; char *exp_exp_word;
char *loc; char *loc;
unsigned scan_flags = pick_scan(exp_op, *exp_word); unsigned scan_flags = pick_scan(exp_op, *exp_word);
if (exp_op == *exp_word) /* ## or %% */ if (exp_op == *exp_word) /* ## or %% */
exp_word++; exp_word++;
//TODO: avoid xstrdup unless needed
// (see HACK ALERT below)
val = to_be_freed = xstrdup(val); val = to_be_freed = xstrdup(val);
exp_exp_word = expand_pseudo_dquoted(exp_word); exp_exp_word = expand_pseudo_dquoted(exp_word);
if (exp_exp_word) if (exp_exp_word)
@ -3046,12 +2915,157 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
} /* if (exp_op) */ } /* if (exp_op) */
arg[0] = first_ch; arg[0] = first_ch;
*pp = p;
*to_be_freed_pp = to_be_freed;
return val;
}
/* Expand all variable references in given string, adding words to list[]
* at n, n+1,... positions. Return updated n (so that list[n] is next one
* to be filled). This routine is extremely tricky: has to deal with
* variables/parameters with whitespace, $* and $@, and constructs like
* 'echo -$*-'. If you play here, you must run testsuite afterwards! */
static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
{
/* or_mask is either 0 (normal case) or 0x80 -
* expansion of right-hand side of assignment == 1-element expand.
* It will also do no globbing, and thus we must not backslash-quote!
*/
char ored_ch;
char *p;
ored_ch = 0;
debug_printf_expand("expand_vars_to_list: arg:'%s' or_mask:%x\n", arg, or_mask);
debug_print_list("expand_vars_to_list", output, n);
n = o_save_ptr(output, n);
debug_print_list("expand_vars_to_list[0]", output, n);
while ((p = strchr(arg, SPECIAL_VAR_SYMBOL)) != NULL) {
char first_ch;
int i;
char *to_be_freed = NULL;
const char *val = NULL;
#if ENABLE_HUSH_TICK #if ENABLE_HUSH_TICK
store_val: o_string subst_result = NULL_O_STRING;
#endif #endif
#if ENABLE_SH_MATH_SUPPORT
char arith_buf[sizeof(arith_t)*3 + 2];
#endif
o_addblock(output, arg, p - arg);
debug_print_list("expand_vars_to_list[1]", output, n);
arg = ++p;
p = strchr(p, SPECIAL_VAR_SYMBOL);
first_ch = arg[0] | or_mask; /* forced to "quoted" if or_mask = 0x80 */
/* "$@" is special. Even if quoted, it can still
* expand to nothing (not even an empty string) */
if ((first_ch & 0x7f) != '@')
ored_ch |= first_ch;
switch (first_ch & 0x7f) {
/* Highest bit in first_ch indicates that var is double-quoted */
case '*':
case '@':
i = 1;
if (!G.global_argv[i])
break;
ored_ch |= first_ch; /* do it for "$@" _now_, when we know it's not empty */
if (!(first_ch & 0x80)) { /* unquoted $* or $@ */
smallint sv = output->o_escape;
/* unquoted var's contents should be globbed, so don't escape */
output->o_escape = 0;
while (G.global_argv[i]) {
n = expand_on_ifs(output, n, G.global_argv[i]);
debug_printf_expand("expand_vars_to_list: argv %d (last %d)\n", i, G.global_argc - 1);
if (G.global_argv[i++][0] && G.global_argv[i]) {
/* this argv[] is not empty and not last:
* put terminating NUL, start new word */
o_addchr(output, '\0');
debug_print_list("expand_vars_to_list[2]", output, n);
n = o_save_ptr(output, n);
debug_print_list("expand_vars_to_list[3]", output, n);
}
}
output->o_escape = sv;
} else
/* If or_mask is nonzero, we handle assignment 'a=....$@.....'
* and in this case should treat it like '$*' - see 'else...' below */
if (first_ch == ('@'|0x80) && !or_mask) { /* quoted $@ */
while (1) {
o_addQstr(output, G.global_argv[i], strlen(G.global_argv[i]));
if (++i >= G.global_argc)
break;
o_addchr(output, '\0');
debug_print_list("expand_vars_to_list[4]", output, n);
n = o_save_ptr(output, n);
}
} else { /* quoted $*: add as one word */
while (1) {
o_addQstr(output, G.global_argv[i], strlen(G.global_argv[i]));
if (!G.global_argv[++i])
break;
if (G.ifs[0])
o_addchr(output, G.ifs[0]);
}
}
break;
case SPECIAL_VAR_SYMBOL: /* <SPECIAL_VAR_SYMBOL><SPECIAL_VAR_SYMBOL> */
/* "Empty variable", used to make "" etc to not disappear */
arg++;
ored_ch = 0x80;
break;
#if ENABLE_HUSH_TICK
case '`': /* <SPECIAL_VAR_SYMBOL>`cmd<SPECIAL_VAR_SYMBOL> */
*p = '\0';
arg++;
/* Can't just stuff it into output o_string,
* expanded result may need to be globbed
* and $IFS-splitted */
debug_printf_subst("SUBST '%s' first_ch %x\n", arg, first_ch);
G.last_exitcode = process_command_subs(&subst_result, arg);
debug_printf_subst("SUBST RES:%d '%s'\n", G.last_exitcode, subst_result.data);
val = subst_result.data;
goto store_val;
#endif
#if ENABLE_SH_MATH_SUPPORT
case '+': { /* <SPECIAL_VAR_SYMBOL>+cmd<SPECIAL_VAR_SYMBOL> */
arith_t res;
int errcode;
arg++; /* skip '+' */
*p = '\0'; /* replace trailing <SPECIAL_VAR_SYMBOL> */
debug_printf_subst("ARITH '%s' first_ch %x\n", arg, first_ch);
res = expand_and_evaluate_arith(arg, &errcode);
if (errcode < 0) {
const char *msg = "error in arithmetic";
switch (errcode) {
case -3:
msg = "exponent less than 0";
break;
case -2:
msg = "divide by 0";
break;
case -5:
msg = "expression recursion loop detected";
break;
}
die_if_script(msg);
}
debug_printf_subst("ARITH RES '"arith_t_fmt"'\n", res);
sprintf(arith_buf, arith_t_fmt, res);
val = arith_buf;
break;
}
#endif
default:
val = expand_one_var(&to_be_freed, arg, &p, first_ch);
IF_HUSH_TICK(store_val:)
if (!(first_ch & 0x80)) { /* unquoted $VAR */ if (!(first_ch & 0x80)) { /* unquoted $VAR */
debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val, output->o_escape); debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val, output->o_escape);
if (val) { if (val && val[0]) {
/* unquoted var's contents should be globbed, so don't escape */ /* unquoted var's contents should be globbed, so don't escape */
smallint sv = output->o_escape; smallint sv = output->o_escape;
output->o_escape = 0; output->o_escape = 0;
@ -3062,10 +3076,11 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
} else { /* quoted $VAR, val will be appended below */ } else { /* quoted $VAR, val will be appended below */
debug_printf_expand("quoted '%s', output->o_escape:%d\n", val, output->o_escape); debug_printf_expand("quoted '%s', output->o_escape:%d\n", val, output->o_escape);
} }
} /* default: */ break;
} /* switch (char after <SPECIAL_VAR_SYMBOL>) */ } /* switch (char after <SPECIAL_VAR_SYMBOL>) */
if (val) { if (val && val[0]) {
o_addQstr(output, val, strlen(val)); o_addQstr(output, val, strlen(val));
} }
free(to_be_freed); free(to_be_freed);