Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion crates/bashkit/src/parser/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -682,11 +682,34 @@ impl<'a> Lexer<'a> {
}
}
} else {
// Command substitution $(...) - track nested parens
// THREAT[TM-DOS]: Track substitution nesting depth to
// enforce max_subst_depth consistently in both quoted
// and unquoted contexts (issue #996).
let mut depth = 1;
let mut subst_depth = 1usize;
while let Some(c) = self.peek_char() {
word.push(c);
self.advance();
if c == '$' && self.peek_char() == Some('(') {
subst_depth += 1;
if subst_depth > self.max_subst_depth {
// Depth limit exceeded — consume remaining
// parens and stop nesting deeper.
while let Some(ic) = self.peek_char() {
word.push(ic);
self.advance();
if ic == '(' {
depth += 1;
} else if ic == ')' {
depth -= 1;
if depth == 0 {
break;
}
}
}
break;
}
}
if c == '(' {
depth += 1;
} else if c == ')' {
Expand Down
26 changes: 26 additions & 0 deletions crates/bashkit/tests/spec_cases/bash/cmdsub_depth_unquoted.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Command substitution depth limit in unquoted context
# Regression tests for issue #996

### shallow_cmdsub_unquoted
# Shallow command substitution in unquoted context works
x=$(echo hello)
echo $x
### expect
hello
### end

### nested_cmdsub_unquoted
# Nested command substitution in unquoted context works
x=$(echo $(echo nested))
echo $x
### expect
nested
### end

### cmdsub_depth_3_unquoted
# Three levels of nesting works
x=$(echo $(echo $(echo deep)))
echo $x
### expect
deep
### end
Loading