Skip to content

refactor(dirstack): move directory stack to typed interpreter state#2103

Merged
chaliy merged 2 commits into
mainfrom
claude/migrate-dirstack-state
Jun 19, 2026
Merged

refactor(dirstack): move directory stack to typed interpreter state#2103
chaliy merged 2 commits into
mainfrom
claude/migrate-dirstack-state

Conversation

@chaliy

@chaliy chaliy commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Why

Concludes the #1 series (interpreter-internal state out of the user variable namespace). This is the clearest "leaked abstraction" of the bunch: pushd/popd/dirs stored the entire directory stack as _DIRSTACK_SIZE / _DIRSTACK_N shell variables, guarded by is_internal_variable() — a data structure living in (and forgeable from) the user namespace.

What

  • Add a typed dir_stack: Arc<Vec<String>> to ScopedState (so it's subshell-isolated via the scoped snapshot, exactly like aliases/traps), and expose it to the pushd/popd/dirs builtins through ShellRef::dir_stack (the established pattern for internal-builtin state).
  • Rewrite dirstack.rs to operate on ctx.shell.dir_stack instead of _DIRSTACK_* variables.
  • Add dir_stack to ShellState (serde default) + populate + restore so it roundtrips through shell_state() / restore_shell_state() session snapshots.
  • Drop _DIRSTACK_ from the is_internal_variable() blocklist and its test list. The DoS size cap (DIRSTACK_MAX_SIZE) now applies to pushd growth instead of clamping a user-forgeable _DIRSTACK_SIZE.

Tests

  • Replaced the magic-variable-inspecting unit tests with interpreter-level spec coverage in dirstack.test.sh (the existing suite already covered pushd/popd/dirs/swap/clear/per-line behavior through the real interpreter). Added dirs -v and dirstack_internal_names_are_ordinary_vars (setting _DIRSTACK_SIZE/_DIRSTACK_0 is now inert).
  • New test_shell_state_roundtrips_dir_stack unit test (dir stack survives shell_state() + restore_shell_state()).
  • bash_spec_tests, 2477 lib unit tests, 44 snapshot tests green. fmt + clippy clean.

pushd/popd/dirs stored the whole directory stack in the user variable
namespace as _DIRSTACK_SIZE / _DIRSTACK_N, guarded by is_internal_variable()
— a data structure leaked into shell variables that scripts could forge.

Move it to a typed dir_stack: Arc<Vec<String>> on ScopedState (so it is
subshell-isolated via the scoped snapshot like aliases/traps) and expose it
to the pushd/popd/dirs builtins via ShellRef::dir_stack. Add it to ShellState
(serde default) + restore so it roundtrips through session snapshots, and
drop _DIRSTACK_ from the is_internal_variable() blocklist and its test list.
The DoS size cap now applies to pushd growth instead of clamping a
user-forgeable size variable.

Replace the magic-variable-inspecting unit tests with interpreter-level
spec coverage (dirstack.test.sh), adding dirs -v and a regression that the
former _DIRSTACK_* names are now inert ordinary variables. Adds a
ShellState dir-stack roundtrip test.

Concludes the #1 migration of magic-variable channels to typed state.
Copilot AI review requested due to automatic review settings June 19, 2026 20:28
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 19, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
bashkit ab80a7d Commit Preview URL

Branch Preview URL
Jun 19 2026, 08:34 PM

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Moves the directory stack backing pushd/popd/dirs out of user-forgeable shell variables (_DIRSTACK_*) and into typed, subshell-scoped interpreter state, and ensures it roundtrips through shell_state() / restore_shell_state().

Changes:

  • Add dir_stack to ScopedState (Arc-wrapped, subshell-isolated) and plumb it through ShellRef.
  • Refactor crates/bashkit/src/builtins/dirstack.rs to use ctx.shell.dir_stack and enforce a DoS cap on push growth.
  • Update tests: add spec coverage for dirs -v and _DIRSTACK_* now being ordinary inert vars; add a unit test for shell-state roundtrip.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
crates/bashkit/tests/spec_cases/bash/dirstack.test.sh Adds spec coverage for dirs -v output and verifies _DIRSTACK_* vars no longer affect the directory stack.
crates/bashkit/src/interpreter/mod.rs Introduces typed dir_stack in scoped interpreter state and includes it in shell-state snapshot/restore + unit test.
crates/bashkit/src/builtins/dirstack.rs Rewrites pushd/popd/dirs to use typed dir_stack instead of _DIRSTACK_* variables.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/bashkit/src/builtins/dirstack.rs Outdated
Comment thread crates/bashkit/src/builtins/dirstack.rs Outdated
Review nits:
- pushd used exists() + stat() with unwrap_or(false), misreporting IO/TOCTOU
  errors as 'Not a directory'. Use one stat() and branch on
  Ok(dir)/Ok(non-dir)/Err(not found).
- dirs cloned the whole stack into a Vec even on the default path that
  doesn't use it. Return early for the default output and iterate the
  borrowed dir_stack (reversed) for -p/-v without allocating.
@chaliy chaliy merged commit 76535f9 into main Jun 19, 2026
36 checks passed
@chaliy chaliy deleted the claude/migrate-dirstack-state branch June 19, 2026 20:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants