Fixed possible refcell borrow panic related to expanding variables in an array index
This commit is contained in:
@@ -898,51 +898,49 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
fn set_assignments(&self, assigns: Vec<Node>, behavior: AssignBehavior) -> ShResult<Vec<String>> {
|
fn set_assignments(&self, assigns: Vec<Node>, behavior: AssignBehavior) -> ShResult<Vec<String>> {
|
||||||
let mut new_env_vars = vec![];
|
let mut new_env_vars = vec![];
|
||||||
match behavior {
|
let flags = match behavior {
|
||||||
AssignBehavior::Export => {
|
AssignBehavior::Export => VarFlags::EXPORT,
|
||||||
for assign in assigns {
|
AssignBehavior::Set => VarFlags::NONE,
|
||||||
let is_arr = assign.flags.contains(NdFlags::ARR_ASSIGN);
|
};
|
||||||
let NdRule::Assignment { kind, var, val } = assign.class else {
|
|
||||||
unreachable!()
|
for assign in assigns {
|
||||||
};
|
let is_arr = assign.flags.contains(NdFlags::ARR_ASSIGN);
|
||||||
let var = var.span.as_str();
|
let NdRule::Assignment { kind, var, val } = assign.class else {
|
||||||
let val = if is_arr {
|
unreachable!()
|
||||||
VarKind::arr_from_tk(val)?
|
};
|
||||||
} else {
|
let var = var.span.as_str();
|
||||||
VarKind::Str(val.expand()?.get_words().join(" "))
|
let val = if is_arr {
|
||||||
};
|
VarKind::arr_from_tk(val)?
|
||||||
match kind {
|
} else {
|
||||||
AssignKind::Eq => write_vars(|v| v.set_var(var, val, VarFlags::EXPORT))?,
|
VarKind::Str(val.expand()?.get_words().join(" "))
|
||||||
AssignKind::PlusEq => todo!(),
|
};
|
||||||
AssignKind::MinusEq => todo!(),
|
|
||||||
AssignKind::MultEq => todo!(),
|
// Parse and expand array index BEFORE entering write_vars borrow
|
||||||
AssignKind::DivEq => todo!(),
|
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 {
|
if matches!(behavior, AssignBehavior::Export) {
|
||||||
let is_arr = assign.flags.contains(NdFlags::ARR_ASSIGN);
|
new_env_vars.push(var.to_string());
|
||||||
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!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(new_env_vars)
|
Ok(new_env_vars)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
163
src/state.rs
163
src/state.rs
@@ -191,82 +191,6 @@ impl ScopeStack {
|
|||||||
|
|
||||||
flat_vars
|
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<()> {
|
pub fn set_var(&mut self, var_name: &str, val: VarKind, flags: VarFlags) -> ShResult<()> {
|
||||||
let is_local = self.is_local_var(var_name);
|
let is_local = self.is_local_var(var_name);
|
||||||
if flags.contains(VarFlags::LOCAL) || is_local {
|
if flags.contains(VarFlags::LOCAL) || is_local {
|
||||||
@@ -275,29 +199,27 @@ impl ScopeStack {
|
|||||||
self.set_var_global(var_name, val, flags)
|
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<()> {
|
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 {
|
let Some(scope) = self.scopes.first_mut() else {
|
||||||
return Ok(())
|
return Ok(())
|
||||||
};
|
};
|
||||||
|
scope.set_var(var_name, val, flags)
|
||||||
if let Ok(Some((var,idx))) = idx_result {
|
|
||||||
scope.set_index(&var, idx, val.to_string())
|
|
||||||
} else {
|
|
||||||
scope.set_var(var_name, val, flags)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fn set_var_local(&mut self, var_name: &str, val: VarKind, flags: VarFlags) -> ShResult<()> {
|
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 {
|
let Some(scope) = self.scopes.last_mut() else {
|
||||||
return Ok(())
|
return Ok(())
|
||||||
};
|
};
|
||||||
|
scope.set_var(var_name, val, flags)
|
||||||
if let Ok(Some((var,idx))) = idx_result {
|
|
||||||
scope.set_index(&var, idx, val.to_string())
|
|
||||||
} else {
|
|
||||||
scope.set_var(var_name, val, flags)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
pub fn get_arr_elems(&self, var_name: &str) -> ShResult<Vec<String>> {
|
pub fn get_arr_elems(&self, var_name: &str) -> ShResult<Vec<String>> {
|
||||||
for scope in self.scopes.iter().rev() {
|
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()))
|
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 {
|
pub fn read_meta<T, F: FnOnce(&MetaTab) -> T>(f: F) -> T {
|
||||||
FERN.with(|shed| f(&shed.meta.borrow()))
|
FERN.with(|shed| f(&shed.meta.borrow()))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user