Skip to content

Commit 44309c7

Browse files
authored
fix(interpreter): contain ${var:?msg} error within subshell boundary (#1031)
## Summary - `${var:?msg}` errors used `ControlFlow::Return` which propagated through the subshell boundary, killing the parent shell - Now both `ControlFlow::Exit` and `ControlFlow::Return` are consumed at the subshell boundary, matching real bash behavior ## Test plan - [x] `parameter_error_in_subshell_contained` — verifies parent shell survives `${var:?msg}` in subshell - [x] Smoke test via CLI confirms end-to-end behavior - [x] All existing tests pass (199 tests, full suite) Closes #961
1 parent 84f0f4a commit 44309c7

File tree

2 files changed

+19
-7
lines changed

2 files changed

+19
-7
lines changed

crates/bashkit/src/interpreter/mod.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1438,13 +1438,17 @@ impl Interpreter {
14381438
self.coproc_buffers = saved_coproc;
14391439
self.memory_budget = saved_memory_budget;
14401440

1441-
// Consume Exit control flow at subshell boundary — exit only
1442-
// terminates the subshell, not the parent shell.
1443-
if let Ok(ref mut res) = result
1444-
&& let ControlFlow::Exit(code) = res.control_flow
1445-
{
1446-
res.exit_code = code;
1447-
res.control_flow = ControlFlow::None;
1441+
// Consume Exit and Return control flow at subshell boundary —
1442+
// they only terminate the subshell, not the parent shell.
1443+
// Return is used by ${var:?msg} error handling and nounset errors.
1444+
if let Ok(ref mut res) = result {
1445+
match res.control_flow {
1446+
ControlFlow::Exit(code) | ControlFlow::Return(code) => {
1447+
res.exit_code = code;
1448+
res.control_flow = ControlFlow::None;
1449+
}
1450+
_ => {}
1451+
}
14481452
}
14491453

14501454
result

crates/bashkit/tests/spec_cases/bash/subshell.test.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,11 @@ echo "$@"
129129
x y
130130
a b c
131131
### end
132+
133+
### parameter_error_in_subshell_contained
134+
# ${var:?msg} error in subshell should not kill parent
135+
(unset NOSUCHVAR; echo "${NOSUCHVAR:?gone}" 2>/dev/null)
136+
echo "survived: $?"
137+
### expect
138+
survived: 1
139+
### end

0 commit comments

Comments
 (0)