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 edc29595b8
commit f7e3d18502
2 changed files with 111 additions and 130 deletions

View File

@@ -898,51 +898,49 @@ impl Dispatcher {
}
fn set_assignments(&self, assigns: Vec<Node>, behavior: AssignBehavior) -> ShResult<Vec<String>> {
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)
}
}

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()))
}