use std::fmt;
use crate::{
emulation::{engine::EmulationError, EmValue},
metadata::typesystem::CilFlavor,
Result,
};
#[derive(Clone, Debug)]
pub struct LocalVariables {
values: Vec<EmValue>,
types: Vec<CilFlavor>,
}
impl LocalVariables {
#[must_use]
pub fn new(types: Vec<CilFlavor>) -> Self {
let values = types.iter().map(EmValue::default_for_flavor).collect();
LocalVariables { values, types }
}
#[must_use]
pub fn empty() -> Self {
LocalVariables {
values: Vec::new(),
types: Vec::new(),
}
}
#[must_use]
pub fn with_values(values: Vec<EmValue>, types: Vec<CilFlavor>) -> Self {
assert_eq!(
values.len(),
types.len(),
"values and types must have same length"
);
LocalVariables { values, types }
}
pub fn get(&self, index: usize) -> Result<&EmValue> {
if index >= self.values.len() {
return Err(EmulationError::LocalIndexOutOfBounds {
index,
count: self.values.len(),
}
.into());
}
Ok(&self.values[index])
}
pub fn get_mut(&mut self, index: usize) -> Result<&mut EmValue> {
if index >= self.values.len() {
return Err(EmulationError::LocalIndexOutOfBounds {
index,
count: self.values.len(),
}
.into());
}
Ok(&mut self.values[index])
}
pub fn set(&mut self, index: usize, value: EmValue) -> Result<()> {
if index >= self.values.len() {
return Err(EmulationError::LocalIndexOutOfBounds {
index,
count: self.values.len(),
}
.into());
}
if !value.is_symbolic() {
let found = value.cil_flavor();
if !self.types[index].is_stack_assignable_from(&found) {
self.types[index] = found;
}
}
self.values[index] = value;
Ok(())
}
pub fn get_type(&self, index: usize) -> Result<&CilFlavor> {
if index >= self.types.len() {
return Err(EmulationError::LocalIndexOutOfBounds {
index,
count: self.types.len(),
}
.into());
}
Ok(&self.types[index])
}
#[must_use]
pub fn count(&self) -> usize {
self.values.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
#[must_use]
pub fn values(&self) -> &[EmValue] {
&self.values
}
#[must_use]
pub fn types(&self) -> &[CilFlavor] {
&self.types
}
pub fn reset(&mut self) {
for (value, typ) in self.values.iter_mut().zip(self.types.iter()) {
*value = EmValue::default_for_flavor(typ);
}
}
#[must_use]
pub fn snapshot(&self) -> Vec<EmValue> {
self.values.clone()
}
pub fn restore(&mut self, snapshot: Vec<EmValue>) {
assert_eq!(snapshot.len(), self.values.len(), "snapshot size mismatch");
self.values = snapshot;
}
pub fn iter(&self) -> impl Iterator<Item = (usize, &EmValue)> {
self.values.iter().enumerate()
}
pub fn iter_typed(&self) -> impl Iterator<Item = (usize, &CilFlavor, &EmValue)> {
self.values
.iter()
.zip(self.types.iter())
.enumerate()
.map(|(i, (v, t))| (i, t, v))
}
}
impl Default for LocalVariables {
fn default() -> Self {
Self::empty()
}
}
impl fmt::Display for LocalVariables {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Locals[")?;
for (i, (value, typ)) in self.values.iter().zip(self.types.iter()).enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{i}:{typ:?}={value}")?;
}
write!(f, "]")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Error;
#[test]
fn test_locals_creation() {
let locals = LocalVariables::new(vec![CilFlavor::I4, CilFlavor::I8, CilFlavor::Object]);
assert_eq!(locals.count(), 3);
assert_eq!(locals.get(0).unwrap(), &EmValue::I32(0));
assert_eq!(locals.get(1).unwrap(), &EmValue::I64(0));
assert_eq!(locals.get(2).unwrap(), &EmValue::Null);
}
#[test]
fn test_locals_empty() {
let locals = LocalVariables::empty();
assert!(locals.is_empty());
assert_eq!(locals.count(), 0);
}
#[test]
fn test_locals_get_set() {
let mut locals = LocalVariables::new(vec![CilFlavor::I4, CilFlavor::I8]);
locals.set(0, EmValue::I32(42)).unwrap();
assert_eq!(locals.get(0).unwrap(), &EmValue::I32(42));
locals.set(1, EmValue::I64(100)).unwrap();
assert_eq!(locals.get(1).unwrap(), &EmValue::I64(100));
}
#[test]
fn test_locals_out_of_bounds() {
let locals = LocalVariables::new(vec![CilFlavor::I4]);
let result = locals.get(5);
assert!(matches!(
result,
Err(Error::Emulation(ref e)) if matches!(e.as_ref(), EmulationError::LocalIndexOutOfBounds { index: 5, count: 1 })
));
}
#[test]
fn test_locals_type_mismatch_accepted() {
let mut locals = LocalVariables::new(vec![CilFlavor::I4]);
let result = locals.set(0, EmValue::I64(100));
assert!(result.is_ok());
assert_eq!(locals.get(0).unwrap(), &EmValue::I64(100));
assert_eq!(locals.get_type(0).unwrap(), &CilFlavor::I8);
}
#[test]
fn test_locals_get_type() {
let locals = LocalVariables::new(vec![CilFlavor::I4, CilFlavor::R8]);
assert_eq!(locals.get_type(0).unwrap(), &CilFlavor::I4);
assert_eq!(locals.get_type(1).unwrap(), &CilFlavor::R8);
}
#[test]
fn test_locals_reset() {
let mut locals = LocalVariables::new(vec![CilFlavor::I4, CilFlavor::I8]);
locals.set(0, EmValue::I32(42)).unwrap();
locals.set(1, EmValue::I64(100)).unwrap();
locals.reset();
assert_eq!(locals.get(0).unwrap(), &EmValue::I32(0));
assert_eq!(locals.get(1).unwrap(), &EmValue::I64(0));
}
#[test]
fn test_locals_snapshot_restore() {
let mut locals = LocalVariables::new(vec![CilFlavor::I4, CilFlavor::I8]);
locals.set(0, EmValue::I32(42)).unwrap();
let snapshot = locals.snapshot();
locals.set(0, EmValue::I32(99)).unwrap();
assert_eq!(locals.get(0).unwrap(), &EmValue::I32(99));
locals.restore(snapshot);
assert_eq!(locals.get(0).unwrap(), &EmValue::I32(42));
}
#[test]
fn test_locals_iter() {
let locals = LocalVariables::new(vec![CilFlavor::I4, CilFlavor::I8]);
let items: Vec<_> = locals.iter().collect();
assert_eq!(items.len(), 2);
assert_eq!(items[0], (0, &EmValue::I32(0)));
assert_eq!(items[1], (1, &EmValue::I64(0)));
}
#[test]
fn test_locals_display() {
let mut locals = LocalVariables::new(vec![CilFlavor::I4]);
locals.set(0, EmValue::I32(42)).unwrap();
let display = format!("{locals}");
assert!(display.contains("42"));
}
#[test]
fn test_locals_error_display() {
let err = EmulationError::LocalIndexOutOfBounds { index: 5, count: 3 };
assert!(format!("{err}").contains("5"));
assert!(format!("{err}").contains("3"));
}
}