Repository: helix-editor/helix (language: rust)

Issue:
Panic in expand_impl due to invalid slice indices in helix-stdx/src/env.rs
### Summary

The function `expand_impl` in `helix-stdx/src/env.rs` triggers a panic when changing a file path that contains bash parameter expansion:
```
thread 'main' panicked at /usr/src/debug/helix/helix-25.01/helix-stdx/src/env.rs:130:37:
slice index starts at 33 but ends at 19
```

Encountered on Helix 25.1, reproduced on Helix built from master:
```
$ ~/.cargo/bin/hx --version
helix 25.01 (3318953b)
```

Thank you for looking into this. 

### Reproduction Steps

Open a file with the following contents:
```
X="${HOME:-$HOME}/test"
```
change contents in the quotes, e.g. 
`f / w c` 
or 
`gl c`

### Helix log

<details><summary>backtrace</summary>

```
thread 'main' panicked at /home/lj/helix-bug/helix/helix-stdx/src/env.rs:130:37:
slice index starts at 33 but ends at 19
stack backtrace:
   0: rust_begin_unwind
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:645:5
   1: core::panicking::panic_fmt
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/panicking.rs:72:14
   2: core::slice::index::slice_index_order_fail_rt
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/slice/index.rs:98:5
   3: core::slice::index::slice_index_order_fail
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/slice/index.rs:91:14
   4: helix_stdx::env::expand_impl
   5: helix_term::handlers::completion::path::path_completion
   6: helix_term::handlers::completion::request_completion
   7: core::ops::function::FnOnce::call_once{{vtable.shim}}
   8: helix_term::job::Jobs::handle_callback
   9: hx::main_impl::{{closure}}
  10: tokio::runtime::park::CachedParkThread::block_on
  11: tokio::runtime::runtime::Runtime::block_on
  12: hx::main
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
```

</details>

<details><summary>backtrace full</summary>

```
thread 'main' panicked at /home/lj/helix-bug/helix/helix-stdx/src/env.rs:130:37:
slice index starts at 33 but ends at 19
stack backtrace:
   0:     0x6223a4d2891b - std::backtrace_rs::backtrace::libunwind::trace::hbee8a7973eeb6c93
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/../../backtrace/src/backtrace/libunwind.rs:104:5
   1:     0x6223a4d2891b - std::backtrace_rs::backtrace::trace_unsynchronized::hc8ac75eea3aa6899
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/../../backtrace/src/backtrace/mod.rs:66:5
   2:     0x6223a4d2891b - std::sys_common::backtrace::_print_fmt::hc7f3e3b5298b1083
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/sys_common/backtrace.rs:68:5
   3:     0x6223a4d2891b - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt::hbb235daedd7c6190
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/sys_common/backtrace.rs:44:22
   4:     0x6223a3eb2330 - core::fmt::rt::Argument::fmt::h76c38a80d925a410
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/fmt/rt.rs:142:9
   5:     0x6223a3eb2330 - core::fmt::write::h3ed6aeaa977c8e45
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/fmt/mod.rs:1120:17
   6:     0x6223a4d246a3 - std::io::Write::write_fmt::h78b18af5775fedb5
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/io/mod.rs:1810:15
   7:     0x6223a4d286b4 - std::sys_common::backtrace::_print::h5d645a07e0fcfdbb
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/sys_common/backtrace.rs:47:5
   8:     0x6223a4d286b4 - std::sys_common::backtrace::print::h85035a511aafe7a8
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/sys_common/backtrace.rs:34:9
   9:     0x6223a4d2ac30 - std::panicking::default_hook::{{closure}}::hcce8cea212785a25
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:272:22
  10:     0x6223a4d2a94f - std::panicking::default_hook::hf5fcb0f213fe709a
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:292:9
  11:     0x6223a4d2b2ff - <alloc::boxed::Box<F,A> as core::ops::function::Fn<Args>>::call::hbc5ccf4eb663e1e5
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/alloc/src/boxed.rs:2029:9
  12:     0x6223a4d2b2ff - std::panicking::rust_panic_with_hook::h095fccf1dc9379ee
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:783:13
  13:     0x6223a4d2b052 - std::panicking::begin_panic_handler::{{closure}}::h032ba12139b353db
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:657:13
  14:     0x6223a4d28e16 - std::sys_common::backtrace::__rust_end_short_backtrace::h9259bc2ff8fd0f76
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/sys_common/backtrace.rs:171:18
  15:     0x6223a4d2adc0 - rust_begin_unwind
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:645:5
  16:     0x6223a3da63b5 - core::panicking::panic_fmt::h784f20a50eaab275
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/panicking.rs:72:14
  17:     0x6223a3da6a82 - core::slice::index::slice_index_order_fail_rt::h989db0619cdb04b1
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/slice/index.rs:98:5
  18:     0x6223a3da6a82 - core::slice::index::slice_index_order_fail::hcfcb08cd5efc8d4c
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/slice/index.rs:91:14
  19:     0x6223a460c6ef - helix_stdx::env::expand_impl::h53c843f49d2f2210
  20:     0x6223a4902db6 - helix_term::handlers::completion::path::path_completion::h70aa78fc369f9c9f
  21:     0x6223a47ae5c9 - helix_term::handlers::completion::request_completion::hfbd3d8f51d9e40ef
  22:     0x6223a47f6699 - core::ops::function::FnOnce::call_once{{vtable.shim}}::h099e0475843d84b9
  23:     0x6223a4829d84 - helix_term::job::Jobs::handle_callback::h9cd6db19b1a8cdc0
  24:     0x6223a4aa57ee - hx::main_impl::{{closure}}::h66ea079cc130f777
  25:     0x6223a4aa3158 - tokio::runtime::park::CachedParkThread::block_on::h15e62e3275166942
  26:     0x6223a4afa648 - tokio::runtime::runtime::Runtime::block_on::h728ec91321745dec
  27:     0x6223a4adb273 - hx::main::h870ed68d75be97e6
  28:     0x6223a4b40513 - std::sys_common::backtrace::__rust_begin_short_backtrace::h8fa950b23349c1a3
  29:     0x6223a4b4052d - std::rt::lang_start::{{closure}}::hade2538e3b58a866
  30:     0x6223a4d1b025 - core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once::h37600b1e5eea4ecd
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/ops/function.rs:284:13
  31:     0x6223a4d1b025 - std::panicking::try::do_call::hb4bda49fa13a0c2b
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:552:40
  32:     0x6223a4d1b025 - std::panicking::try::h8bbf75149211aaaa
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:516:19
  33:     0x6223a4d1b025 - std::panic::catch_unwind::h8c78ec68ebea34cb
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panic.rs:142:14
  34:     0x6223a4d1b025 - std::rt::lang_start_internal::{{closure}}::hffdf44a19fd9e220
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/rt.rs:148:48
  35:     0x6223a4d1b025 - std::panicking::try::do_call::hcb3194972c74716d
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:552:40
  36:     0x6223a4d1b025 - std::panicking::try::hcdc6892c5f0dba4c
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:516:19
  37:     0x6223a4d1b025 - std::panic::catch_unwind::h4910beb4573f4776
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panic.rs:142:14
  38:     0x6223a4d1b025 - std::rt::lang_start_internal::h6939038e2873596b
                               at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/rt.rs:148:20
  39:     0x6223a4adb365 - main
  40:     0x725c28f98e08 - <unknown>
  41:     0x725c28f98ecc - __libc_start_main
  42:     0x6223a3e2aac5 - _start
  43:                0x0 - <unknown>
```

</details>

### Platform

Linux

### Terminal Emulator

kitty 0.38.1

### Installation Method

pacman / source

### Helix Version

helix 25.01 (3318953b)


Discussion / hints:

Fix patch:
diff --git a/helix-stdx/src/env.rs b/helix-stdx/src/env.rs
index 6e14c7a875ae..b3f46c25fcc9 100644
--- a/helix-stdx/src/env.rs
+++ b/helix-stdx/src/env.rs
@@ -103,6 +103,12 @@ fn expand_impl(src: &OsStr, mut resolve: impl FnMut(&OsStr) -> Option<OsString>)
         let mat = captures.get_match().unwrap();
         let pattern_id = mat.pattern().as_usize();
         let mut range = mat.range();
+        // A pattern may match multiple times on a single variable, for example `${HOME:-$HOME}`:
+        // `${HOME:-` matches and also the default value (`$HOME`). Skip past any variables which
+        // have already been expanded.
+        if range.start < pos {
+            continue;
+        }
         let var = &bytes[captures.get_group(1).unwrap().range()];
         let default = if pattern_id != 5 {
             let Some(bracket_pos) = find_brace_end(&bytes[range.end..]) else {
@@ -203,6 +209,7 @@ mod tests {
         assert_env_expand!(env, "bar/$FOO/baz", "bar/foo/baz");
         assert_env_expand!(env, "bar/${FOO}/baz", "bar/foo/baz");
         assert_env_expand!(env, "baz/${BAR:-bar}/foo", "baz/bar/foo");
+        assert_env_expand!(env, "baz/${FOO:-$FOO}/foo", "baz/foo/foo");
         assert_env_expand!(env, "baz/${BAR:=bar}/foo", "baz/bar/foo");
         assert_env_expand!(env, "baz/${BAR-bar}/foo", "baz/bar/foo");
         assert_env_expand!(env, "baz/${BAR=bar}/foo", "baz/bar/foo");


Test patch: