hush: make var nesting code independent of "local" support
Also, add code to abort at ~65000 function recursion depth. SEGVing is not as nice as exiting with a message (and restoring termios!): $ f() { echo -n .; f; }; f ....<many dots later>....hush: fatal recursion (depth 65281) function old new delta run_pipe 1826 1890 +64 pseudo_exec_argv 544 554 +10 parse_and_run_file 71 80 +9 i_getch 104 107 +3 done_command 99 102 +3 set_local_var 508 510 +2 helper_export_local 214 215 +1 builtin_local 49 46 -3 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 7/1 up/down: 92/-3) Total: 89 bytes Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
parent
61407807ab
commit
332e4115c9
103
shell/hush.c
103
shell/hush.c
@ -730,10 +730,8 @@ struct parse_context {
|
||||
struct variable {
|
||||
struct variable *next;
|
||||
char *varstr; /* points to "name=" portion */
|
||||
#if ENABLE_HUSH_LOCAL
|
||||
unsigned func_nest_level;
|
||||
#endif
|
||||
int max_len; /* if > 0, name is part of initial env; else name is malloced */
|
||||
uint16_t var_nest_level;
|
||||
smallint flg_export; /* putenv should be done on this var */
|
||||
smallint flg_read_only;
|
||||
};
|
||||
@ -922,12 +920,13 @@ struct globals {
|
||||
const char *cwd;
|
||||
struct variable *top_var;
|
||||
char **expanded_assignments;
|
||||
#if ENABLE_HUSH_FUNCTIONS
|
||||
struct function *top_func;
|
||||
# if ENABLE_HUSH_LOCAL
|
||||
struct variable **shadowed_vars_pp;
|
||||
unsigned func_nest_level;
|
||||
unsigned var_nest_level;
|
||||
#if ENABLE_HUSH_FUNCTIONS
|
||||
# if ENABLE_HUSH_LOCAL
|
||||
unsigned func_nest_level; /* solely to prevent "local v" in non-functions */
|
||||
# endif
|
||||
struct function *top_func;
|
||||
#endif
|
||||
/* Signal and trap handling */
|
||||
#if ENABLE_HUSH_FAST
|
||||
@ -2131,7 +2130,7 @@ static const char* FAST_FUNC get_local_var_value(const char *name)
|
||||
#define SETFLAG_EXPORT (1 << 0)
|
||||
#define SETFLAG_UNEXPORT (1 << 1)
|
||||
#define SETFLAG_MAKE_RO (1 << 2)
|
||||
#define SETFLAG_LOCAL_SHIFT 3
|
||||
#define SETFLAG_VARLVL_SHIFT 3
|
||||
static int set_local_var(char *str, unsigned flags)
|
||||
{
|
||||
struct variable **cur_pp;
|
||||
@ -2139,7 +2138,7 @@ static int set_local_var(char *str, unsigned flags)
|
||||
char *free_me = NULL;
|
||||
char *eq_sign;
|
||||
int name_len;
|
||||
IF_HUSH_LOCAL(unsigned local_lvl = (flags >> SETFLAG_LOCAL_SHIFT);)
|
||||
unsigned local_lvl = (flags >> SETFLAG_VARLVL_SHIFT);
|
||||
|
||||
eq_sign = strchr(str, '=');
|
||||
if (!eq_sign) { /* not expected to ever happen? */
|
||||
@ -2176,8 +2175,7 @@ static int set_local_var(char *str, unsigned flags)
|
||||
unsetenv(str);
|
||||
*eq_sign = '=';
|
||||
}
|
||||
#if ENABLE_HUSH_LOCAL
|
||||
if (cur->func_nest_level < local_lvl) {
|
||||
if (cur->var_nest_level < local_lvl) {
|
||||
/* New variable is declared as local,
|
||||
* and existing one is global, or local
|
||||
* from enclosing function.
|
||||
@ -2197,7 +2195,7 @@ static int set_local_var(char *str, unsigned flags)
|
||||
flags |= SETFLAG_EXPORT;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) {
|
||||
free_and_exp:
|
||||
free(str);
|
||||
@ -2225,7 +2223,7 @@ static int set_local_var(char *str, unsigned flags)
|
||||
|
||||
/* Not found - create new variable struct */
|
||||
cur = xzalloc(sizeof(*cur));
|
||||
IF_HUSH_LOCAL(cur->func_nest_level = local_lvl;)
|
||||
cur->var_nest_level = local_lvl;
|
||||
cur->next = *cur_pp;
|
||||
*cur_pp = cur;
|
||||
|
||||
@ -5509,7 +5507,7 @@ static struct pipe *parse_stream(char **pstring,
|
||||
goto parse_error2;
|
||||
default:
|
||||
if (HUSH_DEBUG)
|
||||
bb_error_msg_and_die("BUG: unexpected %c\n", ch);
|
||||
bb_error_msg_and_die("BUG: unexpected %c", ch);
|
||||
}
|
||||
} /* while (1) */
|
||||
|
||||
@ -7362,8 +7360,9 @@ static void exec_function(char ***to_free,
|
||||
// for "more correctness" we might want to close those extra fds here:
|
||||
//? close_saved_fds_and_FILE_fds();
|
||||
|
||||
/* "we are in function, ok to use return" */
|
||||
/* "we are in a function, ok to use return" */
|
||||
G_flag_return_in_progress = -1;
|
||||
G.var_nest_level++;
|
||||
IF_HUSH_LOCAL(G.func_nest_level++;)
|
||||
|
||||
/* On MMU, funcp->body is always non-NULL */
|
||||
@ -7383,6 +7382,47 @@ static void exec_function(char ***to_free,
|
||||
# endif
|
||||
}
|
||||
|
||||
static void enter_var_nest_level(void)
|
||||
{
|
||||
G.var_nest_level++;
|
||||
|
||||
/* Try: f() { echo -n .; f; }; f
|
||||
* struct variable::var_nest_level is uint16_t,
|
||||
* thus limiting recursion to < 2^16.
|
||||
* In any case, with 8 Mbyte stack SEGV happens
|
||||
* not too long after 2^16 recursions anyway.
|
||||
*/
|
||||
if (G.var_nest_level > 0xff00)
|
||||
bb_error_msg_and_die("fatal recursion (depth %u)", G.var_nest_level);
|
||||
}
|
||||
|
||||
static void leave_var_nest_level(void)
|
||||
{
|
||||
struct variable *cur;
|
||||
struct variable **cur_pp;
|
||||
|
||||
cur_pp = &G.top_var;
|
||||
while ((cur = *cur_pp) != NULL) {
|
||||
if (cur->var_nest_level < G.var_nest_level) {
|
||||
cur_pp = &cur->next;
|
||||
continue;
|
||||
}
|
||||
/* Unexport */
|
||||
if (cur->flg_export)
|
||||
bb_unsetenv(cur->varstr);
|
||||
/* Remove from global list */
|
||||
*cur_pp = cur->next;
|
||||
/* Free */
|
||||
if (!cur->max_len)
|
||||
free(cur->varstr);
|
||||
free(cur);
|
||||
}
|
||||
|
||||
G.var_nest_level--;
|
||||
if (HUSH_DEBUG && (int)G.var_nest_level < 0)
|
||||
bb_error_msg_and_die("BUG: nesting underflow");
|
||||
}
|
||||
|
||||
static int run_function(const struct function *funcp, char **argv)
|
||||
{
|
||||
int rc;
|
||||
@ -7394,6 +7434,8 @@ static int run_function(const struct function *funcp, char **argv)
|
||||
/* "we are in function, ok to use return" */
|
||||
sv_flg = G_flag_return_in_progress;
|
||||
G_flag_return_in_progress = -1;
|
||||
|
||||
enter_var_nest_level();
|
||||
IF_HUSH_LOCAL(G.func_nest_level++;)
|
||||
|
||||
/* On MMU, funcp->body is always non-NULL */
|
||||
@ -7408,30 +7450,9 @@ static int run_function(const struct function *funcp, char **argv)
|
||||
rc = run_list(funcp->body);
|
||||
}
|
||||
|
||||
# if ENABLE_HUSH_LOCAL
|
||||
{
|
||||
struct variable *var;
|
||||
struct variable **var_pp;
|
||||
IF_HUSH_LOCAL(G.func_nest_level--;)
|
||||
leave_var_nest_level();
|
||||
|
||||
var_pp = &G.top_var;
|
||||
while ((var = *var_pp) != NULL) {
|
||||
if (var->func_nest_level < G.func_nest_level) {
|
||||
var_pp = &var->next;
|
||||
continue;
|
||||
}
|
||||
/* Unexport */
|
||||
if (var->flg_export)
|
||||
bb_unsetenv(var->varstr);
|
||||
/* Remove from global list */
|
||||
*var_pp = var->next;
|
||||
/* Free */
|
||||
if (!var->max_len)
|
||||
free(var->varstr);
|
||||
free(var);
|
||||
}
|
||||
G.func_nest_level--;
|
||||
}
|
||||
# endif
|
||||
G_flag_return_in_progress = sv_flg;
|
||||
|
||||
restore_G_args(&sv, argv);
|
||||
@ -9959,8 +9980,8 @@ static int helper_export_local(char **argv, unsigned flags)
|
||||
# if ENABLE_HUSH_LOCAL
|
||||
/* Is this "local" bltin? */
|
||||
if (!(flags & (SETFLAG_EXPORT|SETFLAG_UNEXPORT|SETFLAG_MAKE_RO))) {
|
||||
unsigned lvl = flags >> SETFLAG_LOCAL_SHIFT;
|
||||
if (var && var->func_nest_level == lvl) {
|
||||
unsigned lvl = flags >> SETFLAG_VARLVL_SHIFT;
|
||||
if (var && var->var_nest_level == lvl) {
|
||||
/* "local x=abc; ...; local x" - ignore second local decl */
|
||||
continue;
|
||||
}
|
||||
@ -10045,7 +10066,7 @@ static int FAST_FUNC builtin_local(char **argv)
|
||||
return EXIT_FAILURE; /* bash compat */
|
||||
}
|
||||
argv++;
|
||||
return helper_export_local(argv, G.func_nest_level << SETFLAG_LOCAL_SHIFT);
|
||||
return helper_export_local(argv, G.var_nest_level << SETFLAG_VARLVL_SHIFT);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user