use std::fmt;
use crate::{
emulation::{engine::EmulationError, EmValue},
metadata::typesystem::CilFlavor,
Result,
};
#[derive(Clone, Debug)]
pub struct ArgumentStorage {
values: Vec<EmValue>,
types: Vec<CilFlavor>,
has_this: bool,
}
impl ArgumentStorage {
#[must_use]
pub fn new(values: Vec<EmValue>, types: Vec<CilFlavor>) -> Self {
assert_eq!(
values.len(),
types.len(),
"values and types must have same length"
);
ArgumentStorage {
values,
types,
has_this: false,
}
}
#[must_use]
pub fn with_this(
this_ref: EmValue,
arg_values: Vec<EmValue>,
arg_types: Vec<CilFlavor>,
) -> Self {
let mut values = vec![this_ref];
values.extend(arg_values);
let mut types = vec![CilFlavor::Object];
types.extend(arg_types);
ArgumentStorage {
values,
types,
has_this: true,
}
}
#[must_use]
pub fn empty() -> Self {
ArgumentStorage {
values: Vec::new(),
types: Vec::new(),
has_this: false,
}
}
pub fn get(&self, index: usize) -> Result<&EmValue> {
if index >= self.values.len() {
return Err(EmulationError::ArgumentIndexOutOfBounds {
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::ArgumentIndexOutOfBounds {
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::ArgumentIndexOutOfBounds {
index,
count: self.values.len(),
}
.into());
}
let expected = &self.types[index];
let found = value.cil_flavor();
if !expected.is_stack_assignable_from(&found) && !value.is_symbolic() {
return Err(EmulationError::ArgumentFlavorMismatch {
index,
expected: Box::new(expected.clone()),
found: Box::new(found),
}
.into());
}
self.values[index] = value;
Ok(())
}
pub fn get_type(&self, index: usize) -> Result<&CilFlavor> {
if index >= self.types.len() {
return Err(EmulationError::ArgumentIndexOutOfBounds {
index,
count: self.types.len(),
}
.into());
}
Ok(&self.types[index])
}
#[must_use]
pub fn this(&self) -> Option<&EmValue> {
if self.has_this && !self.values.is_empty() {
Some(&self.values[0])
} else {
None
}
}
#[must_use]
pub fn has_this(&self) -> bool {
self.has_this
}
#[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 iter(&self) -> impl Iterator<Item = (usize, &EmValue)> {
self.values.iter().enumerate()
}
#[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;
}
}
impl Default for ArgumentStorage {
fn default() -> Self {
Self::empty()
}
}
impl fmt::Display for ArgumentStorage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Args[")?;
for (i, (value, typ)) in self.values.iter().zip(self.types.iter()).enumerate() {
if i > 0 {
write!(f, ", ")?;
}
if i == 0 && self.has_this {
write!(f, "this={value}")?;
} else {
write!(f, "{i}:{typ:?}={value}")?;
}
}
write!(f, "]")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{emulation::HeapRef, Error};
#[test]
fn test_arguments_creation() {
let args = ArgumentStorage::new(
vec![EmValue::I32(10), EmValue::I32(20)],
vec![CilFlavor::I4, CilFlavor::I4],
);
assert_eq!(args.count(), 2);
assert_eq!(args.get(0).unwrap(), &EmValue::I32(10));
assert_eq!(args.get(1).unwrap(), &EmValue::I32(20));
assert!(!args.has_this());
}
#[test]
fn test_arguments_with_this() {
let this_ref = EmValue::ObjectRef(HeapRef::new(1));
let args = ArgumentStorage::with_this(
this_ref.clone(),
vec![EmValue::I32(42)],
vec![CilFlavor::I4],
);
assert!(args.has_this());
assert_eq!(args.count(), 2);
assert_eq!(args.this().unwrap(), &this_ref);
assert_eq!(args.get(1).unwrap(), &EmValue::I32(42));
}
#[test]
fn test_arguments_empty() {
let args = ArgumentStorage::empty();
assert!(args.is_empty());
assert!(!args.has_this());
}
#[test]
fn test_arguments_get_set() {
let mut args = ArgumentStorage::new(vec![EmValue::I32(10)], vec![CilFlavor::I4]);
args.set(0, EmValue::I32(42)).unwrap();
assert_eq!(args.get(0).unwrap(), &EmValue::I32(42));
}
#[test]
fn test_arguments_out_of_bounds() {
let args = ArgumentStorage::new(vec![EmValue::I32(10)], vec![CilFlavor::I4]);
assert!(matches!(
args.get(5),
Err(Error::Emulation(ref e)) if matches!(e.as_ref(), EmulationError::ArgumentIndexOutOfBounds { .. })
));
}
#[test]
fn test_arguments_type_mismatch() {
let mut args = ArgumentStorage::new(vec![EmValue::I32(10)], vec![CilFlavor::I4]);
let result = args.set(0, EmValue::I64(100));
assert!(matches!(
result,
Err(Error::Emulation(ref e)) if matches!(e.as_ref(), EmulationError::ArgumentFlavorMismatch { .. })
));
}
#[test]
fn test_arguments_get_type() {
let args = ArgumentStorage::new(
vec![EmValue::I32(10), EmValue::I64(20)],
vec![CilFlavor::I4, CilFlavor::I8],
);
assert_eq!(args.get_type(0).unwrap(), &CilFlavor::I4);
assert_eq!(args.get_type(1).unwrap(), &CilFlavor::I8);
}
#[test]
fn test_arguments_snapshot_restore() {
let mut args = ArgumentStorage::new(vec![EmValue::I32(10)], vec![CilFlavor::I4]);
let snapshot = args.snapshot();
args.set(0, EmValue::I32(99)).unwrap();
args.restore(snapshot);
assert_eq!(args.get(0).unwrap(), &EmValue::I32(10));
}
#[test]
fn test_arguments_iter() {
let args = ArgumentStorage::new(
vec![EmValue::I32(10), EmValue::I32(20)],
vec![CilFlavor::I4, CilFlavor::I4],
);
let items: Vec<_> = args.iter().collect();
assert_eq!(items.len(), 2);
}
#[test]
fn test_arguments_display() {
let args = ArgumentStorage::new(vec![EmValue::I32(42)], vec![CilFlavor::I4]);
let display = format!("{args}");
assert!(display.contains("42"));
}
}