use std::{fmt, time::Duration};
use crate::metadata::{token::Token, typesystem::CilFlavor};
pub mod synthetic_exception {
use crate::metadata::token::Token;
pub const INDEX_OUT_OF_RANGE: Token = Token::new(0x0100_FF01);
pub const NULL_REFERENCE: Token = Token::new(0x0100_FF02);
pub const DIVIDE_BY_ZERO: Token = Token::new(0x0100_FF03);
pub const OVERFLOW: Token = Token::new(0x0100_FF04);
pub const INVALID_CAST: Token = Token::new(0x0100_FF05);
pub const BASE_EXCEPTION: Token = Token::new(0x0100_FF00);
}
#[derive(Debug, Clone, PartialEq)]
pub enum EmulationError {
StackOverflow,
StackUnderflow,
StackTypeMismatch {
expected: &'static str,
found: &'static str,
},
LocalIndexOutOfBounds {
index: usize,
count: usize,
},
ArgumentIndexOutOfBounds {
index: usize,
count: usize,
},
LocalFlavorMismatch {
index: usize,
expected: Box<CilFlavor>,
found: Box<CilFlavor>,
},
ArgumentFlavorMismatch {
index: usize,
expected: Box<CilFlavor>,
found: Box<CilFlavor>,
},
InvalidHeapReference {
reference_id: u64,
},
HeapMemoryLimitExceeded {
current: usize,
limit: usize,
},
InvalidPointer {
address: u64,
reason: &'static str,
},
NullReference,
DivisionByZero,
ArithmeticOverflow,
InvalidConversion {
from: &'static str,
to: &'static str,
},
TypeMismatch {
operation: &'static str,
expected: &'static str,
found: &'static str,
},
InvalidCast {
from_type: String,
to_type: String,
},
InvalidBranchTarget {
target: u64,
},
InvalidInstructionPointer {
offset: u32,
},
MethodNotFound {
token: Token,
},
CallDepthExceeded {
depth: usize,
limit: usize,
},
InstructionLimitExceeded {
executed: u64,
limit: u64,
},
Timeout {
elapsed: Duration,
limit: Duration,
},
UnsupportedOpcode {
opcode: u8,
prefix: Option<u8>,
mnemonic: Option<&'static str>,
},
InvalidOperand {
instruction: &'static str,
expected: &'static str,
},
MissingMethodBody {
token: Token,
},
InvalidMethodMetadata {
token: Token,
reason: &'static str,
},
FieldNotFound {
token: Token,
},
ArrayIndexOutOfBounds {
index: i64,
length: usize,
},
ArrayElementTypeMismatch {
expected: &'static str,
found: &'static str,
},
UserStringNotFound {
index: u32,
},
InvalidStringOperation {
description: String,
},
UnhandledException {
description: String,
},
InvalidExceptionHandler {
description: String,
},
InternalError {
description: String,
},
SymbolicValueRequired {
operation: &'static str,
},
InvalidOperationTypes {
operation: String,
operand_types: String,
},
InvalidStackState {
message: String,
},
HeapTypeMismatch {
expected: &'static str,
found: &'static str,
},
UnsupportedMethod {
token: Token,
reason: &'static str,
},
TypeNotFound {
method_token: Token,
local_index: u16,
},
ValueConversion {
source_type: &'static str,
target_type: &'static str,
},
InvalidAddress {
address: u64,
reason: String,
},
UnresolvedTypeToken {
token: Token,
},
HookError(String),
PageOutOfBounds {
offset: usize,
size: usize,
page_size: usize,
},
LockPoisoned {
description: &'static str,
},
}
impl fmt::Display for EmulationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EmulationError::StackOverflow => write!(f, "evaluation stack overflow"),
EmulationError::StackUnderflow => write!(f, "evaluation stack underflow"),
EmulationError::StackTypeMismatch { expected, found } => {
write!(f, "stack type mismatch: expected {expected}, found {found}")
}
EmulationError::LocalIndexOutOfBounds { index, count } => {
write!(
f,
"local variable index {index} out of bounds (count: {count})"
)
}
EmulationError::ArgumentIndexOutOfBounds { index, count } => {
write!(f, "argument index {index} out of bounds (count: {count})")
}
EmulationError::LocalFlavorMismatch {
index,
expected,
found,
} => {
write!(
f,
"local variable {index} type mismatch: expected {expected:?}, found {found:?}"
)
}
EmulationError::ArgumentFlavorMismatch {
index,
expected,
found,
} => {
write!(
f,
"argument {index} type mismatch: expected {expected:?}, found {found:?}"
)
}
EmulationError::InvalidHeapReference { reference_id } => {
write!(f, "invalid heap reference: {reference_id}")
}
EmulationError::HeapMemoryLimitExceeded { current, limit } => {
write!(
f,
"heap memory limit exceeded: {current} bytes (limit: {limit})"
)
}
EmulationError::InvalidPointer { address, reason } => {
write!(f, "invalid pointer 0x{address:016X}: {reason}")
}
EmulationError::NullReference => write!(f, "null reference"),
EmulationError::DivisionByZero => write!(f, "division by zero"),
EmulationError::ArithmeticOverflow => write!(f, "arithmetic overflow"),
EmulationError::InvalidConversion { from, to } => {
write!(f, "invalid conversion from {from} to {to}")
}
EmulationError::TypeMismatch {
operation,
expected,
found,
} => {
write!(
f,
"type mismatch in {operation}: expected {expected}, found {found}"
)
}
EmulationError::InvalidCast { from_type, to_type } => {
write!(f, "invalid cast from {from_type} to {to_type}")
}
EmulationError::InvalidBranchTarget { target } => {
write!(f, "invalid branch target: 0x{target:08X}")
}
EmulationError::InvalidInstructionPointer { offset } => {
write!(f, "invalid instruction pointer: 0x{offset:08X}")
}
EmulationError::MethodNotFound { token } => {
write!(f, "method not found: 0x{:08X}", token.value())
}
EmulationError::CallDepthExceeded { depth, limit } => {
write!(f, "call depth exceeded: {depth} (limit: {limit})")
}
EmulationError::InstructionLimitExceeded { executed, limit } => {
write!(f, "instruction limit exceeded: {executed} (limit: {limit})")
}
EmulationError::Timeout { elapsed, limit } => {
write!(
f,
"execution timeout: {}ms (limit: {}ms)",
elapsed.as_millis(),
limit.as_millis()
)
}
EmulationError::UnsupportedOpcode {
opcode,
prefix,
mnemonic,
} => {
let mnemonic_str = mnemonic.unwrap_or("unknown");
if let Some(p) = prefix {
write!(
f,
"unsupported opcode: 0x{p:02X}:0x{opcode:02X} ({mnemonic_str})"
)
} else {
write!(f, "unsupported opcode: 0x{opcode:02X} ({mnemonic_str})")
}
}
EmulationError::InvalidOperand {
instruction,
expected,
} => {
write!(f, "invalid operand for {instruction}: expected {expected}")
}
EmulationError::MissingMethodBody { token } => {
write!(f, "missing method body: 0x{:08X}", token.value())
}
EmulationError::InvalidMethodMetadata { token, reason } => {
write!(
f,
"invalid method metadata for 0x{:08X}: {reason}",
token.value()
)
}
EmulationError::FieldNotFound { token } => {
write!(f, "field not found: 0x{:08X}", token.value())
}
EmulationError::ArrayIndexOutOfBounds { index, length } => {
write!(f, "array index {index} out of bounds (length: {length})")
}
EmulationError::ArrayElementTypeMismatch { expected, found } => {
write!(
f,
"array element type mismatch: expected {expected}, found {found}"
)
}
EmulationError::UserStringNotFound { index } => {
write!(f, "user string not found: 0x{index:08X}")
}
EmulationError::InvalidStringOperation { description } => {
write!(f, "invalid string operation: {description}")
}
EmulationError::UnhandledException { description } => {
write!(f, "unhandled exception: {description}")
}
EmulationError::InvalidExceptionHandler { description } => {
write!(f, "invalid exception handler: {description}")
}
EmulationError::InternalError { description } => {
write!(f, "internal emulation error: {description}")
}
EmulationError::SymbolicValueRequired { operation } => {
write!(
f,
"symbolic value encountered in {operation} (concrete value required)"
)
}
EmulationError::InvalidOperationTypes {
operation,
operand_types,
} => {
write!(f, "invalid types for {operation}: {operand_types}")
}
EmulationError::InvalidStackState { message } => {
write!(f, "invalid stack state: {message}")
}
EmulationError::HeapTypeMismatch { expected, found } => {
write!(f, "heap type mismatch: expected {expected}, found {found}")
}
EmulationError::UnsupportedMethod { token, reason } => {
write!(f, "unsupported method 0x{:08X}: {reason}", token.value())
}
EmulationError::TypeNotFound {
method_token,
local_index,
} => {
write!(
f,
"type not found for local variable {local_index} in method 0x{:08X}",
method_token.value()
)
}
EmulationError::ValueConversion {
source_type,
target_type,
} => {
write!(f, "cannot convert {source_type} to {target_type}")
}
EmulationError::InvalidAddress { address, reason } => {
write!(f, "invalid address 0x{address:08X}: {reason}")
}
EmulationError::UnresolvedTypeToken { token } => {
write!(
f,
"unresolved type token 0x{:08X} (table: 0x{:02X})",
token.value(),
token.table()
)
}
EmulationError::HookError(msg) => {
write!(f, "hook error: {msg}")
}
EmulationError::PageOutOfBounds {
offset,
size,
page_size,
} => {
write!(
f,
"page out of bounds: offset {offset} + size {size} exceeds page size {page_size}"
)
}
EmulationError::LockPoisoned { description } => {
write!(f, "lock poisoned: {description}")
}
}
}
}
impl std::error::Error for EmulationError {}
impl EmulationError {
#[must_use]
pub fn is_clr_exception(&self) -> bool {
matches!(
self,
EmulationError::ArrayIndexOutOfBounds { .. }
| EmulationError::NullReference
| EmulationError::DivisionByZero
| EmulationError::ArithmeticOverflow
| EmulationError::InvalidCast { .. }
)
}
#[must_use]
pub fn to_exception_token(&self) -> Token {
match self {
EmulationError::ArrayIndexOutOfBounds { .. } => synthetic_exception::INDEX_OUT_OF_RANGE,
EmulationError::NullReference => synthetic_exception::NULL_REFERENCE,
EmulationError::DivisionByZero => synthetic_exception::DIVIDE_BY_ZERO,
EmulationError::ArithmeticOverflow => synthetic_exception::OVERFLOW,
EmulationError::InvalidCast { .. } => synthetic_exception::INVALID_CAST,
_ => synthetic_exception::BASE_EXCEPTION,
}
}
#[must_use]
pub fn description(&self) -> String {
format!("{self}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let errors = vec![
EmulationError::StackOverflow,
EmulationError::StackUnderflow,
EmulationError::DivisionByZero,
EmulationError::NullReference,
EmulationError::LocalIndexOutOfBounds { index: 5, count: 3 },
EmulationError::UnsupportedOpcode {
opcode: 0xFF,
prefix: None,
mnemonic: Some("invalid"),
},
];
for err in errors {
let display = format!("{err}");
assert!(!display.is_empty());
}
}
#[test]
fn test_local_errors() {
let err = EmulationError::LocalIndexOutOfBounds {
index: 10,
count: 5,
};
assert!(format!("{err}").contains("10"));
assert!(format!("{err}").contains("5"));
let err = EmulationError::LocalFlavorMismatch {
index: 3,
expected: Box::new(CilFlavor::I4),
found: Box::new(CilFlavor::I8),
};
assert!(format!("{err}").contains("3"));
}
#[test]
fn test_new_variants() {
let err = EmulationError::InvalidOperationTypes {
operation: "add".to_string(),
operand_types: "i32, string".to_string(),
};
assert!(format!("{err}").contains("add"));
let err = EmulationError::InvalidStackState {
message: "corrupted".to_string(),
};
assert!(format!("{err}").contains("corrupted"));
let err = EmulationError::HeapTypeMismatch {
expected: "String",
found: "Array",
};
assert!(format!("{err}").contains("String"));
}
#[test]
fn test_error_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<EmulationError>();
}
}