use std::fmt;
use crate::{
emulation::{engine::EmulationError, EmValue},
metadata::typesystem::CilFlavor,
Result,
};
#[derive(Clone, Debug)]
pub struct EvaluationStack {
values: Vec<EmValue>,
max_depth: usize,
}
impl EvaluationStack {
#[must_use]
pub fn new(max_depth: usize) -> Self {
EvaluationStack {
values: Vec::with_capacity(max_depth.min(256)),
max_depth,
}
}
#[must_use]
pub fn default_depth() -> Self {
Self::new(10000)
}
pub fn push(&mut self, value: EmValue) -> Result<()> {
if self.values.len() >= self.max_depth {
return Err(EmulationError::StackOverflow.into());
}
self.values.push(value);
Ok(())
}
pub fn pop(&mut self) -> Result<EmValue> {
self.values
.pop()
.ok_or(EmulationError::StackUnderflow.into())
}
pub fn peek(&self) -> Result<&EmValue> {
self.values
.last()
.ok_or(EmulationError::StackUnderflow.into())
}
pub fn peek_at(&self, depth: usize) -> Result<&EmValue> {
if depth >= self.values.len() {
return Err(EmulationError::StackUnderflow.into());
}
Ok(&self.values[self.values.len() - 1 - depth])
}
pub fn pop_typed(&mut self, expected: &CilFlavor) -> Result<EmValue> {
let value = self.pop()?;
let actual = value.cil_flavor();
if !actual.is_compatible_with(expected) {
self.values.push(value);
return Err(EmulationError::StackTypeMismatch {
expected: expected.as_str(),
found: actual.as_str(),
}
.into());
}
Ok(value)
}
pub fn pop_i32(&mut self) -> Result<i32> {
let value = self.pop()?;
match value {
EmValue::I32(v) => Ok(v),
EmValue::Bool(v) => Ok(i32::from(v)),
EmValue::Char(v) => Ok(v as i32),
_ => {
let found = value.cil_flavor();
self.values.push(value);
Err(EmulationError::StackTypeMismatch {
expected: "int32",
found: found.as_str(),
}
.into())
}
}
}
pub fn pop_i64(&mut self) -> Result<i64> {
let value = self.pop()?;
if let EmValue::I64(v) = value {
Ok(v)
} else {
let found = value.cil_flavor();
self.values.push(value);
Err(EmulationError::StackTypeMismatch {
expected: "int64",
found: found.as_str(),
}
.into())
}
}
pub fn pop_f32(&mut self) -> Result<f32> {
let value = self.pop()?;
if let EmValue::F32(v) = value {
Ok(v)
} else {
let found = value.cil_flavor();
self.values.push(value);
Err(EmulationError::StackTypeMismatch {
expected: "float32",
found: found.as_str(),
}
.into())
}
}
pub fn pop_f64(&mut self) -> Result<f64> {
let value = self.pop()?;
match value {
EmValue::F64(v) => Ok(v),
EmValue::F32(v) => Ok(f64::from(v)), _ => {
let found = value.cil_flavor();
self.values.push(value);
Err(EmulationError::StackTypeMismatch {
expected: "float64",
found: found.as_str(),
}
.into())
}
}
}
pub fn pop_object_ref(&mut self) -> Result<EmValue> {
let value = self.pop()?;
match value {
EmValue::ObjectRef(_) | EmValue::Null => Ok(value),
_ => {
let found = value.cil_flavor();
self.values.push(value);
Err(EmulationError::StackTypeMismatch {
expected: "object ref",
found: found.as_str(),
}
.into())
}
}
}
pub fn pop_binary(&mut self) -> Result<(EmValue, EmValue)> {
let right = self.pop()?;
let left = self.pop().inspect_err(|_| {
self.values.push(right.clone());
})?;
Ok((left, right))
}
pub fn dup(&mut self) -> Result<()> {
let value = self.peek()?.clone();
self.push(value)
}
pub fn swap(&mut self) -> Result<()> {
let len = self.values.len();
if len < 2 {
return Err(EmulationError::StackUnderflow.into());
}
self.values.swap(len - 1, len - 2);
Ok(())
}
pub fn clear(&mut self) {
self.values.clear();
}
#[must_use]
pub fn depth(&self) -> usize {
self.values.len()
}
#[must_use]
pub fn max_depth(&self) -> usize {
self.max_depth
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
#[must_use]
pub fn values(&self) -> &[EmValue] {
&self.values
}
pub fn iter(&self) -> impl Iterator<Item = &EmValue> {
self.values.iter()
}
pub fn iter_top_down(&self) -> impl Iterator<Item = &EmValue> {
self.values.iter().rev()
}
#[must_use]
pub fn snapshot(&self) -> Vec<EmValue> {
self.values.clone()
}
pub fn restore(&mut self, snapshot: Vec<EmValue>) {
self.values = snapshot;
}
pub fn verify_types(&self, expected: &[CilFlavor]) -> Result<()> {
if self.values.len() != expected.len() {
return Err(EmulationError::InvalidStackState {
message: format!(
"stack depth mismatch: expected {}, found {}",
expected.len(),
self.values.len()
),
}
.into());
}
for (i, (value, exp)) in self.values.iter().zip(expected.iter()).enumerate() {
let actual = value.cil_flavor();
if !actual.is_compatible_with(exp) {
return Err(EmulationError::InvalidStackState {
message: format!(
"type mismatch at position {i}: expected {exp}, found {actual}"
),
}
.into());
}
}
Ok(())
}
}
impl Default for EvaluationStack {
fn default() -> Self {
Self::default_depth()
}
}
impl fmt::Display for EvaluationStack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Stack[")?;
for (i, value) in self.values.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{value}")?;
}
write!(f, "]")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{emulation::HeapRef, Error};
#[test]
fn test_stack_push_pop() {
let mut stack = EvaluationStack::new(10);
stack.push(EmValue::I32(1)).unwrap();
stack.push(EmValue::I32(2)).unwrap();
stack.push(EmValue::I32(3)).unwrap();
assert_eq!(stack.depth(), 3);
assert_eq!(stack.pop().unwrap(), EmValue::I32(3));
assert_eq!(stack.pop().unwrap(), EmValue::I32(2));
assert_eq!(stack.pop().unwrap(), EmValue::I32(1));
assert!(stack.is_empty());
}
#[test]
fn test_stack_overflow() {
let mut stack = EvaluationStack::new(2);
stack.push(EmValue::I32(1)).unwrap();
stack.push(EmValue::I32(2)).unwrap();
let result = stack.push(EmValue::I32(3));
assert!(matches!(
result,
Err(Error::Emulation(ref e)) if matches!(e.as_ref(), EmulationError::StackOverflow)
));
}
#[test]
fn test_stack_underflow() {
let mut stack = EvaluationStack::new(10);
let result = stack.pop();
assert!(matches!(
result,
Err(Error::Emulation(ref e)) if matches!(e.as_ref(), EmulationError::StackUnderflow)
));
}
#[test]
fn test_stack_peek() {
let mut stack = EvaluationStack::new(10);
stack.push(EmValue::I32(42)).unwrap();
assert_eq!(stack.peek().unwrap(), &EmValue::I32(42));
assert_eq!(stack.depth(), 1); }
#[test]
fn test_stack_peek_at() {
let mut stack = EvaluationStack::new(10);
stack.push(EmValue::I32(1)).unwrap();
stack.push(EmValue::I32(2)).unwrap();
stack.push(EmValue::I32(3)).unwrap();
assert_eq!(stack.peek_at(0).unwrap(), &EmValue::I32(3)); assert_eq!(stack.peek_at(1).unwrap(), &EmValue::I32(2));
assert_eq!(stack.peek_at(2).unwrap(), &EmValue::I32(1)); assert!(stack.peek_at(3).is_err());
}
#[test]
fn test_stack_pop_typed() {
let mut stack = EvaluationStack::new(10);
stack.push(EmValue::I32(42)).unwrap();
let value = stack.pop_typed(&CilFlavor::I4).unwrap();
assert_eq!(value, EmValue::I32(42));
stack.push(EmValue::I64(100)).unwrap();
let result = stack.pop_typed(&CilFlavor::I4);
assert!(matches!(
result,
Err(Error::Emulation(ref e)) if matches!(e.as_ref(), EmulationError::StackTypeMismatch { .. })
));
assert_eq!(stack.depth(), 1);
}
#[test]
fn test_stack_pop_i32() {
let mut stack = EvaluationStack::new(10);
stack.push(EmValue::I32(42)).unwrap();
assert_eq!(stack.pop_i32().unwrap(), 42);
stack.push(EmValue::Bool(true)).unwrap();
assert_eq!(stack.pop_i32().unwrap(), 1);
stack.push(EmValue::Char('A')).unwrap();
assert_eq!(stack.pop_i32().unwrap(), 65);
stack.push(EmValue::I64(100)).unwrap();
assert!(stack.pop_i32().is_err());
}
#[test]
fn test_stack_pop_object_ref() {
let mut stack = EvaluationStack::new(10);
stack.push(EmValue::Null).unwrap();
let val = stack.pop_object_ref().unwrap();
assert_eq!(val, EmValue::Null);
stack.push(EmValue::ObjectRef(HeapRef::new(1))).unwrap();
let val = stack.pop_object_ref().unwrap();
assert!(matches!(val, EmValue::ObjectRef(_)));
stack.push(EmValue::I32(0)).unwrap();
assert!(stack.pop_object_ref().is_err());
}
#[test]
fn test_stack_pop_binary() {
let mut stack = EvaluationStack::new(10);
stack.push(EmValue::I32(10)).unwrap();
stack.push(EmValue::I32(20)).unwrap();
let (left, right) = stack.pop_binary().unwrap();
assert_eq!(left, EmValue::I32(10));
assert_eq!(right, EmValue::I32(20));
assert!(stack.is_empty());
}
#[test]
fn test_stack_dup() {
let mut stack = EvaluationStack::new(10);
stack.push(EmValue::I32(42)).unwrap();
stack.dup().unwrap();
assert_eq!(stack.depth(), 2);
assert_eq!(stack.pop().unwrap(), EmValue::I32(42));
assert_eq!(stack.pop().unwrap(), EmValue::I32(42));
}
#[test]
fn test_stack_swap() {
let mut stack = EvaluationStack::new(10);
stack.push(EmValue::I32(1)).unwrap();
stack.push(EmValue::I32(2)).unwrap();
stack.swap().unwrap();
assert_eq!(stack.pop().unwrap(), EmValue::I32(1));
assert_eq!(stack.pop().unwrap(), EmValue::I32(2));
}
#[test]
fn test_stack_clear() {
let mut stack = EvaluationStack::new(10);
stack.push(EmValue::I32(1)).unwrap();
stack.push(EmValue::I32(2)).unwrap();
stack.clear();
assert!(stack.is_empty());
}
#[test]
fn test_stack_snapshot_restore() {
let mut stack = EvaluationStack::new(10);
stack.push(EmValue::I32(1)).unwrap();
stack.push(EmValue::I32(2)).unwrap();
let snapshot = stack.snapshot();
stack.push(EmValue::I32(3)).unwrap();
assert_eq!(stack.depth(), 3);
stack.restore(snapshot);
assert_eq!(stack.depth(), 2);
}
#[test]
fn test_stack_verify_types() {
let mut stack = EvaluationStack::new(10);
stack.push(EmValue::I32(1)).unwrap();
stack.push(EmValue::I64(2)).unwrap();
let result = stack.verify_types(&[CilFlavor::I4, CilFlavor::I8]);
assert!(result.is_ok());
let result = stack.verify_types(&[CilFlavor::I4, CilFlavor::I4]);
assert!(result.is_err());
let result = stack.verify_types(&[CilFlavor::I4]);
assert!(result.is_err()); }
#[test]
fn test_stack_display() {
let mut stack = EvaluationStack::new(10);
stack.push(EmValue::I32(1)).unwrap();
stack.push(EmValue::I64(2)).unwrap();
let display = format!("{stack}");
assert!(display.contains("1"));
assert!(display.contains("2L"));
}
}