Don't overwrite unrelated variables with for-loop-variables

for-loops that were not inside a function could overwrite global
and universal variables with the loop variable.  Avoid this by making
for-loop-variables local variables in their enclosing scope.

This means that if someone does:

    set a global
    for a in local; end
    echo $a

The local $a will shadow the global one (but not be visible in child
scopes). Which is surprising, but less dangerous than the previous
behavior.

The detection whether the loop is running inside a function was failing
inside command substitutions. Remove this special handling of functions
alltogether, it's not needed anymore.

Fixes #6480
This commit is contained in:
Johannes Altmanninger 2020-01-08 09:03:34 +01:00
parent e0cdea9bb6
commit 992c864f26
3 changed files with 46 additions and 20 deletions

View File

@ -376,15 +376,6 @@ eval_result_t parse_execution_context_t::run_block_statement(tnode_t<g::block_st
return ret;
}
/// Return true if the current execution context is within a function block, else false.
bool parse_execution_context_t::is_function_context() const {
const block_t *current = parser->block_at_index(0);
const block_t *parent = parser->block_at_index(1);
bool is_within_function_call =
(current && parent && current->type() == block_type_t::top && parent->is_function_call());
return is_within_function_call;
}
eval_result_t parse_execution_context_t::run_for_statement(
tnode_t<grammar::for_header> header, tnode_t<grammar::job_list> block_contents) {
// Get the variable name: `for var_name in ...`. We expand the variable name. It better result
@ -404,17 +395,19 @@ eval_result_t parse_execution_context_t::run_for_statement(
return ret;
}
auto &vars = parser->vars();
auto var = vars.get(for_var_name, ENV_LOCAL);
if (!var && !is_function_context()) var = vars.get(for_var_name, ENV_DEFAULT);
if (!var || var->read_only()) {
int retval = parser->vars().set_empty(for_var_name, ENV_LOCAL | ENV_USER);
if (retval != ENV_OK) {
report_error(var_name_node, L"You cannot use read-only variable '%ls' in a for loop",
for_var_name.c_str());
return eval_result_t::error;
}
auto var = parser->vars().get(for_var_name, ENV_DEFAULT);
if (var && var->read_only()) {
report_error(var_name_node, L"You cannot use read-only variable '%ls' in a for loop",
for_var_name.c_str());
return eval_result_t::error;
}
int retval;
if (var) {
retval = parser->vars().set(for_var_name, ENV_LOCAL | ENV_USER, var->as_list());
} else {
retval = parser->vars().set_empty(for_var_name, ENV_LOCAL | ENV_USER);
}
assert(retval == ENV_OK);
if (!valid_var_name(for_var_name)) {
report_error(var_name_node, BUILTIN_ERR_VARNAME, L"for", for_var_name.c_str());

View File

@ -66,7 +66,6 @@ class parse_execution_context_t {
wcstring get_source(const parse_node_t &node) const;
tnode_t<grammar::plain_statement> infinite_recursive_statement_in_job_list(
tnode_t<grammar::job_list> job_list, wcstring *out_func_name) const;
bool is_function_context() const;
// Expand a command which may contain variables, producing an expand command and possibly
// arguments. Prints an error message on error.

34
tests/checks/for.fish Normal file
View File

@ -0,0 +1,34 @@
# RUN: %fish %s
# A for-loop-variable is a local variable in the enclosing scope.
set -g i global
# implicit set -l i $i
for i in local
end
set -ql i && echo $i
# CHECK: local
# The loop variable is initialized with any previous value.
set -g j global
for j in
end
set -ql j && echo $j
# CHECK: global
# Loop variables exist only locally in the enclosing local scope.
# They do not modify other local/global/universal variables.
set -g k global
begin
for k in local1
echo $k
# CHECK: local1
for k in local2
end
echo $k
# CHECK: local2
end
echo $k
# CHECK: local1
end
echo $k
# CHECK: global