diff --git a/crates/bashkit/src/interpreter/mod.rs b/crates/bashkit/src/interpreter/mod.rs index b81e408b..79da16a3 100644 --- a/crates/bashkit/src/interpreter/mod.rs +++ b/crates/bashkit/src/interpreter/mod.rs @@ -8371,7 +8371,8 @@ impl Interpreter { cmd.push(c); } } - // Execute the command and substitute + // Execute the command and substitute in a subshell context: + // save/restore mutable state so mutations don't leak. let parser = Parser::with_limits( &cmd, self.limits.max_ast_depth, @@ -8382,8 +8383,24 @@ impl Interpreter { if self.counters.push_function(&self.limits).is_err() { result.push('0'); } else { + let saved_vars = self.variables.clone(); + let saved_arrays = self.arrays.clone(); + let saved_assoc = self.assoc_arrays.clone(); + let saved_functions = self.functions.clone(); + let saved_traps = self.traps.clone(); + let saved_aliases = self.aliases.clone(); + let saved_cwd = self.cwd.clone(); + let saved_memory_budget = self.memory_budget.clone(); let cmd_result = self.execute_command_sequence(&script.commands).await?; + self.variables = saved_vars; + self.arrays = saved_arrays; + self.assoc_arrays = saved_assoc; + self.functions = saved_functions; + self.traps = saved_traps; + self.aliases = saved_aliases; + self.cwd = saved_cwd; + self.memory_budget = saved_memory_budget; self.counters.pop_function(); let trimmed = cmd_result.stdout.trim_end_matches('\n'); if trimmed.is_empty() { diff --git a/crates/bashkit/tests/spec_cases/bash/recursive-cmdsub.test.sh b/crates/bashkit/tests/spec_cases/bash/recursive-cmdsub.test.sh new file mode 100644 index 00000000..8fc35eeb --- /dev/null +++ b/crates/bashkit/tests/spec_cases/bash/recursive-cmdsub.test.sh @@ -0,0 +1,36 @@ +### recursive_function_command_subst +# Recursive function calls inside $() should work +factorial() { + if (( $1 <= 1 )); then echo 1 + else echo $(( $1 * $(factorial $(($1 - 1))) )) + fi +} +factorial 5 +### expect +120 +### end + +### recursive_depth_3 +# Recursive function with depth 3 +f() { + if (( $1 <= 0 )); then echo "base" + else echo "depth=$1 $(f $(($1 - 1)))" + fi +} +f 3 +### expect +depth=3 depth=2 depth=1 base +### end + +### recursive_cmdsub_var_isolation +# Variable mutations inside $() in arithmetic should not leak to parent +x=100 +inner() { + x=999 + echo 42 +} +result=$(( $(inner) + x )) +echo "$result $x" +### expect +142 100 +### end