Fixed possible refcell borrow panic related to expanding variables in an array index

This commit is contained in:
2026-02-27 02:14:40 -05:00
parent 4ecfb28535
commit 30bc394d18
2 changed files with 111 additions and 130 deletions

View File

@@ -191,82 +191,6 @@ impl ScopeStack {
flat_vars
}
fn parse_arr_index(&self, var_name: &str) -> ShResult<Option<(String,ArrIndex)>> {
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::<ArrIndex>().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<Vec<String>> {
for scope in self.scopes.iter().rev() {
@@ -1227,6 +1149,67 @@ pub fn write_vars<T, F: FnOnce(&mut ScopeStack) -> 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<ArrIndex> {
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::<ArrIndex>().map_err(|_| ShErr::simple(
ShErrKind::ParseErr,
format!("Invalid array index: {}", expanded)
))
}
pub fn read_meta<T, F: FnOnce(&MetaTab) -> T>(f: F) -> T {
FERN.with(|shed| f(&shed.meta.borrow()))
}