Skip to content

Commit c853e18

Browse files
committed
Implement analyzer-owned Bash loop state
1 parent fc2ded0 commit c853e18

27 files changed

Lines changed: 1345 additions & 193 deletions

IMPLEMENTATION_PLAN.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -291,11 +291,15 @@ priorities.
291291
flow partitions, failure-aware `cd`, conservative `lastpipe` / `pipefail`,
292292
ordered duplicate-preserving loop plans, inherited but isolated
293293
decoded-wrapper state, and dynamic fail-closed compatibility attribution
294-
whenever cwd joins to Unknown. Keep OpenSpec task 6.5 open, then add the
295-
remaining loop cases and Netclaw approval matrix after implementation.
296-
The non-loop state engine is now implemented for lists, pipelines,
297-
substitutions, subshells, and decoded wrappers; loop iteration state and
298-
removal of the temporary mutation rejection remain next. An adversarial
294+
whenever cwd joins to Unknown. The analyzer now owns persistent loop
295+
bindings, ordered and empty iteration, occurrence-fact joins, unreachable
296+
flow partitions, substitution inheritance, and explicit decoded-wrapper
297+
remapping of loop plans and argument provenance. Unknown-cardinality
298+
loops use bounded fixed-point widening, and a 4096-transition global
299+
budget fails nested cross-products atomically. Static bodies retain an
300+
exact incoming cwd when no transfer can change it. Keep OpenSpec task 6.5
301+
open for full effective-argv transfer and removal of the temporary loop
302+
mutation rejection, then add the Netclaw approval matrix. An adversarial
299303
pre-implementation review halted the first loop-state draft: parser-time
300304
binding frames could not model zero-iteration persistence, correlated
301305
nested iterables, special Bash variables, or candidate-derived `cd`
@@ -307,7 +311,7 @@ priorities.
307311
cwd-changing loop body. Corpus-pin `HOME`, `RANDOM`, `LINENO`, `PATH`,
308312
`CDPATH`, `IFS`, 32/33 ordered visits, zero iterations, nested
309313
correlation, wrapped transfers, wrapper mapping, substitutions, and
310-
pipelines in the same vertical slice.
314+
pipelines across these two analyzer slices.
311315
- [ ] Complete PowerShell `$()` discovery in `foreach` expressions and add the
312316
Netclaw approval-matrix cases. The simple-command slice is delivered for
313317
ordinary, adjacent, quoted, here-string, redirect, standalone,

SPEC.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,9 @@ iteration plan: `a b a` performs three state transitions and leaves an exact
599599
final binding of `a`; 33 authored values use widening even when every value is
600600
the same. An iterable that depends on an outer binding is evaluated separately
601601
for each concrete outer visit so correlated nested state is not flattened into
602-
an artificial cross-product.
602+
an artificial cross-product. The analyzer permits at most 4096 total loop-body
603+
transitions per parse; exceeding that resource budget makes the complete result
604+
unparseable rather than returning a partial cross-product.
603605

604606
Each concrete iteration assigns its candidate into the analyzer variable map,
605607
re-evaluates the complete effective argument vector for every body occurrence,

openspec/changes/v0-3-structured-shell-analysis/design.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -453,10 +453,11 @@ proved syntax tree, not by exposing the compatibility parser's mutable
453453
parse-order cwd attribution. Parse order is not execution-state order for
454454
pipelines or conditional lists, and one symbolic loop-body parse cannot prove
455455
the cwd of later iterations. The compatibility attribution path remains a
456-
v0.2 leaf-construction detail. Until the abstract pass lands, loop cwd mutation
457-
fails closed, recognized shell-state mutation before or inside a loop fails
458-
closed, nested reuse of an active Bash binding name fails closed, and
459-
occurrence `WorkingDirectory` stays `Unknown`.
456+
v0.2 leaf-construction detail. The ordered-binding pass may retain exact cwd
457+
for reached nonmutating loop occurrences. Loop cwd mutation, recognized shell-
458+
state mutation before or inside a loop, and nested active-name reuse continue
459+
to fail closed until complete effective-argv transfers and repeated mutable
460+
state are modeled.
460461

461462
Bash command substitution executes in an isolated subshell state. State changes
462463
affect later commands inside that substitution but never the containing command
@@ -492,6 +493,11 @@ wrapped builtin forms such as `builtin break` and `command exit`. `eval`,
492493
`source` / `.`, and execution-bearing `trap` also fail the whole region closed
493494
unless every executable region and state transfer is discovered.
494495

496+
The analyzer also has a parse-wide budget of 4096 loop-body transitions.
497+
Concrete nested products and fixed-point visits consume the same budget; an
498+
overflow fails the complete parse atomically so an adversarial input cannot
499+
force an unbounded cross-product or receive a partial security projection.
500+
495501
The internal loop plan is distinct from the public value-domain summary. It
496502
retains an executable word plan parameterized by the incoming analyzer binding
497503
map, plus a cardinality of `Never`, `OneOrMore`, or `ZeroOrMore`. Evaluating the

openspec/changes/v0-3-structured-shell-analysis/specs/bounded-shell-analysis/spec.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,11 @@ partition merely to publish exact continuation facts.
362362
- **THEN** the internal iteration cardinality is `Never`
363363
- **THEN** the following `pwd` retains the exact incoming cwd
364364

365+
#### Scenario: Reached nonmutating loop body retains incoming cwd
366+
- **WHEN** isolated-mode Bash parses a supported loop whose body cannot change cwd
367+
- **THEN** each reached body occurrence retains the exact incoming cwd
368+
- **THEN** a structurally present but unreachable body or continuation still receives conservative cwd facts
369+
365370
#### Scenario: Duplicate iteration values retain order
366371
- **WHEN** isolated-mode Bash parses `for f in a b a; do :; done; printf '%s' "$f"`
367372
- **THEN** the internal iteration plan retains `a`, `b`, `a` in that order
@@ -372,6 +377,11 @@ partition merely to publish exact continuation facts.
372377
- **THEN** the internal plan exceeds the concrete-iteration cap
373378
- **THEN** it uses bounded fixed-point analysis instead of treating one distinct public value as one visit
374379

380+
#### Scenario: Nested loop analysis stays resource bounded
381+
- **WHEN** nested concrete loops require more than 4096 total body transitions
382+
- **THEN** the complete parse is unparseable
383+
- **THEN** no partial occurrence or compatibility projection is published
384+
375385
#### Scenario: Loop-derived cd option is rebound from effective argv
376386
- **WHEN** isolated-mode Bash analyzes `for f in -P /tmp; do cd "$f"; done`
377387
- **THEN** the first visit treats `-P` as a `cd` option rather than a path operand

openspec/changes/v0-3-structured-shell-analysis/tasks.md

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,23 @@
8787
make the analyzer own persistent bindings, parameterized ordered plans,
8888
full argument provenance/effective-argv transfer, occurrence-fact joins,
8989
and unreachable exit partitions.
90-
- [ ] 6.5c.2 Implement that corrected contract and corpus-pin `HOME`,
91-
`RANDOM`, `LINENO`, `PATH`, `CDPATH`, `IFS`, 32/33 ordered visits,
92-
zero-iteration state, loop-derived `cd` options, nested correlation,
93-
wrapped transfers, wrapper mapping, substitutions, and pipelines.
94-
- The first static-value slice deliberately leaves occurrence cwd Unknown
95-
and rejects loop shell-state mutation, nested active-binding reuse, or
96-
loops reached after recognized prior shell-state mutation. A separate
97-
structure-aware abstract-state pass is required
98-
before enabling cwd-changing bodies;
99-
mutable parse-order attribution is unsound across pipelines, `&&` / `||`,
100-
substitutions, and repeated iterations.
90+
- [x] 6.5c.2 Make the analyzer own persistent bindings, ordered and empty
91+
iteration, visit-joined effective arguments, unreachable exit partitions,
92+
substitution inheritance, and explicit decoded-wrapper remapping. Pin
93+
special-name rejection, 32/33 visit boundaries, duplicate order,
94+
zero-iteration state, nested correlation, substitutions, pipelines, and
95+
wrapper provenance in unit tests and the Bash corpus. Use bounded
96+
fixed-point widening for unknown cardinality and fail atomically after
97+
4096 total loop-body transitions.
98+
- [ ] 6.5c.3 Re-parse each visit's complete effective argv for state
99+
transfers, including loop-derived `cd` options and wrapped dispatch;
100+
carry those transfers through the bounded fixed point, then remove only
101+
the temporary mutation rejections whose transfers are fully modeled.
102+
- The analyzer now publishes exact incoming cwd for reached loop occurrences
103+
when no modeled transfer can disagree. It still rejects loop shell-state
104+
mutation, nested active-binding reuse, and loops reached after recognized
105+
prior shell-state mutation until 6.5c.3 reclassifies complete effective argv
106+
and models the corresponding repeated transfers.
101107
- [ ] 6.6 Cover empty iterables, separators, multiline bodies, redirects, pipelines, nested loops, and wrapper boundaries.
102108
- [ ] 6.7 Add adversarial cases for option injection, mutation, unquoted expansion, indirect expansion, substitutions, and cap overflow.
103109
- [ ] 6.8 Add sanitized Bash corpus entries and Netclaw allow/prompt/deny integration cases.

0 commit comments

Comments
 (0)