Skip to content

Commit a570705

Browse files
authored
fix(interpreter): isolate command substitution subshell state (#917)
Closes #910
1 parent 865ee37 commit a570705

File tree

2 files changed

+45
-3
lines changed

2 files changed

+45
-3
lines changed

crates/bashkit/src/interpreter/mod.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5943,9 +5943,15 @@ impl Interpreter {
59435943
"maximum nesting depth exceeded in command substitution".to_string(),
59445944
));
59455945
}
5946-
// Command substitution runs in a subshell: snapshot trap state
5947-
// so EXIT traps set inside $() fire and don't leak to parent.
5946+
// Command substitution runs in a subshell: snapshot all
5947+
// mutable state so mutations don't leak to the parent.
59485948
let saved_traps = self.traps.clone();
5949+
let saved_functions = self.functions.clone();
5950+
let saved_vars = self.variables.clone();
5951+
let saved_arrays = self.arrays.clone();
5952+
let saved_assoc = self.assoc_arrays.clone();
5953+
let saved_aliases = self.aliases.clone();
5954+
let saved_cwd = self.cwd.clone();
59495955
let mut stdout = String::new();
59505956
for cmd in commands {
59515957
let cmd_result = self.execute_command(cmd).await?;
@@ -5969,8 +5975,14 @@ impl Interpreter {
59695975
{
59705976
stdout.push_str(&trap_result.stdout);
59715977
}
5972-
// Restore parent trap state
5978+
// Restore parent state
59735979
self.traps = saved_traps;
5980+
self.functions = saved_functions;
5981+
self.variables = saved_vars;
5982+
self.arrays = saved_arrays;
5983+
self.assoc_arrays = saved_assoc;
5984+
self.aliases = saved_aliases;
5985+
self.cwd = saved_cwd;
59745986
self.counters.pop_function();
59755987
self.subst_generation += 1;
59765988
let trimmed = stdout.trim_end_matches('\n');

crates/bashkit/tests/spec_cases/bash/command-subst.test.sh

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,3 +223,33 @@ trap - EXIT
223223
result=[inner
224224
CHILD]
225225
### end
226+
227+
### subst_function_isolation
228+
# Functions defined in $() should not leak to parent shell
229+
x=$(function foo() { echo "sub"; }; echo "ok")
230+
echo "$x"
231+
foo 2>/dev/null
232+
echo "exit=$?"
233+
### expect
234+
ok
235+
exit=127
236+
### end
237+
238+
### subst_variable_isolation
239+
# Variables set in $() should not leak to parent shell
240+
myvar="before"
241+
x=$(myvar="inside"; echo "ok")
242+
echo "$x"
243+
echo "$myvar"
244+
### expect
245+
ok
246+
before
247+
### end
248+
249+
### subst_alias_isolation
250+
# Aliases set in $() should not leak to parent shell
251+
x=$(alias myalias='echo aliased' 2>/dev/null; echo "ok")
252+
echo "$x"
253+
### expect
254+
ok
255+
### end

0 commit comments

Comments
 (0)