use std::fmt;
use crate::{
analysis::ConstValue,
emulation::{value::symbolic::SymbolicValue, EmulationError},
metadata::{
token::Token,
typesystem::{CilFlavor, PointerSize},
},
};
#[derive(Clone, Debug)]
pub enum EmValue {
Void,
I32(i32),
I64(i64),
F32(f32),
F64(f64),
NativeInt(i64),
NativeUInt(u64),
Bool(bool),
Char(char),
ObjectRef(HeapRef),
Null,
ManagedPtr(ManagedPointer),
UnmanagedPtr(u64),
ValueType {
type_token: Token,
fields: Vec<EmValue>,
},
TypedRef {
ptr: Box<ManagedPointer>,
type_token: Token,
},
Symbolic(SymbolicValue),
}
impl EmValue {
#[must_use]
pub fn type_token(&self) -> Option<Token> {
match self {
EmValue::ValueType { type_token, .. } | EmValue::TypedRef { type_token, .. } => {
Some(*type_token)
}
_ => None,
}
}
#[must_use]
pub fn is_concrete(&self) -> bool {
!matches!(self, EmValue::Symbolic(_))
}
#[must_use]
pub fn is_symbolic(&self) -> bool {
matches!(self, EmValue::Symbolic(_))
}
#[must_use]
pub fn is_null(&self) -> bool {
matches!(self, EmValue::Null)
}
#[must_use]
pub fn is_void(&self) -> bool {
matches!(self, EmValue::Void)
}
#[must_use]
pub fn is_reference(&self) -> bool {
matches!(self, EmValue::ObjectRef(_) | EmValue::Null)
}
#[must_use]
pub fn as_i32(&self) -> Option<i32> {
match self {
EmValue::I32(v) => Some(*v),
EmValue::Bool(v) => Some(i32::from(*v)),
EmValue::Char(v) => Some(*v as i32),
_ => None,
}
}
#[must_use]
pub fn as_i64(&self) -> Option<i64> {
match self {
EmValue::I64(v) | EmValue::NativeInt(v) => Some(*v),
_ => None,
}
}
#[must_use]
pub fn as_f32(&self) -> Option<f32> {
match self {
EmValue::F32(v) => Some(*v),
_ => None,
}
}
#[must_use]
pub fn as_f64(&self) -> Option<f64> {
match self {
EmValue::F64(v) => Some(*v),
EmValue::F32(v) => Some(f64::from(*v)),
_ => None,
}
}
#[must_use]
pub fn as_bool(&self) -> Option<bool> {
match self {
EmValue::Bool(v) => Some(*v),
EmValue::I32(v) => Some(*v != 0),
_ => None,
}
}
#[must_use]
#[allow(clippy::cast_sign_loss)] pub fn as_char(&self) -> Option<char> {
match self {
EmValue::Char(v) => Some(*v),
EmValue::I32(v) => {
let code = *v as u32;
char::from_u32(code)
}
_ => None,
}
}
#[must_use]
pub fn as_object_ref(&self) -> Option<HeapRef> {
match self {
EmValue::ObjectRef(r) => Some(*r),
_ => None,
}
}
#[must_use]
pub fn as_managed_ptr(&self) -> Option<&ManagedPointer> {
match self {
EmValue::ManagedPtr(p) => Some(p),
_ => None,
}
}
#[must_use]
pub fn as_unmanaged_ptr(&self) -> Option<u64> {
match self {
EmValue::UnmanagedPtr(v) => Some(*v),
_ => None,
}
}
#[must_use]
pub fn as_native_int(&self) -> Option<i64> {
match self {
EmValue::NativeInt(v) => Some(*v),
EmValue::NativeUInt(v) => Some((*v).cast_signed()),
_ => None,
}
}
#[must_use]
#[allow(clippy::cast_possible_wrap)] pub fn to_const_value(&self) -> Option<ConstValue> {
match self {
EmValue::I32(v) => Some(ConstValue::I32(*v)),
EmValue::I64(v) => Some(ConstValue::I64(*v)),
EmValue::F32(v) => Some(ConstValue::F32(*v)),
EmValue::F64(v) => Some(ConstValue::F64(*v)),
EmValue::Bool(v) => Some(ConstValue::from_bool(*v)),
EmValue::NativeInt(v) => Some(ConstValue::NativeInt(*v)),
EmValue::NativeUInt(v) => Some(ConstValue::NativeUInt(*v)),
EmValue::Null => Some(ConstValue::Null),
_ => None,
}
}
#[must_use]
pub fn default_for_flavor(flavor: &CilFlavor) -> Self {
match flavor {
CilFlavor::Boolean => EmValue::Bool(false),
CilFlavor::Char => EmValue::Char('\0'),
CilFlavor::I1
| CilFlavor::U1
| CilFlavor::I2
| CilFlavor::U2
| CilFlavor::I4
| CilFlavor::U4 => EmValue::I32(0),
CilFlavor::I8 | CilFlavor::U8 => EmValue::I64(0),
CilFlavor::R4 => EmValue::F32(0.0),
CilFlavor::R8 => EmValue::F64(0.0),
CilFlavor::I => EmValue::NativeInt(0),
CilFlavor::U => EmValue::NativeUInt(0),
CilFlavor::Object
| CilFlavor::String
| CilFlavor::Class
| CilFlavor::Interface
| CilFlavor::Array { .. }
| CilFlavor::GenericInstance
| CilFlavor::ByRef => EmValue::Null,
CilFlavor::Pointer | CilFlavor::FnPtr { .. } => EmValue::UnmanagedPtr(0),
CilFlavor::Void
| CilFlavor::ValueType
| CilFlavor::GenericParameter { .. }
| CilFlavor::Pinned
| CilFlavor::TypedRef { .. }
| CilFlavor::Unknown => EmValue::Void,
}
}
#[must_use]
pub fn cil_flavor(&self) -> CilFlavor {
match self {
EmValue::Void => CilFlavor::Void,
EmValue::Bool(_) => CilFlavor::Boolean,
EmValue::Char(_) => CilFlavor::Char,
EmValue::I32(_) => CilFlavor::I4,
EmValue::I64(_) => CilFlavor::I8,
EmValue::F32(_) => CilFlavor::R4,
EmValue::F64(_) => CilFlavor::R8,
EmValue::NativeInt(_) => CilFlavor::I,
EmValue::NativeUInt(_) => CilFlavor::U,
EmValue::ObjectRef(_) | EmValue::Null => CilFlavor::Object,
EmValue::ManagedPtr(_) => CilFlavor::ByRef,
EmValue::UnmanagedPtr(_) => CilFlavor::Pointer,
EmValue::ValueType { .. } | EmValue::TypedRef { .. } => CilFlavor::ValueType,
EmValue::Symbolic(s) => s.cil_flavor.clone(),
}
}
}
impl PartialEq for EmValue {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(EmValue::Void, EmValue::Void) | (EmValue::Null, EmValue::Null) => true,
(EmValue::I32(a), EmValue::I32(b)) => a == b,
(EmValue::I64(a), EmValue::I64(b)) | (EmValue::NativeInt(a), EmValue::NativeInt(b)) => {
a == b
}
(EmValue::F32(a), EmValue::F32(b)) => a.to_bits() == b.to_bits(),
(EmValue::F64(a), EmValue::F64(b)) => a.to_bits() == b.to_bits(),
(EmValue::NativeUInt(a), EmValue::NativeUInt(b))
| (EmValue::UnmanagedPtr(a), EmValue::UnmanagedPtr(b)) => a == b,
(EmValue::Bool(a), EmValue::Bool(b)) => a == b,
(EmValue::Char(a), EmValue::Char(b)) => a == b,
(EmValue::ObjectRef(a), EmValue::ObjectRef(b)) => a == b,
(
EmValue::ValueType {
type_token: t1,
fields: f1,
},
EmValue::ValueType {
type_token: t2,
fields: f2,
},
) => t1 == t2 && f1 == f2,
(EmValue::ManagedPtr(a), EmValue::ManagedPtr(b)) => a == b,
_ => false,
}
}
}
impl fmt::Display for EmValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EmValue::Void => write!(f, "void"),
EmValue::I32(v) => write!(f, "{v}"),
EmValue::I64(v) => write!(f, "{v}L"),
EmValue::F32(v) => write!(f, "{v}f"),
EmValue::F64(v) => write!(f, "{v}"),
EmValue::NativeInt(v) => write!(f, "nint({v})"),
EmValue::NativeUInt(v) => write!(f, "nuint({v})"),
EmValue::Bool(v) => write!(f, "{v}"),
EmValue::Char(v) => write!(f, "'{v}'"),
EmValue::ObjectRef(r) => write!(f, "ref@{}", r.0),
EmValue::Null => write!(f, "null"),
EmValue::ManagedPtr(p) => write!(f, "&{p:?}"),
EmValue::UnmanagedPtr(v) => write!(f, "ptr(0x{v:016X})"),
EmValue::ValueType { type_token, .. } => write!(f, "valuetype({type_token})"),
EmValue::TypedRef { type_token, .. } => write!(f, "typedref({type_token})"),
EmValue::Symbolic(s) => write!(f, "symbolic({})", s.id),
}
}
}
impl From<i32> for EmValue {
fn from(value: i32) -> Self {
EmValue::I32(value)
}
}
impl From<i64> for EmValue {
fn from(value: i64) -> Self {
EmValue::I64(value)
}
}
impl From<f32> for EmValue {
fn from(value: f32) -> Self {
EmValue::F32(value)
}
}
impl From<f64> for EmValue {
fn from(value: f64) -> Self {
EmValue::F64(value)
}
}
impl From<bool> for EmValue {
fn from(value: bool) -> Self {
EmValue::Bool(value)
}
}
impl From<char> for EmValue {
fn from(value: char) -> Self {
EmValue::Char(value)
}
}
impl From<u8> for EmValue {
fn from(value: u8) -> Self {
EmValue::I32(i32::from(value))
}
}
impl From<i8> for EmValue {
fn from(value: i8) -> Self {
EmValue::I32(i32::from(value))
}
}
impl From<u16> for EmValue {
fn from(value: u16) -> Self {
EmValue::I32(i32::from(value))
}
}
impl From<i16> for EmValue {
fn from(value: i16) -> Self {
EmValue::I32(i32::from(value))
}
}
impl From<HeapRef> for EmValue {
fn from(value: HeapRef) -> Self {
EmValue::ObjectRef(value)
}
}
impl From<ManagedPointer> for EmValue {
fn from(value: ManagedPointer) -> Self {
EmValue::ManagedPtr(value)
}
}
impl From<&ConstValue> for EmValue {
#[allow(clippy::cast_possible_wrap)] fn from(value: &ConstValue) -> Self {
match value {
ConstValue::I8(v) => EmValue::I32(i32::from(*v)),
ConstValue::I16(v) => EmValue::I32(i32::from(*v)),
ConstValue::I32(v) => EmValue::I32(*v),
ConstValue::I64(v) => EmValue::I64(*v),
ConstValue::U8(v) => EmValue::I32(i32::from(*v)),
ConstValue::U16(v) => EmValue::I32(i32::from(*v)),
ConstValue::U32(v) => EmValue::I32(*v as i32),
ConstValue::U64(v) => EmValue::I64(*v as i64),
ConstValue::NativeInt(v) => EmValue::NativeInt(*v),
ConstValue::NativeUInt(v) => EmValue::NativeUInt(*v),
ConstValue::F32(v) => EmValue::F32(*v),
ConstValue::F64(v) => EmValue::F64(*v),
ConstValue::Null
| ConstValue::DecryptedString(_)
| ConstValue::Type(_)
| ConstValue::MethodHandle(_)
| ConstValue::FieldHandle(_) => EmValue::Null,
ConstValue::True => EmValue::I32(1),
ConstValue::False => EmValue::I32(0),
#[allow(clippy::cast_possible_wrap)]
ConstValue::String(idx) => EmValue::I32(*idx as i32),
}
}
}
impl TryFrom<&EmValue> for i32 {
type Error = crate::emulation::EmulationError;
fn try_from(value: &EmValue) -> Result<Self, Self::Error> {
match value {
EmValue::I32(v) => Ok(*v),
EmValue::Bool(v) => Ok(i32::from(*v)),
EmValue::Char(v) => Ok(*v as i32),
_ => Err(crate::emulation::EmulationError::ValueConversion {
source_type: value.type_name(),
target_type: "i32",
}),
}
}
}
impl TryFrom<EmValue> for i32 {
type Error = crate::emulation::EmulationError;
fn try_from(value: EmValue) -> Result<Self, Self::Error> {
i32::try_from(&value)
}
}
impl TryFrom<&EmValue> for i64 {
type Error = crate::emulation::EmulationError;
fn try_from(value: &EmValue) -> Result<Self, Self::Error> {
match value {
EmValue::I64(v) | EmValue::NativeInt(v) => Ok(*v),
EmValue::I32(v) => Ok(i64::from(*v)),
EmValue::Bool(v) => Ok(i64::from(*v)),
EmValue::Char(v) => Ok(i64::from(*v as u32)),
_ => Err(crate::emulation::EmulationError::ValueConversion {
source_type: value.type_name(),
target_type: "i64",
}),
}
}
}
impl TryFrom<EmValue> for i64 {
type Error = crate::emulation::EmulationError;
fn try_from(value: EmValue) -> Result<Self, Self::Error> {
i64::try_from(&value)
}
}
impl TryFrom<&EmValue> for u64 {
type Error = crate::emulation::EmulationError;
#[allow(clippy::cast_sign_loss)] fn try_from(value: &EmValue) -> Result<Self, Self::Error> {
match value {
EmValue::NativeUInt(v) | EmValue::UnmanagedPtr(v) => Ok(*v),
EmValue::I64(v) | EmValue::NativeInt(v) => Ok(*v as u64),
EmValue::I32(v) => Ok(*v as u64),
EmValue::Bool(v) => Ok(u64::from(*v)),
EmValue::Char(v) => Ok(u64::from(*v as u32)),
_ => Err(crate::emulation::EmulationError::ValueConversion {
source_type: value.type_name(),
target_type: "u64",
}),
}
}
}
impl TryFrom<EmValue> for u64 {
type Error = crate::emulation::EmulationError;
fn try_from(value: EmValue) -> Result<Self, Self::Error> {
u64::try_from(&value)
}
}
impl TryFrom<&EmValue> for f32 {
type Error = crate::emulation::EmulationError;
fn try_from(value: &EmValue) -> Result<Self, Self::Error> {
match value {
EmValue::F32(v) => Ok(*v),
_ => Err(crate::emulation::EmulationError::ValueConversion {
source_type: value.type_name(),
target_type: "f32",
}),
}
}
}
impl TryFrom<EmValue> for f32 {
type Error = crate::emulation::EmulationError;
fn try_from(value: EmValue) -> Result<Self, Self::Error> {
f32::try_from(&value)
}
}
impl TryFrom<&EmValue> for f64 {
type Error = crate::emulation::EmulationError;
fn try_from(value: &EmValue) -> Result<Self, Self::Error> {
match value {
EmValue::F64(v) => Ok(*v),
EmValue::F32(v) => Ok(f64::from(*v)),
_ => Err(crate::emulation::EmulationError::ValueConversion {
source_type: value.type_name(),
target_type: "f64",
}),
}
}
}
impl TryFrom<EmValue> for f64 {
type Error = crate::emulation::EmulationError;
fn try_from(value: EmValue) -> Result<Self, Self::Error> {
f64::try_from(&value)
}
}
impl TryFrom<&EmValue> for bool {
type Error = crate::emulation::EmulationError;
fn try_from(value: &EmValue) -> Result<Self, Self::Error> {
match value {
EmValue::Bool(v) => Ok(*v),
EmValue::I32(v) => Ok(*v != 0),
EmValue::I64(v) => Ok(*v != 0),
_ => Err(crate::emulation::EmulationError::ValueConversion {
source_type: value.type_name(),
target_type: "bool",
}),
}
}
}
impl TryFrom<EmValue> for bool {
type Error = crate::emulation::EmulationError;
fn try_from(value: EmValue) -> Result<Self, Self::Error> {
bool::try_from(&value)
}
}
impl TryFrom<&EmValue> for char {
type Error = crate::emulation::EmulationError;
#[allow(clippy::cast_sign_loss)] fn try_from(value: &EmValue) -> Result<Self, Self::Error> {
match value {
EmValue::Char(v) => Ok(*v),
EmValue::I32(v) => {
char::from_u32(*v as u32).ok_or(crate::emulation::EmulationError::ValueConversion {
source_type: "I32",
target_type: "char",
})
}
_ => Err(crate::emulation::EmulationError::ValueConversion {
source_type: value.type_name(),
target_type: "char",
}),
}
}
}
impl TryFrom<EmValue> for char {
type Error = crate::emulation::EmulationError;
fn try_from(value: EmValue) -> Result<Self, Self::Error> {
char::try_from(&value)
}
}
impl TryFrom<&EmValue> for HeapRef {
type Error = crate::emulation::EmulationError;
fn try_from(value: &EmValue) -> Result<Self, Self::Error> {
match value {
EmValue::ObjectRef(r) => Ok(*r),
_ => Err(crate::emulation::EmulationError::ValueConversion {
source_type: value.type_name(),
target_type: "HeapRef",
}),
}
}
}
impl TryFrom<EmValue> for HeapRef {
type Error = crate::emulation::EmulationError;
fn try_from(value: EmValue) -> Result<Self, Self::Error> {
HeapRef::try_from(&value)
}
}
impl TryFrom<&EmValue> for usize {
type Error = crate::emulation::EmulationError;
fn try_from(value: &EmValue) -> Result<Self, Self::Error> {
match value {
EmValue::I32(v) => usize::try_from(*v).map_err(|_| EmulationError::ValueConversion {
source_type: "I32 (negative)",
target_type: "usize",
}),
EmValue::I64(v) | EmValue::NativeInt(v) => {
usize::try_from(*v).map_err(|_| EmulationError::ValueConversion {
source_type: "I64 (negative or overflow)",
target_type: "usize",
})
}
EmValue::Bool(v) => Ok(usize::from(*v)),
EmValue::Char(v) => Ok(*v as usize),
_ => Err(EmulationError::ValueConversion {
source_type: value.type_name(),
target_type: "usize",
}),
}
}
}
impl TryFrom<EmValue> for usize {
type Error = EmulationError;
fn try_from(value: EmValue) -> Result<Self, Self::Error> {
usize::try_from(&value)
}
}
impl EmValue {
#[must_use]
pub fn type_name(&self) -> &'static str {
match self {
EmValue::Void => "Void",
EmValue::I32(_) => "I32",
EmValue::I64(_) => "I64",
EmValue::F32(_) => "F32",
EmValue::F64(_) => "F64",
EmValue::NativeInt(_) => "NativeInt",
EmValue::NativeUInt(_) => "NativeUInt",
EmValue::Bool(_) => "Bool",
EmValue::Char(_) => "Char",
EmValue::ObjectRef(_) => "ObjectRef",
EmValue::Null => "Null",
EmValue::ManagedPtr(_) => "ManagedPtr",
EmValue::UnmanagedPtr(_) => "UnmanagedPtr",
EmValue::ValueType { .. } => "ValueType",
EmValue::TypedRef { .. } => "TypedRef",
EmValue::Symbolic(_) => "Symbolic",
}
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn to_i8_cil(&self) -> i8 {
self.to_i64_cil() as i8
}
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub fn to_u8_cil(&self) -> u8 {
self.to_i64_cil() as u8
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn to_i16_cil(&self) -> i16 {
self.to_i64_cil() as i16
}
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub fn to_u16_cil(&self) -> u16 {
self.to_i64_cil() as u16
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn to_i32_cil(&self) -> i32 {
self.to_i64_cil() as i32
}
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub fn to_u32_cil(&self) -> u32 {
self.to_i64_cil() as u32
}
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
pub fn to_i64_cil(&self) -> i64 {
match self {
EmValue::I32(n) => i64::from(*n),
EmValue::I64(n) | EmValue::NativeInt(n) => *n,
EmValue::NativeUInt(n) | EmValue::UnmanagedPtr(n) => *n as i64,
EmValue::F32(f) => *f as i64,
EmValue::F64(f) => *f as i64,
EmValue::Bool(b) => i64::from(*b),
EmValue::Char(c) => i64::from(*c as u32),
_ => 0,
}
}
#[must_use]
pub fn try_to_i64(&self) -> Option<i64> {
match self {
EmValue::I32(n) => Some(i64::from(*n)),
EmValue::I64(n) | EmValue::NativeInt(n) => Some(*n),
EmValue::NativeUInt(n) => Some((*n).cast_signed()),
EmValue::Bool(b) => Some(i64::from(*b)),
EmValue::Char(c) => Some(i64::from(*c as u32)),
_ => None,
}
}
#[must_use]
#[allow(clippy::cast_sign_loss)]
pub fn to_u64_cil(&self) -> u64 {
self.to_i64_cil() as u64
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn to_f32_cil(&self) -> f32 {
self.to_f64_cil() as f32
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn to_f64_cil(&self) -> f64 {
match self {
EmValue::F64(f) => *f,
EmValue::F32(f) => f64::from(*f),
EmValue::I32(n) => f64::from(*n),
EmValue::I64(n) | EmValue::NativeInt(n) => *n as f64,
EmValue::NativeUInt(n) | EmValue::UnmanagedPtr(n) => *n as f64,
EmValue::Bool(b) => f64::from(u8::from(*b)),
EmValue::Char(c) => f64::from(*c as u32),
_ => 0.0,
}
}
#[must_use]
pub fn to_bool_cil(&self) -> bool {
match self {
EmValue::Bool(b) => *b,
EmValue::I32(n) => *n != 0,
EmValue::I64(n) | EmValue::NativeInt(n) => *n != 0,
EmValue::NativeUInt(n) | EmValue::UnmanagedPtr(n) => *n != 0,
EmValue::F32(f) => *f != 0.0,
EmValue::F64(f) => *f != 0.0,
_ => false,
}
}
#[must_use]
pub fn clr_equals(&self, other: &EmValue) -> bool {
match (self, other) {
(EmValue::Null, EmValue::Null) => true,
(EmValue::ObjectRef(a), EmValue::ObjectRef(b)) => a.id() == b.id(),
(EmValue::I32(a), EmValue::I32(b)) => a == b,
(EmValue::I64(a), EmValue::I64(b)) => a == b,
(EmValue::F32(a), EmValue::F32(b)) => a.to_bits() == b.to_bits(),
(EmValue::F64(a), EmValue::F64(b)) => a.to_bits() == b.to_bits(),
(EmValue::NativeInt(a), EmValue::NativeInt(b)) => a == b,
(EmValue::NativeUInt(a), EmValue::NativeUInt(b)) => a == b,
(EmValue::Bool(a), EmValue::Bool(b)) => a == b,
(EmValue::Char(a), EmValue::Char(b)) => a == b,
(EmValue::UnmanagedPtr(a), EmValue::UnmanagedPtr(b)) => a == b,
(EmValue::I32(a), EmValue::I64(b)) | (EmValue::I32(a), EmValue::NativeInt(b)) => {
i64::from(*a) == *b
}
(EmValue::I64(a), EmValue::I32(b)) | (EmValue::NativeInt(a), EmValue::I32(b)) => {
*a == i64::from(*b)
}
(EmValue::I64(a), EmValue::NativeInt(b)) | (EmValue::NativeInt(a), EmValue::I64(b)) => {
a == b
}
(
EmValue::ValueType {
type_token: t1,
fields: f1,
},
EmValue::ValueType {
type_token: t2,
fields: f2,
},
) => {
t1 == t2 && f1.len() == f2.len() && f1.iter().zip(f2).all(|(a, b)| a.clr_equals(b))
}
(EmValue::ManagedPtr(a), EmValue::ManagedPtr(b)) => a == b,
_ => false,
}
}
#[allow(clippy::match_same_arms)]
pub fn as_size(&self) -> Result<usize, EmulationError> {
match self {
EmValue::I32(n) => {
if *n < 0 {
return Err(EmulationError::InvalidOperand {
instruction: "memory operation",
expected: "non-negative size",
});
}
#[allow(clippy::cast_sign_loss)]
let result = *n as usize;
Ok(result)
}
EmValue::I64(n) => {
if *n < 0 {
return Err(EmulationError::InvalidOperand {
instruction: "memory operation",
expected: "non-negative size",
});
}
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let result = *n as usize;
Ok(result)
}
EmValue::NativeInt(n) => {
if *n < 0 {
return Err(EmulationError::InvalidOperand {
instruction: "memory operation",
expected: "non-negative size",
});
}
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let result = *n as usize;
Ok(result)
}
#[allow(clippy::cast_possible_truncation)]
EmValue::NativeUInt(n) => Ok(*n as usize),
_ => Err(EmulationError::TypeMismatch {
operation: "size extraction",
expected: "integer",
found: self.type_name(),
}),
}
}
pub fn as_pointer_address(&self) -> Result<u64, EmulationError> {
match self {
EmValue::UnmanagedPtr(addr) => Ok(*addr),
EmValue::NativeInt(n) | EmValue::I64(n) => Ok((*n).cast_unsigned()),
EmValue::NativeUInt(n) => Ok(*n),
EmValue::I32(n) => Ok(u64::from((*n).cast_unsigned())),
EmValue::ManagedPtr(_) => Err(EmulationError::TypeMismatch {
operation: "unmanaged memory access",
expected: "unmanaged pointer",
found: "managed pointer",
}),
_ => Err(EmulationError::TypeMismatch {
operation: "pointer extraction",
expected: "pointer or integer",
found: self.type_name(),
}),
}
}
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub fn to_le_bytes(&self, flavor: &CilFlavor, ptr_size: PointerSize) -> Vec<u8> {
match (self, flavor) {
(EmValue::I32(v), CilFlavor::I1 | CilFlavor::U1 | CilFlavor::Boolean) => {
vec![*v as u8]
}
(EmValue::I32(v), CilFlavor::I2 | CilFlavor::U2 | CilFlavor::Char) => {
(*v as i16).to_le_bytes().to_vec()
}
(EmValue::I32(v), _) => v.to_le_bytes().to_vec(),
(EmValue::I64(v), _) => v.to_le_bytes().to_vec(),
(EmValue::F32(v), _) => v.to_le_bytes().to_vec(),
(EmValue::F64(v), _) => v.to_le_bytes().to_vec(),
(EmValue::NativeInt(v), _) => match ptr_size {
PointerSize::Bit32 => (*v as i32).to_le_bytes().to_vec(),
PointerSize::Bit64 => v.to_le_bytes().to_vec(),
},
(EmValue::NativeUInt(v), _) => match ptr_size {
PointerSize::Bit32 => (*v as u32).to_le_bytes().to_vec(),
PointerSize::Bit64 => v.to_le_bytes().to_vec(),
},
(EmValue::Bool(v), _) => vec![u8::from(*v)],
(EmValue::Char(v), _) => (*v as u16).to_le_bytes().to_vec(),
_ => vec![0; flavor.element_size(ptr_size).unwrap_or(ptr_size.bytes())],
}
}
#[must_use]
pub fn from_le_bytes(bytes: &[u8], flavor: &CilFlavor, ptr_size: PointerSize) -> EmValue {
match flavor {
CilFlavor::I1 | CilFlavor::U1 | CilFlavor::Boolean => {
EmValue::I32(i32::from(bytes.first().copied().unwrap_or(0)))
}
CilFlavor::I2 | CilFlavor::U2 | CilFlavor::Char => {
let arr: [u8; 2] = bytes[..2.min(bytes.len())].try_into().unwrap_or([0, 0]);
EmValue::I32(i32::from(i16::from_le_bytes(arr)))
}
CilFlavor::I4 | CilFlavor::U4 | CilFlavor::R4 => {
let arr: [u8; 4] = bytes[..4.min(bytes.len())]
.try_into()
.unwrap_or([0, 0, 0, 0]);
if matches!(flavor, CilFlavor::R4) {
EmValue::F32(f32::from_le_bytes(arr))
} else {
EmValue::I32(i32::from_le_bytes(arr))
}
}
CilFlavor::I8 | CilFlavor::U8 | CilFlavor::R8 => {
let arr: [u8; 8] = bytes[..8.min(bytes.len())]
.try_into()
.unwrap_or([0, 0, 0, 0, 0, 0, 0, 0]);
if matches!(flavor, CilFlavor::R8) {
EmValue::F64(f64::from_le_bytes(arr))
} else {
EmValue::I64(i64::from_le_bytes(arr))
}
}
CilFlavor::I | CilFlavor::U => match ptr_size {
PointerSize::Bit32 => {
let arr: [u8; 4] = bytes[..4.min(bytes.len())]
.try_into()
.unwrap_or([0, 0, 0, 0]);
if matches!(flavor, CilFlavor::I) {
EmValue::NativeInt(i64::from(i32::from_le_bytes(arr)))
} else {
EmValue::NativeUInt(u64::from(u32::from_le_bytes(arr)))
}
}
PointerSize::Bit64 => {
let arr: [u8; 8] = bytes[..8.min(bytes.len())]
.try_into()
.unwrap_or([0, 0, 0, 0, 0, 0, 0, 0]);
if matches!(flavor, CilFlavor::I) {
EmValue::NativeInt(i64::from_le_bytes(arr))
} else {
EmValue::NativeUInt(u64::from_le_bytes(arr))
}
}
},
_ => EmValue::I32(0),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct HeapRef(pub(crate) u64);
impl HeapRef {
#[must_use]
pub fn new(id: u64) -> Self {
HeapRef(id)
}
#[must_use]
pub fn id(&self) -> u64 {
self.0
}
}
impl fmt::Display for HeapRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "HeapRef({})", self.0)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ManagedPointer {
pub target: PointerTarget,
pub offset: u32,
}
impl ManagedPointer {
#[must_use]
pub fn to_local(index: u16) -> Self {
ManagedPointer {
target: PointerTarget::Local(index),
offset: 0,
}
}
#[must_use]
pub fn to_argument(index: u16) -> Self {
ManagedPointer {
target: PointerTarget::Argument(index),
offset: 0,
}
}
#[must_use]
pub fn to_array_element(array: HeapRef, index: usize) -> Self {
ManagedPointer {
target: PointerTarget::ArrayElement { array, index },
offset: 0,
}
}
#[must_use]
pub fn to_object_field(object: HeapRef, field: Token) -> Self {
ManagedPointer {
target: PointerTarget::ObjectField { object, field },
offset: 0,
}
}
#[must_use]
pub fn to_static_field(field: Token) -> Self {
ManagedPointer {
target: PointerTarget::StaticField(field),
offset: 0,
}
}
#[must_use]
pub fn with_offset(mut self, additional_offset: u32) -> Self {
self.offset = self.offset.saturating_add(additional_offset);
self
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum PointerTarget {
Local(u16),
Argument(u16),
ArrayElement {
array: HeapRef,
index: usize,
},
ObjectField {
object: HeapRef,
field: Token,
},
StaticField(Token),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_emvalue_cil_flavors() {
assert_eq!(EmValue::I32(0).cil_flavor(), CilFlavor::I4);
assert_eq!(EmValue::Bool(true).cil_flavor(), CilFlavor::Boolean);
assert_eq!(EmValue::Char('A').cil_flavor(), CilFlavor::Char);
assert_eq!(EmValue::I64(0).cil_flavor(), CilFlavor::I8);
assert_eq!(EmValue::F32(0.0).cil_flavor(), CilFlavor::R4);
assert_eq!(EmValue::F64(0.0).cil_flavor(), CilFlavor::R8);
assert_eq!(EmValue::NativeInt(0).cil_flavor(), CilFlavor::I);
assert_eq!(EmValue::NativeUInt(0).cil_flavor(), CilFlavor::U);
assert_eq!(
EmValue::ObjectRef(HeapRef::new(1)).cil_flavor(),
CilFlavor::Object
);
assert_eq!(EmValue::Null.cil_flavor(), CilFlavor::Object);
assert_eq!(EmValue::UnmanagedPtr(0).cil_flavor(), CilFlavor::Pointer);
}
#[test]
fn test_emvalue_as_i32() {
assert_eq!(EmValue::I32(42).as_i32(), Some(42));
assert_eq!(EmValue::I32(-1).as_i32(), Some(-1));
assert_eq!(EmValue::Bool(true).as_i32(), Some(1));
assert_eq!(EmValue::Bool(false).as_i32(), Some(0));
assert_eq!(EmValue::Char('A').as_i32(), Some(65));
assert_eq!(EmValue::I64(100).as_i32(), None);
assert_eq!(EmValue::Null.as_i32(), None);
}
#[test]
fn test_emvalue_as_bool() {
assert_eq!(EmValue::Bool(true).as_bool(), Some(true));
assert_eq!(EmValue::Bool(false).as_bool(), Some(false));
assert_eq!(EmValue::I32(1).as_bool(), Some(true));
assert_eq!(EmValue::I32(0).as_bool(), Some(false));
assert_eq!(EmValue::I32(-1).as_bool(), Some(true));
assert_eq!(EmValue::I32(42).as_bool(), Some(true));
}
#[test]
fn test_emvalue_equality() {
assert_eq!(EmValue::I32(42), EmValue::I32(42));
assert_ne!(EmValue::I32(42), EmValue::I32(43));
assert_eq!(EmValue::Null, EmValue::Null);
assert_eq!(EmValue::Void, EmValue::Void);
assert_ne!(EmValue::Null, EmValue::Void);
assert_eq!(EmValue::F64(0.0), EmValue::F64(0.0));
assert_eq!(EmValue::F64(f64::NAN), EmValue::F64(f64::NAN));
assert_ne!(EmValue::F64(0.0), EmValue::F64(-0.0));
}
#[test]
fn test_emvalue_is_predicates() {
assert!(EmValue::Void.is_void());
assert!(!EmValue::Null.is_void());
assert!(EmValue::Null.is_null());
assert!(!EmValue::I32(0).is_null());
assert!(EmValue::I32(42).is_concrete());
assert!(EmValue::Null.is_concrete());
assert!(EmValue::Null.is_reference());
assert!(EmValue::ObjectRef(HeapRef::new(1)).is_reference());
assert!(!EmValue::I32(0).is_reference());
}
#[test]
fn test_emvalue_default_for_flavor() {
assert_eq!(EmValue::default_for_flavor(&CilFlavor::I4), EmValue::I32(0));
assert_eq!(EmValue::default_for_flavor(&CilFlavor::I8), EmValue::I64(0));
assert_eq!(
EmValue::default_for_flavor(&CilFlavor::R4),
EmValue::F32(0.0)
);
assert_eq!(
EmValue::default_for_flavor(&CilFlavor::R8),
EmValue::F64(0.0)
);
assert_eq!(
EmValue::default_for_flavor(&CilFlavor::Object),
EmValue::Null
);
assert_eq!(
EmValue::default_for_flavor(&CilFlavor::Boolean),
EmValue::Bool(false)
);
assert_eq!(
EmValue::default_for_flavor(&CilFlavor::Char),
EmValue::Char('\0')
);
}
#[test]
fn test_heap_ref() {
let r1 = HeapRef::new(42);
let r2 = HeapRef::new(42);
let r3 = HeapRef::new(43);
assert_eq!(r1, r2);
assert_ne!(r1, r3);
assert_eq!(r1.id(), 42);
}
#[test]
fn test_managed_pointer_creation() {
let p1 = ManagedPointer::to_local(0);
assert_eq!(p1.target, PointerTarget::Local(0));
assert_eq!(p1.offset, 0);
let p2 = ManagedPointer::to_argument(1);
assert_eq!(p2.target, PointerTarget::Argument(1));
let p3 = ManagedPointer::to_array_element(HeapRef::new(1), 5);
assert!(matches!(
p3.target,
PointerTarget::ArrayElement { index: 5, .. }
));
let p4 = p1.with_offset(8);
assert_eq!(p4.offset, 8);
}
#[test]
fn test_emvalue_display() {
assert_eq!(format!("{}", EmValue::I32(42)), "42");
assert_eq!(format!("{}", EmValue::I64(42)), "42L");
assert_eq!(format!("{}", EmValue::Bool(true)), "true");
assert_eq!(format!("{}", EmValue::Null), "null");
assert_eq!(format!("{}", EmValue::Void), "void");
assert_eq!(format!("{}", EmValue::Char('A')), "'A'");
}
#[test]
fn test_from_primitives() {
assert_eq!(EmValue::from(42_i32), EmValue::I32(42));
assert_eq!(EmValue::from(42_i64), EmValue::I64(42));
assert_eq!(
EmValue::from(std::f32::consts::PI),
EmValue::F32(std::f32::consts::PI)
);
assert_eq!(
EmValue::from(std::f64::consts::PI),
EmValue::F64(std::f64::consts::PI)
);
assert_eq!(EmValue::from(true), EmValue::Bool(true));
assert_eq!(EmValue::from('A'), EmValue::Char('A'));
assert_eq!(EmValue::from(255_u8), EmValue::I32(255));
assert_eq!(EmValue::from(-128_i8), EmValue::I32(-128));
assert_eq!(EmValue::from(1000_u16), EmValue::I32(1000));
assert_eq!(EmValue::from(-1000_i16), EmValue::I32(-1000));
}
#[test]
fn test_try_from_emvalue_i32() {
assert_eq!(i32::try_from(&EmValue::I32(42)), Ok(42));
assert_eq!(i32::try_from(&EmValue::Bool(true)), Ok(1));
assert_eq!(i32::try_from(&EmValue::Bool(false)), Ok(0));
assert_eq!(i32::try_from(&EmValue::Char('A')), Ok(65));
assert!(i32::try_from(&EmValue::I64(100)).is_err());
assert!(i32::try_from(&EmValue::Null).is_err());
}
#[test]
fn test_try_from_emvalue_i64() {
assert_eq!(i64::try_from(&EmValue::I64(42)), Ok(42));
assert_eq!(i64::try_from(&EmValue::I32(42)), Ok(42));
assert_eq!(i64::try_from(&EmValue::Bool(true)), Ok(1));
assert_eq!(i64::try_from(&EmValue::Char('A')), Ok(65));
assert_eq!(i64::try_from(&EmValue::NativeInt(100)), Ok(100));
assert!(i64::try_from(&EmValue::Null).is_err());
}
#[test]
fn test_try_from_emvalue_bool() {
assert_eq!(bool::try_from(&EmValue::Bool(true)), Ok(true));
assert_eq!(bool::try_from(&EmValue::Bool(false)), Ok(false));
assert_eq!(bool::try_from(&EmValue::I32(0)), Ok(false));
assert_eq!(bool::try_from(&EmValue::I32(1)), Ok(true));
assert_eq!(bool::try_from(&EmValue::I32(-1)), Ok(true));
assert_eq!(bool::try_from(&EmValue::I64(0)), Ok(false));
assert!(bool::try_from(&EmValue::Null).is_err());
}
#[test]
fn test_try_from_emvalue_char() {
assert_eq!(char::try_from(&EmValue::Char('A')), Ok('A'));
assert_eq!(char::try_from(&EmValue::I32(65)), Ok('A'));
assert!(char::try_from(&EmValue::I32(-1)).is_err());
assert!(char::try_from(&EmValue::Null).is_err());
}
#[test]
fn test_try_from_emvalue_heapref() {
let r = HeapRef::new(42);
assert_eq!(HeapRef::try_from(&EmValue::ObjectRef(r)), Ok(r));
assert!(HeapRef::try_from(&EmValue::Null).is_err());
assert!(HeapRef::try_from(&EmValue::I32(42)).is_err());
}
#[test]
fn test_to_i64_cil() {
assert_eq!(EmValue::I32(42).to_i64_cil(), 42);
assert_eq!(EmValue::I64(100).to_i64_cil(), 100);
assert_eq!(EmValue::F32(3.9).to_i64_cil(), 3);
assert_eq!(EmValue::F64(3.9).to_i64_cil(), 3);
assert_eq!(EmValue::Bool(true).to_i64_cil(), 1);
assert_eq!(EmValue::Bool(false).to_i64_cil(), 0);
assert_eq!(EmValue::Null.to_i64_cil(), 0);
}
#[test]
fn test_to_f64_cil() {
assert!((EmValue::I32(42).to_f64_cil() - 42.0).abs() < f64::EPSILON);
assert!((EmValue::F32(3.5).to_f64_cil() - 3.5).abs() < 0.001);
assert!(
(EmValue::F64(std::f64::consts::PI).to_f64_cil() - std::f64::consts::PI).abs()
< f64::EPSILON
);
assert!((EmValue::Bool(true).to_f64_cil() - 1.0).abs() < f64::EPSILON);
assert!((EmValue::Null.to_f64_cil() - 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_type_name() {
assert_eq!(EmValue::I32(0).type_name(), "I32");
assert_eq!(EmValue::I64(0).type_name(), "I64");
assert_eq!(EmValue::F32(0.0).type_name(), "F32");
assert_eq!(EmValue::F64(0.0).type_name(), "F64");
assert_eq!(EmValue::Bool(false).type_name(), "Bool");
assert_eq!(EmValue::Char('a').type_name(), "Char");
assert_eq!(EmValue::Null.type_name(), "Null");
assert_eq!(EmValue::Void.type_name(), "Void");
}
#[test]
fn test_as_size_i32() {
assert_eq!(EmValue::I32(100).as_size().unwrap(), 100);
assert_eq!(EmValue::I32(0).as_size().unwrap(), 0);
}
#[test]
fn test_as_size_i64() {
assert_eq!(EmValue::I64(200).as_size().unwrap(), 200);
}
#[test]
fn test_as_size_native() {
assert_eq!(EmValue::NativeInt(300).as_size().unwrap(), 300);
assert_eq!(EmValue::NativeUInt(400).as_size().unwrap(), 400);
}
#[test]
fn test_as_size_negative_fails() {
assert!(EmValue::I32(-1).as_size().is_err());
assert!(EmValue::I64(-100).as_size().is_err());
assert!(EmValue::NativeInt(-50).as_size().is_err());
}
#[test]
fn test_as_size_wrong_type_fails() {
assert!(EmValue::Null.as_size().is_err());
assert!(EmValue::F64(1.0).as_size().is_err());
}
#[test]
fn test_as_pointer_address_unmanaged() {
assert_eq!(
EmValue::UnmanagedPtr(0x1000).as_pointer_address().unwrap(),
0x1000
);
}
#[test]
fn test_as_pointer_address_native_int() {
assert_eq!(
EmValue::NativeInt(0x2000).as_pointer_address().unwrap(),
0x2000
);
assert_eq!(
EmValue::NativeUInt(0x3000).as_pointer_address().unwrap(),
0x3000
);
}
#[test]
fn test_as_pointer_address_i32_i64() {
assert_eq!(EmValue::I32(0x100).as_pointer_address().unwrap(), 0x100);
assert_eq!(EmValue::I64(0x200).as_pointer_address().unwrap(), 0x200);
}
#[test]
fn test_as_pointer_address_managed_fails() {
let ptr = ManagedPointer::to_local(0);
assert!(EmValue::ManagedPtr(ptr).as_pointer_address().is_err());
}
#[test]
fn test_as_pointer_address_wrong_type_fails() {
assert!(EmValue::Null.as_pointer_address().is_err());
assert!(EmValue::F64(1.0).as_pointer_address().is_err());
}
}