Skip to content

Commit 059eb85

Browse files
authored
chore: pre-release maintenance (test counts, fuzz fix, code cleanup) (#885)
## Summary Pre-release maintenance pass per `specs/012-maintenance.md`. - **fix**: Prevent panic on malformed `${#[}` in arithmetic expansion — found by `arithmetic_fuzz` CI (intermittent failures Mar 22/24/26). Adds regression test under TM-DOS-029. - **docs**: Update spec test counts (2382→2278) and security doc threat model test count (50+→185) to match reality. Added 7 new test files to per-file breakdown table. - **chore**: Clean up stale `#[allow(dead_code)]` annotations and redundant TODO in AWK builtin. - **chore(specs)**: Add "Deferred Items" section to `012-maintenance.md` to track large-scope items found during maintenance passes. ## Deferred to separate issues - #880 — Migrate 27 builtins from manual arg parsing to ArgParser - #881 — Extract errexit suppression propagation helper - #882 — Fuzz crash tracking issue (fixed in this PR) ## Maintenance checklist results | Section | Status | |---------|--------| | Dependencies | All current, `cargo deny` clean, 1 allowed advisory | | Security | 185 TM tests, 14 failpoint, 53 network, 39 error, 26 logging | | Tests | 3478 Rust tests pass, 2278 spec cases (2252 pass, 26 skip) | | Documentation | Test counts updated to match reality | | Examples | All compile and run | | Specs | All 19 specs accurate, no orphaned TODOs | | Code quality | fmt clean, clippy clean | | Simplification | Dead code annotations cleaned; large refactors deferred to #880/#881 | | Agent config | AGENTS.md accurate, all paths verified | | Nightly CI | 7/7 green; fuzz 4/7 (arithmetic_fuzz — fixed in this PR) | ## Test plan - [x] `cargo test --all-features` — all pass - [x] `cargo clippy --all-targets --all-features -- -D warnings` — clean - [x] `cargo fmt --check` — clean - [x] New regression test `arithmetic_malformed_brace_length_no_panic` passes
1 parent d4839ac commit 059eb85

File tree

6 files changed

+77
-35
lines changed

6 files changed

+77
-35
lines changed

crates/bashkit/src/builtins/awk.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ enum AwkPattern {
5555
}
5656

5757
#[derive(Debug, Clone)]
58-
#[allow(dead_code)] // Regex and Match used for pattern matching expansion
5958
enum AwkExpr {
6059
Number(f64),
6160
String(String),
@@ -69,7 +68,8 @@ enum AwkExpr {
6968
Concat(Vec<AwkExpr>),
7069
FuncCall(String, Vec<AwkExpr>),
7170
Regex(String),
72-
Match(Box<AwkExpr>, String), // expr ~ /pattern/
71+
#[allow(dead_code)] // matched in eval but construction deferred to pattern expansion
72+
Match(Box<AwkExpr>, String), // expr ~ /pattern/
7373
PostIncrement(String), // var++
7474
PostDecrement(String), // var--
7575
PreIncrement(String), // ++var
@@ -114,7 +114,6 @@ enum AwkAction {
114114
var: Option<String>,
115115
file: AwkExpr,
116116
},
117-
#[allow(dead_code)] // Exit code support for future
118117
Exit(Option<AwkExpr>),
119118
Return(Option<AwkExpr>),
120119
Expression(AwkExpr),
@@ -948,7 +947,7 @@ impl<'a> AwkParser<'a> {
948947
Ok(Some(AwkOutputTarget::Truncate(target)))
949948
}
950949
} else if c == '|' {
951-
// TODO: pipe output (e.g., `print ... | "cmd"`) not yet supported
950+
// Pipe output (e.g., `print ... | "cmd"`) not supported in virtual mode
952951
Err(Error::Execution(
953952
"awk: pipe output redirection (|) is not supported".to_string(),
954953
))

crates/bashkit/src/interpreter/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7277,8 +7277,14 @@ impl Interpreter {
72777277
// ${#arr[@]} or ${#arr[*]} — array length
72787278
if let Some(rest) = inner.strip_prefix('#') {
72797279
if let Some(bracket) = rest.find('[') {
7280+
// Guard against malformed input like ${#[} where bracket+1 > len-1
7281+
let end = rest.len().saturating_sub(1);
7282+
if bracket + 1 > end {
7283+
// Malformed — treat as string length of empty var
7284+
return "0".to_string();
7285+
}
72807286
let arr_name = &rest[..bracket];
7281-
let idx = &rest[bracket + 1..rest.len().saturating_sub(1)];
7287+
let idx = &rest[bracket + 1..end];
72827288
if idx == "@" || idx == "*" {
72837289
if let Some(arr) = self.arrays.get(arr_name) {
72847290
return arr.len().to_string();

crates/bashkit/tests/threat_model_tests.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3827,4 +3827,16 @@ mod trace_events {
38273827
"should record CommandExit with code 127 for not-found"
38283828
);
38293829
}
3830+
3831+
// TM-DOS-029: Malformed ${#[} must not panic (fuzz crash from CI)
3832+
#[tokio::test]
3833+
async fn arithmetic_malformed_brace_length_no_panic() {
3834+
let mut bash = Bash::new();
3835+
// Input discovered by arithmetic_fuzz: [${#[
3836+
// Triggered panic "byte range starts at 1 but ends at 0" in
3837+
// expand_brace_expr_in_arithmetic when rest="[" and bracket=0.
3838+
let r = bash.exec("echo $((0 + ${#[}))").await.unwrap();
3839+
// Should not panic — just return 0 for malformed expression
3840+
assert_eq!(r.exit_code, 0);
3841+
}
38303842
}

docs/security.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ These decisions are documented in [`specs/008-posix-compliance.md`](../specs/008
5656

5757
Bashkit uses multiple layers of security testing:
5858

59-
**Threat model tests**Over 50 tests in `threat_model_tests.rs` that directly
59+
**Threat model tests**185 tests in `threat_model_tests.rs` that directly
6060
validate mitigations against documented threat IDs. Each test maps to a specific
6161
`TM-*` threat.
6262

specs/009-implementation-status.md

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -103,28 +103,28 @@ Bashkit implements IEEE 1003.1-2024 Shell Command Language. See
103103

104104
## Spec Test Coverage
105105

106-
**Total spec test cases:** 2382 (2354 pass, 28 skip)
106+
**Total spec test cases:** 2278 (2252 pass, 26 skip)
107107

108108
| Category | Cases | In CI | Pass | Skip | Notes |
109109
|----------|-------|-------|------|------|-------|
110-
| Bash (core) | 1934 | Yes | 1909 | 25 | `bash_spec_tests` in CI |
111-
| AWK | 104 | Yes | 104 | 0 | loops, arrays, -v, ternary, field assign, getline, %.6g |
112-
| Grep | 91 | Yes | 91 | 0 | -z, -r, -a, -b, -H, -h, -f, -P, --include, --exclude, binary detect |
110+
| Bash (core) | 1809 | Yes | 1786 | 23 | `bash_spec_tests` in CI |
111+
| AWK | 126 | Yes | 126 | 0 | loops, arrays, -v, ternary, field assign, getline, %.6g, delete, dev-stderr |
112+
| Grep | 95 | Yes | 95 | 0 | -z, -r, -a, -b, -H, -h, -f, -P, --include, --exclude, binary detect, rg |
113113
| Sed | 75 | Yes | 75 | 0 | hold space, change, regex ranges, -E |
114-
| JQ | 120 | Yes | 119 | 1 | reduce, walk, regex funcs, --arg/--argjson, combined flags, input/inputs, env |
115-
| Python | 58 | Yes | 56 | 2 | embedded Python (Monty) |
116-
| **Total** | **2382** | **Yes** | **2354** | **28** | |
114+
| JQ | 116 | Yes | 115 | 1 | reduce, walk, regex funcs, --arg/--argjson, combined flags, input/inputs, env |
115+
| Python | 57 | Yes | 55 | 2 | embedded Python (Monty) |
116+
| **Total** | **2278** | **Yes** | **2252** | **26** | |
117117

118118
### Bash Spec Tests Breakdown
119119

120120
| File | Cases | Notes |
121121
|------|-------|-------|
122-
| alias.test.sh | 15 | alias expansion (15 skipped) |
123-
| arith-dynamic.test.sh | 14 | dynamic arithmetic contexts (5 skipped) |
122+
| alias.test.sh | 15 | alias expansion (1 skipped) |
123+
| arith-dynamic.test.sh | 14 | dynamic arithmetic contexts |
124124
| arithmetic.test.sh | 68 | includes logical, bitwise, compound assign, increment/decrement, `let` builtin, `declare -i` arithmetic |
125125
| array-slicing.test.sh | 8 | array slice operations |
126126
| arrays.test.sh | 27 | indices, `${arr[@]}` / `${arr[*]}`, negative indexing `${arr[-1]}` |
127-
| assoc-arrays.test.sh | 15 | associative arrays `declare -A` |
127+
| assoc-arrays.test.sh | 19 | associative arrays `declare -A` |
128128
| background.test.sh | 2 | background job handling |
129129
| bash-command.test.sh | 25 | bash/sh re-invocation |
130130
| bash-flags.test.sh | 13 | bash `-e`, `-x`, `-u`, `-f`, `-o option` flags |
@@ -136,8 +136,9 @@ Bashkit implements IEEE 1003.1-2024 Shell Command Language. See
136136
| command.test.sh | 9 | `command -v`, `-V`, function bypass |
137137
| command-not-found.test.sh | 9 | unknown command handling |
138138
| cmd-suggestions.test.sh | 4 | command suggestions on typos |
139-
| command-subst.test.sh | 25 | includes backtick substitution, nested quotes in `$()` |
140-
| conditional.test.sh | 24 | `[[ ]]` conditionals, `=~` regex, BASH_REMATCH, glob `==`/`!=` |
139+
| command-subst.test.sh | 29 | includes backtick substitution, nested quotes in `$()` |
140+
| compgen-path.test.sh | 2 | compgen PATH completion |
141+
| conditional.test.sh | 29 | `[[ ]]` conditionals, `=~` regex, BASH_REMATCH, glob `==`/`!=` |
141142
| control-flow.test.sh | 58 | if/elif/else, for, while, case `;;`/`;&`/`;;&`, select, trap ERR, `[[ =~ ]]` BASH_REMATCH, compound input redirects |
142143
| comm.test.sh | 6 | comm column comparison |
143144
| cuttr.test.sh | 39 | cut and tr commands, `-z` zero-terminated |
@@ -148,70 +149,76 @@ Bashkit implements IEEE 1003.1-2024 Shell Command Language. See
148149
| du.test.sh | 4 | disk usage reporting |
149150
| dirstack.test.sh | 12 | `pushd`, `popd`, `dirs` directory stack operations |
150151
| echo.test.sh | 24 | escape sequences |
151-
| empty-bodies.test.sh | 8 | empty loop/function bodies (5 skipped) |
152+
| empty-bodies.test.sh | 8 | empty loop/function bodies |
152153
| env.test.sh | 3 | environment variable operations |
153-
| errexit.test.sh | 8 | set -e tests |
154+
| errexit.test.sh | 11 | set -e tests |
154155
| eval-bugs.test.sh | 4 | regression tests for eval/script bugs |
156+
| exec-command.test.sh | 5 | exec builtin |
155157
| exit-status.test.sh | 18 | exit code propagation |
156158
| expr.test.sh | 13 | `expr` arithmetic, string ops, pattern matching, exit codes |
157159
| extglob.test.sh | 15 | `@()`, `?()`, `*()`, `+()`, `!()` extended globs |
158160
| file.test.sh | 8 | file type detection |
159161
| fileops.test.sh | 28 | `mktemp`, `-d`, `-p`, template |
160-
| find.test.sh | 10 | file search |
162+
| find.test.sh | 19 | file search |
161163
| functions.test.sh | 26 | local dynamic scoping, nested writes, FUNCNAME call stack, `caller` builtin |
162164
| getopts.test.sh | 9 | POSIX option parsing, combined flags, silent mode |
163165
| glob-options.test.sh | 13 | dotglob, nocaseglob, failglob, nullglob, noglob, globstar |
164166
| globs.test.sh | 9 | for-loop glob expansion, recursive `**` |
165167
| gzip.test.sh | 2 | gzip/gunzip compression |
166168
| headtail.test.sh | 14 | |
167169
| heredoc.test.sh | 13 | heredoc variable expansion, quoted delimiters, file redirects, `<<-` tab strip |
168-
| heredoc-edge.test.sh | 15 | heredoc edge cases (6 skipped) |
170+
| heredoc-edge.test.sh | 15 | heredoc edge cases |
169171
| herestring.test.sh | 8 | here-string `<<<` |
170172
| hextools.test.sh | 4 | od/xxd/hexdump (3 skipped) |
171173
| history.test.sh | 2 | history builtin |
172174
| less.test.sh | 3 | less pager |
173175
| ln.test.sh | 5 | `ln -s`, `-f`, symlink creation |
174-
| nameref.test.sh | 14 | nameref variables (14 skipped) |
176+
| ls.test.sh | 4 | ls directory listing |
177+
| nameref-assoc.test.sh | 7 | nameref with associative arrays |
178+
| nameref.test.sh | 23 | nameref variables (1 skipped) |
175179
| negative-tests.test.sh | 13 | error conditions |
176180
| nl.test.sh | 14 | line numbering |
177181
| nounset.test.sh | 7 | `set -u` unbound variable checks, `${var:-default}` nounset-aware |
178-
| parse-errors.test.sh | 18 | syntax error detection (13 skipped) |
182+
| parse-errors.test.sh | 18 | syntax error detection (4 skipped) |
179183
| paste.test.sh | 4 | line merging with `-s` serial and `-d` delimiter |
180184
| path.test.sh | 18 | basename, dirname, `realpath` canonical path resolution |
181185
| pipes-redirects.test.sh | 26 | includes stderr redirects |
182186
| printenv.test.sh | 2 | printenv builtin |
183187
| printf.test.sh | 32 | format specifiers, array expansion, `-v` variable assignment, `%q` shell quoting |
184188
| procsub.test.sh | 11 | process substitution |
185-
| quote.test.sh | 35 | quoting edge cases (2 skipped) |
186-
| read-builtin.test.sh | 10 | `read` builtin, IFS splitting, `-r`, `-a` (array), `-n` (nchars), here-string |
187-
| script-exec.test.sh | 10 | script execution by path, $PATH search, exit codes |
189+
| quote.test.sh | 42 | quoting edge cases |
190+
| read-builtin.test.sh | 12 | `read` builtin, IFS splitting, `-r`, `-a` (array), `-n` (nchars), here-string |
191+
| script-exec.test.sh | 14 | script execution by path, $PATH search, exit codes |
188192
| seq.test.sh | 12 | `seq` numeric sequences, `-w`, `-s`, decrement, negative |
193+
| set-allexport.test.sh | 5 | set -a / allexport |
189194
| shell-grammar.test.sh | 23 | shell grammar edge cases |
190195
| sleep.test.sh | 9 | sleep timing |
191-
| sortuniq.test.sh | 32 | sort `-f`/`-n`/`-r`/`-u`/`-V`/`-t`/`-k`/`-s`/`-c`/`-h`/`-M`/`-m`/`-z`/`-o`, uniq `-c`/`-d`/`-u`/`-i`/`-f` |
196+
| sortuniq.test.sh | 39 | sort `-f`/`-n`/`-r`/`-u`/`-V`/`-t`/`-k`/`-s`/`-c`/`-h`/`-M`/`-m`/`-z`/`-o`, uniq `-c`/`-d`/`-u`/`-i`/`-f` |
192197
| source.test.sh | 19 | source/., function loading, PATH search, positional params |
193198
| stat.test.sh | 7 | stat file information |
194199
| string-ops.test.sh | 14 | string replacement (prefix/suffix anchored), `${var:?}`, case conversion |
195200
| strings.test.sh | 6 | strings extraction |
196-
| subshell.test.sh | 13 | subshell execution (4 skipped) |
201+
| subprocess-isolation.test.sh | 8 | subprocess variable isolation |
202+
| subshell.test.sh | 13 | subshell execution |
197203
| tar.test.sh | 8 | tar archive operations |
198204
| tee.test.sh | 6 | tee output splitting |
199205
| temp-binding.test.sh | 10 | temporary variable bindings `VAR=val cmd` |
200-
| test-operators.test.sh | 27 | file/string tests, `-nt`/`-ot`/`-ef` file comparisons |
206+
| test-operators.test.sh | 29 | file/string tests, `-nt`/`-ot`/`-ef` file comparisons |
207+
| test-tty.test.sh | 5 | tty detection tests |
201208
| textrev.test.sh | 14 | `tac` reverse line order, `rev` reverse characters, `yes` repeated output |
202209
| time.test.sh | 11 | Wall-clock only (user/sys always 0) |
203210
| timeout.test.sh | 16 | |
204211
| type.test.sh | 15 | `type`, `which`, `hash` builtins |
205212
| unicode.test.sh | 17 | unicode handling (3 skipped) |
206-
| var-op-test.test.sh | 21 | variable operations (16 skipped) |
213+
| var-op-test.test.sh | 26 | variable operations (1 skipped) |
207214
| variables.test.sh | 97 | includes special vars, prefix env, PIPESTATUS, trap EXIT, `${var@Q}`, `\<newline>` line continuation, PWD/HOME/USER/HOSTNAME/BASH_VERSION/SECONDS, `set -x` xtrace, `shopt` builtin, nullglob, `set -o`/`set +o` display, `trap -p` |
208215
| wait.test.sh | 2 | wait builtin |
209216
| watch.test.sh | 2 | watch command |
210217
| wc.test.sh | 20 | word count |
211-
| word-split.test.sh | 39 | IFS word splitting (36 skipped) |
212-
| xargs.test.sh | 7 | xargs command |
213-
| blackbox-edge-cases.test.sh | 92 | edge cases for quoting, expansion, redirection, error handling |
214-
| blackbox-exploration.test.sh | 206 | broad coverage exploration: builtins, pipelines, subshells, traps |
218+
| word-split.test.sh | 39 | IFS word splitting (10 skipped) |
219+
| xargs.test.sh | 7 | xargs command (1 skipped) |
220+
| blackbox-edge-cases.test.sh | 89 | edge cases for quoting, expansion, redirection, error handling |
221+
| blackbox-exploration.test.sh | 199 | broad coverage exploration: builtins, pipelines, subshells, traps |
215222

216223
## Shell Features
217224

specs/012-maintenance.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,24 @@ Failures persisting **>2 consecutive days** are blocking:
119119
3. Assign to most recent contributor in failing area
120120
4. If upstream dep change: pin to known-good rev, open follow-up issue
121121

122+
## Deferred Items
123+
124+
When a maintenance pass identifies issues too large to fix inline (e.g.
125+
multi-file refactors, cross-cutting changes), the pass must:
126+
127+
1. Create a GitHub issue for each deferred item with clear scope and reproduction steps
128+
2. Record the issue numbers in the summary below so they are tracked
129+
130+
Deferred items are **not** failures — they are expected for large-scope
131+
improvements. The requirement is that they are **tracked**, not silently skipped.
132+
133+
### Deferred from 2026-03-27 run
134+
135+
| Issue | Section | Description |
136+
|-------|---------|-------------|
137+
| #880 | Simplification | Migrate 27 builtins from manual arg parsing to ArgParser |
138+
| #881 | Simplification | Extract errexit suppression propagation helper |
139+
122140
## Automation
123141

124142
Sections dependencies, tests, examples, code quality, and nightly CI are fully

0 commit comments

Comments
 (0)