From 30bc394d1882d955217c104233a9b726371536ac Mon Sep 17 00:00:00 2001 From: pagedmov Date: Fri, 27 Feb 2026 02:14:40 -0500 Subject: [PATCH] Fixed possible refcell borrow panic related to expanding variables in an array index --- src/parse/execute.rs | 78 ++++++++++----------- src/state.rs | 163 +++++++++++++++++++------------------------ 2 files changed, 111 insertions(+), 130 deletions(-) diff --git a/src/parse/execute.rs b/src/parse/execute.rs index ad59d79..8e50b0a 100644 --- a/src/parse/execute.rs +++ b/src/parse/execute.rs @@ -898,51 +898,49 @@ impl Dispatcher { } fn set_assignments(&self, assigns: Vec, behavior: AssignBehavior) -> ShResult> { let mut new_env_vars = vec![]; - match behavior { - AssignBehavior::Export => { - for assign in assigns { - let is_arr = assign.flags.contains(NdFlags::ARR_ASSIGN); - let NdRule::Assignment { kind, var, val } = assign.class else { - unreachable!() - }; - let var = var.span.as_str(); - let val = if is_arr { - VarKind::arr_from_tk(val)? - } else { - VarKind::Str(val.expand()?.get_words().join(" ")) - }; - match kind { - AssignKind::Eq => write_vars(|v| v.set_var(var, val, VarFlags::EXPORT))?, - AssignKind::PlusEq => todo!(), - AssignKind::MinusEq => todo!(), - AssignKind::MultEq => todo!(), - AssignKind::DivEq => todo!(), + let flags = match behavior { + AssignBehavior::Export => VarFlags::EXPORT, + AssignBehavior::Set => VarFlags::NONE, + }; + + for assign in assigns { + let is_arr = assign.flags.contains(NdFlags::ARR_ASSIGN); + let NdRule::Assignment { kind, var, val } = assign.class else { + unreachable!() + }; + let var = var.span.as_str(); + let val = if is_arr { + VarKind::arr_from_tk(val)? + } else { + VarKind::Str(val.expand()?.get_words().join(" ")) + }; + + // Parse and expand array index BEFORE entering write_vars borrow + let indexed = state::parse_arr_bracket(var) + .map(|(name, idx_raw)| { + state::expand_arr_index(&idx_raw).map(|idx| (name, idx)) + }) + .transpose()?; + + match kind { + AssignKind::Eq => { + if let Some((name, idx)) = indexed { + write_vars(|v| v.set_var_indexed(&name, idx, val.to_string(), flags))?; + } else { + write_vars(|v| v.set_var(var, val, flags))?; } - new_env_vars.push(var.to_string()); } + AssignKind::PlusEq => todo!(), + AssignKind::MinusEq => todo!(), + AssignKind::MultEq => todo!(), + AssignKind::DivEq => todo!(), } - AssignBehavior::Set => { - for assign in assigns { - let is_arr = assign.flags.contains(NdFlags::ARR_ASSIGN); - let NdRule::Assignment { kind, var, val } = assign.class else { - unreachable!() - }; - let var = var.span.as_str(); - let val = if is_arr { - VarKind::arr_from_tk(val)? - } else { - VarKind::Str(val.expand()?.get_words().join(" ")) - }; - match kind { - AssignKind::Eq => write_vars(|v| v.set_var(var, val, VarFlags::NONE))?, - AssignKind::PlusEq => todo!(), - AssignKind::MinusEq => todo!(), - AssignKind::MultEq => todo!(), - AssignKind::DivEq => todo!(), - } - } + + if matches!(behavior, AssignBehavior::Export) { + new_env_vars.push(var.to_string()); } } + Ok(new_env_vars) } } diff --git a/src/state.rs b/src/state.rs index 5036adc..5f1636a 100644 --- a/src/state.rs +++ b/src/state.rs @@ -191,82 +191,6 @@ impl ScopeStack { flat_vars } - fn parse_arr_index(&self, var_name: &str) -> ShResult> { - let mut chars = var_name.chars(); - let mut var_name = String::new(); - let mut idx_raw = String::new(); - let mut bracket_depth = 0; - - while let Some(ch) = chars.next() { - match ch { - '\\' => { - // Skip the next character, as it's escaped - chars.next(); - } - '[' => { - bracket_depth += 1; - if bracket_depth > 1 { - idx_raw.push(ch); - } - } - ']' => { - if bracket_depth > 0 { - bracket_depth -= 1; - if bracket_depth == 0 { - if idx_raw.is_empty() { - return Ok(None); - } - break; - } - } - - idx_raw.push(ch); - } - _ if bracket_depth > 0 => { - idx_raw.push(ch); - } - _ => { - var_name.push(ch); - } - } - } - if idx_raw.is_empty() { - Ok(None) - } else { - if var_name.is_empty() { - return Ok(None); - } - - if !self.var_exists(&var_name) { - return Err(ShErr::simple( - ShErrKind::ExecFail, - format!("Variable '{}' not found", var_name) - )); - } - - let expanded = LexStream::new(Arc::new(idx_raw), LexFlags::empty()) - .map(|tk| tk.and_then(|tk| tk.expand()).map(|tk| tk.get_words())) - .try_fold(vec![], |mut acc, wrds| { - match wrds { - Ok(wrds) => acc.extend(wrds), - Err(e) => return Err(e), - } - Ok(acc) - })? - .into_iter() - .next(); - - let Some(exp) = expanded else { - return Ok(None) - }; - let idx = exp.parse::().map_err(|_| ShErr::simple( - ShErrKind::ParseErr, - format!("Invalid array index: {}", exp) - ))?; - - Ok(Some((var_name, idx))) - } - } pub fn set_var(&mut self, var_name: &str, val: VarKind, flags: VarFlags) -> ShResult<()> { let is_local = self.is_local_var(var_name); if flags.contains(VarFlags::LOCAL) || is_local { @@ -275,29 +199,27 @@ impl ScopeStack { self.set_var_global(var_name, val, flags) } } + pub fn set_var_indexed(&mut self, var_name: &str, idx: ArrIndex, val: String, flags: VarFlags) -> ShResult<()> { + let is_local = self.is_local_var(var_name); + if flags.contains(VarFlags::LOCAL) || is_local { + let Some(scope) = self.scopes.last_mut() else { return Ok(()) }; + scope.set_index(var_name, idx, val) + } else { + let Some(scope) = self.scopes.first_mut() else { return Ok(()) }; + scope.set_index(var_name, idx, val) + } + } fn set_var_global(&mut self, var_name: &str, val: VarKind, flags: VarFlags) -> ShResult<()> { - let idx_result = self.parse_arr_index(var_name); let Some(scope) = self.scopes.first_mut() else { return Ok(()) }; - - if let Ok(Some((var,idx))) = idx_result { - scope.set_index(&var, idx, val.to_string()) - } else { - scope.set_var(var_name, val, flags) - } + scope.set_var(var_name, val, flags) } fn set_var_local(&mut self, var_name: &str, val: VarKind, flags: VarFlags) -> ShResult<()> { - let idx_result = self.parse_arr_index(var_name); let Some(scope) = self.scopes.last_mut() else { return Ok(()) }; - - if let Ok(Some((var,idx))) = idx_result { - scope.set_index(&var, idx, val.to_string()) - } else { - scope.set_var(var_name, val, flags) - } + scope.set_var(var_name, val, flags) } pub fn get_arr_elems(&self, var_name: &str) -> ShResult> { for scope in self.scopes.iter().rev() { @@ -1227,6 +1149,67 @@ pub fn write_vars T>(f: F) -> T { FERN.with(|shed| f(&mut shed.var_scopes.borrow_mut())) } +/// Parse `arr[idx]` into (name, raw_index_expr). Pure parsing, no expansion. +pub fn parse_arr_bracket(var_name: &str) -> Option<(String, String)> { + let mut chars = var_name.chars(); + let mut name = String::new(); + let mut idx_raw = String::new(); + let mut bracket_depth = 0; + + while let Some(ch) = chars.next() { + match ch { + '\\' => { chars.next(); } + '[' => { + bracket_depth += 1; + if bracket_depth > 1 { + idx_raw.push(ch); + } + } + ']' => { + if bracket_depth > 0 { + bracket_depth -= 1; + if bracket_depth == 0 { + if idx_raw.is_empty() { + return None; + } + break; + } + } + idx_raw.push(ch); + } + _ if bracket_depth > 0 => idx_raw.push(ch), + _ => name.push(ch), + } + } + + if name.is_empty() || idx_raw.is_empty() { + None + } else { + Some((name, idx_raw)) + } +} + +/// Expand the raw index expression and parse it into an ArrIndex. +pub fn expand_arr_index(idx_raw: &str) -> ShResult { + let expanded = LexStream::new(Arc::new(idx_raw.to_string()), LexFlags::empty()) + .map(|tk| tk.and_then(|tk| tk.expand()).map(|tk| tk.get_words())) + .try_fold(vec![], |mut acc, wrds| { + match wrds { + Ok(wrds) => acc.extend(wrds), + Err(e) => return Err(e), + } + Ok(acc) + })? + .into_iter() + .next() + .ok_or_else(|| ShErr::simple(ShErrKind::ParseErr, "Empty array index"))?; + + expanded.parse::().map_err(|_| ShErr::simple( + ShErrKind::ParseErr, + format!("Invalid array index: {}", expanded) + )) +} + pub fn read_meta T>(f: F) -> T { FERN.with(|shed| f(&shed.meta.borrow())) }