use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
use crate::{
emulation::{
capture::CaptureContext,
engine::EmulationError,
exception::ThreadExceptionState,
fakeobjects::SharedFakeObjects,
memory::{HeapObject, ManagedHeap},
value::PointerTarget,
AddressSpace, ArgumentStorage, EmValue, EvaluationStack, HeapRef, LocalVariables,
ManagedPointer, ThreadId,
},
metadata::{token::Token, typesystem::CilFlavor},
CilObject, Error, Result,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum ThreadState {
#[default]
Ready,
Running,
Waiting(WaitReason),
Completed,
Faulted,
Aborted,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WaitReason {
Monitor(HeapRef),
Event(HeapRef),
Thread(ThreadId),
Sleep {
until_instruction: u64,
},
Mutex(HeapRef),
Semaphore(HeapRef),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum ThreadPriority {
Lowest = 0,
BelowNormal = 1,
#[default]
Normal = 2,
AboveNormal = 3,
Highest = 4,
}
#[derive(Clone, Debug)]
pub struct ThreadCallFrame {
method: Token,
return_method: Option<Token>,
return_offset: u32,
locals: LocalVariables,
arguments: ArgumentStorage,
instruction_offset: u32,
expects_return: bool,
caller_stack: Vec<EmValue>,
}
impl ThreadCallFrame {
#[must_use]
pub fn new(
method: Token,
return_method: Option<Token>,
return_offset: u32,
local_types: Vec<CilFlavor>,
args: Vec<(EmValue, CilFlavor)>,
expects_return: bool,
) -> Self {
let (arg_values, arg_types): (Vec<_>, Vec<_>) = args.into_iter().unzip();
Self {
method,
return_method,
return_offset,
locals: LocalVariables::new(local_types),
arguments: ArgumentStorage::new(arg_values, arg_types),
instruction_offset: 0,
expects_return,
caller_stack: Vec::new(),
}
}
#[must_use]
pub fn method(&self) -> Token {
self.method
}
#[must_use]
pub fn return_method(&self) -> Option<Token> {
self.return_method
}
#[must_use]
pub fn return_offset(&self) -> u32 {
self.return_offset
}
#[must_use]
pub fn ip(&self) -> u32 {
self.instruction_offset
}
pub fn set_ip(&mut self, offset: u32) {
self.instruction_offset = offset;
}
pub fn advance_ip(&mut self, delta: u32) {
self.instruction_offset += delta;
}
#[must_use]
pub fn expects_return(&self) -> bool {
self.expects_return
}
#[must_use]
pub fn local_count(&self) -> usize {
self.locals.count()
}
#[must_use]
pub fn get_local(&self, index: u16) -> Option<&EmValue> {
self.locals.get(index as usize).ok()
}
pub fn set_local(&mut self, index: u16, value: EmValue) -> bool {
self.locals.set(index as usize, value).is_ok()
}
#[must_use]
pub fn local_type(&self, index: u16) -> Option<&CilFlavor> {
self.locals.get_type(index as usize).ok()
}
#[must_use]
pub fn argument_count(&self) -> usize {
self.arguments.count()
}
#[must_use]
pub fn get_argument(&self, index: u16) -> Option<&EmValue> {
self.arguments.get(index as usize).ok()
}
pub fn set_argument(&mut self, index: u16, value: EmValue) -> bool {
self.arguments.set(index as usize, value).is_ok()
}
#[must_use]
pub fn argument_type(&self, index: u16) -> Option<&CilFlavor> {
self.arguments.get_type(index as usize).ok()
}
pub fn save_caller_stack(&mut self, stack: Vec<EmValue>) {
self.caller_stack = stack;
}
pub fn take_caller_stack(&mut self) -> Vec<EmValue> {
std::mem::take(&mut self.caller_stack)
}
#[must_use]
pub fn caller_stack(&self) -> &[EmValue] {
&self.caller_stack
}
#[must_use]
pub fn locals(&self) -> &LocalVariables {
&self.locals
}
pub fn locals_mut(&mut self) -> &mut LocalVariables {
&mut self.locals
}
#[must_use]
pub fn arguments(&self) -> &ArgumentStorage {
&self.arguments
}
pub fn arguments_mut(&mut self) -> &mut ArgumentStorage {
&mut self.arguments
}
}
pub struct EmulationThread {
id: ThreadId,
name: Option<String>,
priority: ThreadPriority,
state: ThreadState,
call_stack: Vec<ThreadCallFrame>,
eval_stack: EvaluationStack,
exception_state: ThreadExceptionState,
tls: HashMap<Token, EmValue>,
address_space: Arc<AddressSpace>,
capture: Arc<CaptureContext>,
assembly: Option<Arc<CilObject>>,
instructions_executed: u64,
return_value: Option<EmValue>,
pending_reflection_invoke: Option<ReflectionInvokeRequest>,
fake_objects: SharedFakeObjects,
}
#[derive(Debug, Clone)]
pub struct ReflectionInvokeRequest {
pub method_token: Token,
pub this_ref: Option<EmValue>,
pub args: Vec<EmValue>,
}
impl fmt::Debug for EmulationThread {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EmulationThread")
.field("id", &self.id)
.field("name", &self.name)
.field("priority", &self.priority)
.field("state", &self.state)
.field("call_stack", &self.call_stack)
.field("eval_stack", &self.eval_stack)
.field("exception_state", &self.exception_state)
.field("tls", &self.tls)
.field("address_space", &"...")
.field("capture", &"...")
.field("assembly", &self.assembly.as_ref().map(|_| "..."))
.field("instructions_executed", &self.instructions_executed)
.field("return_value", &self.return_value)
.field("pending_reflection_invoke", &self.pending_reflection_invoke)
.finish_non_exhaustive()
}
}
impl EmulationThread {
pub fn new(
id: ThreadId,
address_space: Arc<AddressSpace>,
capture: Arc<CaptureContext>,
assembly: Option<Arc<CilObject>>,
fake_objects: SharedFakeObjects,
) -> Self {
Self {
id,
name: None,
priority: ThreadPriority::Normal,
state: ThreadState::Ready,
call_stack: Vec::new(),
eval_stack: EvaluationStack::new(1000),
exception_state: ThreadExceptionState::new(),
tls: HashMap::new(),
address_space,
capture,
assembly,
instructions_executed: 0,
return_value: None,
pending_reflection_invoke: None,
fake_objects,
}
}
pub fn main(
address_space: Arc<AddressSpace>,
capture: Arc<CaptureContext>,
assembly: Option<Arc<CilObject>>,
fake_objects: SharedFakeObjects,
) -> Self {
let mut thread = Self::new(
ThreadId::MAIN,
address_space,
capture,
assembly,
fake_objects,
);
thread.name = Some("Main".to_string());
thread
}
#[must_use]
pub fn fake_objects(&self) -> &SharedFakeObjects {
&self.fake_objects
}
#[must_use]
pub fn capture(&self) -> &Arc<CaptureContext> {
&self.capture
}
#[must_use]
pub fn assembly(&self) -> Option<&Arc<CilObject>> {
self.assembly.as_ref()
}
#[must_use]
pub fn id(&self) -> ThreadId {
self.id
}
#[must_use]
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn set_name(&mut self, name: impl Into<String>) {
self.name = Some(name.into());
}
#[must_use]
pub fn priority(&self) -> ThreadPriority {
self.priority
}
pub fn set_priority(&mut self, priority: ThreadPriority) {
self.priority = priority;
}
#[must_use]
pub fn state(&self) -> ThreadState {
self.state
}
pub fn set_state(&mut self, state: ThreadState) {
self.state = state;
}
#[must_use]
pub fn is_ready(&self) -> bool {
matches!(self.state, ThreadState::Ready | ThreadState::Running)
}
#[must_use]
pub fn is_completed(&self) -> bool {
matches!(
self.state,
ThreadState::Completed | ThreadState::Faulted | ThreadState::Aborted
)
}
#[must_use]
pub fn current_frame(&self) -> Option<&ThreadCallFrame> {
self.call_stack.last()
}
pub fn current_frame_mut(&mut self) -> Option<&mut ThreadCallFrame> {
self.call_stack.last_mut()
}
#[must_use]
pub fn call_depth(&self) -> usize {
self.call_stack.len()
}
pub fn push_frame(&mut self, frame: ThreadCallFrame) {
self.call_stack.push(frame);
}
pub fn pop_frame(&mut self) -> Option<ThreadCallFrame> {
self.call_stack.pop()
}
#[must_use]
pub fn stack(&self) -> &EvaluationStack {
&self.eval_stack
}
pub fn stack_mut(&mut self) -> &mut EvaluationStack {
&mut self.eval_stack
}
pub fn push(&mut self, value: EmValue) -> Result<()> {
self.eval_stack.push(value)
}
pub fn pop(&mut self) -> Result<EmValue> {
self.eval_stack.pop()
}
pub fn pop_args(&mut self, count: usize) -> Result<Vec<EmValue>> {
let mut args = Vec::with_capacity(count);
for _ in 0..count {
args.push(self.eval_stack.pop()?);
}
args.reverse();
Ok(args)
}
pub fn peek_args(&self, count: usize) -> Result<Vec<EmValue>> {
let mut args = Vec::with_capacity(count);
for i in 0..count {
args.push(self.eval_stack.peek_at(count - 1 - i)?.clone());
}
Ok(args)
}
#[must_use]
pub fn exception_state(&self) -> &ThreadExceptionState {
&self.exception_state
}
pub fn exception_state_mut(&mut self) -> &mut ThreadExceptionState {
&mut self.exception_state
}
#[must_use]
pub fn address_space(&self) -> &AddressSpace {
&self.address_space
}
#[must_use]
pub fn get_tls(&self, key: Token) -> Option<&EmValue> {
self.tls.get(&key)
}
pub fn set_tls(&mut self, key: Token, value: EmValue) {
self.tls.insert(key, value);
}
#[must_use]
pub fn instructions_executed(&self) -> u64 {
self.instructions_executed
}
pub fn increment_instructions(&mut self) {
self.instructions_executed += 1;
}
#[must_use]
pub fn current_method(&self) -> Option<Token> {
self.current_frame().map(|f| f.method)
}
#[must_use]
pub fn current_offset(&self) -> Option<u32> {
self.current_frame().map(|f| f.instruction_offset)
}
pub fn set_return_value(&mut self, value: Option<EmValue>) {
self.return_value = value;
self.state = ThreadState::Completed;
}
#[must_use]
pub fn return_value(&self) -> Option<&EmValue> {
self.return_value.as_ref()
}
pub fn take_return_value(&mut self) -> Option<EmValue> {
self.return_value.take()
}
#[must_use]
pub fn pending_reflection_invoke(&self) -> Option<&ReflectionInvokeRequest> {
self.pending_reflection_invoke.as_ref()
}
pub fn take_pending_reflection_invoke(&mut self) -> Option<ReflectionInvokeRequest> {
self.pending_reflection_invoke.take()
}
pub fn set_pending_reflection_invoke(&mut self, request: ReflectionInvokeRequest) {
self.pending_reflection_invoke = Some(request);
}
pub fn fault(&mut self) {
self.state = ThreadState::Faulted;
}
pub fn abort(&mut self) {
self.state = ThreadState::Aborted;
}
pub fn start_method(
&mut self,
method: Token,
locals: Vec<CilFlavor>,
args: Vec<(EmValue, CilFlavor)>,
expects_return: bool,
) {
let frame = ThreadCallFrame::new(method, None, 0, locals, args, expects_return);
self.push_frame(frame);
self.state = ThreadState::Running;
}
pub fn get_local(&self, index: usize) -> Result<&EmValue> {
self.current_frame()
.ok_or_else(|| {
Error::from(EmulationError::InternalError {
description: "no active call frame".to_string(),
})
})?
.locals
.get(index)
}
pub fn set_local(&mut self, index: usize, value: EmValue) -> Result<()> {
self.current_frame_mut()
.ok_or_else(|| {
Error::from(EmulationError::InternalError {
description: "no active call frame".to_string(),
})
})?
.locals
.set(index, value)
}
pub fn get_arg(&self, index: usize) -> Result<&EmValue> {
self.current_frame()
.ok_or_else(|| {
Error::from(EmulationError::InternalError {
description: "no active call frame".to_string(),
})
})?
.arguments
.get(index)
}
pub fn set_arg(&mut self, index: usize, value: EmValue) -> Result<()> {
self.current_frame_mut()
.ok_or_else(|| {
Error::from(EmulationError::InternalError {
description: "no active call frame".to_string(),
})
})?
.arguments
.set(index, value)
}
#[must_use]
pub fn heap(&self) -> &ManagedHeap {
self.address_space.managed_heap()
}
#[must_use]
pub fn heap_mut(&self) -> &ManagedHeap {
self.address_space.managed_heap()
}
pub fn get_heap_object(&self, heap_ref: HeapRef) -> Result<HeapObject> {
self.address_space.managed_heap().get(heap_ref)
}
pub fn deref_pointer(&self, ptr: &ManagedPointer) -> Result<EmValue> {
match &ptr.target {
PointerTarget::Local(index) => {
let idx = usize::from(*index);
self.get_local(idx).cloned()
}
PointerTarget::Argument(index) => {
let idx = usize::from(*index);
self.get_arg(idx).cloned()
}
PointerTarget::ArrayElement { array, index } => self
.address_space
.managed_heap()
.get_array_element(*array, *index),
PointerTarget::ObjectField { object, field } => self
.address_space
.managed_heap()
.get_field(*object, *field)
.map_err(|_| {
EmulationError::TypeMismatch {
operation: "ldind",
expected: "valid field",
found: "unknown field",
}
.into()
}),
PointerTarget::StaticField(field_token) => self
.address_space
.statics()
.get(*field_token)
.ok_or_else(|| {
EmulationError::TypeMismatch {
operation: "ldind (static field)",
expected: "initialized static field",
found: "uninitialized static field",
}
.into()
}),
}
}
pub fn store_through_pointer(&mut self, ptr: &ManagedPointer, value: EmValue) -> Result<()> {
match &ptr.target {
PointerTarget::Local(index) => {
let idx = usize::from(*index);
self.set_local(idx, value)
}
PointerTarget::Argument(index) => {
let idx = usize::from(*index);
self.set_arg(idx, value)
}
PointerTarget::ArrayElement { array, index } => self
.address_space
.managed_heap()
.set_array_element(*array, *index, value),
PointerTarget::ObjectField { object, field } => self
.address_space
.managed_heap()
.set_field(*object, *field, value),
PointerTarget::StaticField(field_token) => {
self.address_space.statics().set(*field_token, value);
Ok(())
}
}
}
pub fn configure_locals(&mut self, local_types: Vec<CilFlavor>) {
if let Some(frame) = self.current_frame_mut() {
frame.locals = LocalVariables::new(local_types);
}
}
pub fn configure_arguments(&mut self, arg_values: Vec<EmValue>, arg_types: Vec<CilFlavor>) {
if let Some(frame) = self.current_frame_mut() {
frame.arguments = ArgumentStorage::new(arg_values, arg_types);
}
}
pub fn restore_stack(&mut self, values: Vec<EmValue>) {
self.eval_stack.clear();
for value in values {
let _ = self.eval_stack.push(value);
}
}
pub fn clear_stack(&mut self) {
self.eval_stack.clear();
}
pub fn take_stack(&mut self) -> Vec<EmValue> {
let values = self.eval_stack.snapshot();
self.eval_stack.clear();
values
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::emulation::create_test_thread;
#[test]
fn test_thread_creation() {
let thread = create_test_thread();
assert_eq!(thread.id(), ThreadId::MAIN);
assert_eq!(thread.state(), ThreadState::Ready);
assert!(thread.is_ready());
assert!(!thread.is_completed());
}
#[test]
fn test_thread_main() {
let space = Arc::new(AddressSpace::new());
let capture = Arc::new(CaptureContext::new());
let fake_objects = SharedFakeObjects::new(space.managed_heap());
let thread = EmulationThread::main(space, capture, None, fake_objects);
assert_eq!(thread.id(), ThreadId::MAIN);
assert_eq!(thread.name(), Some("Main"));
}
#[test]
fn test_call_stack() {
let mut thread = create_test_thread();
assert_eq!(thread.call_depth(), 0);
assert!(thread.current_frame().is_none());
thread.start_method(
Token::new(0x06000001),
vec![CilFlavor::I4],
vec![(EmValue::I32(42), CilFlavor::I4)],
true,
);
assert_eq!(thread.call_depth(), 1);
assert!(thread.current_frame().is_some());
assert_eq!(thread.current_method(), Some(Token::new(0x06000001)));
let frame = ThreadCallFrame::new(
Token::new(0x06000002),
Some(Token::new(0x06000001)), 0x10,
vec![],
vec![],
false,
);
thread.push_frame(frame);
assert_eq!(thread.call_depth(), 2);
let popped = thread.pop_frame().unwrap();
assert_eq!(popped.method(), Token::new(0x06000002));
assert_eq!(thread.call_depth(), 1);
}
#[test]
fn test_evaluation_stack() {
let mut thread = create_test_thread();
thread.push(EmValue::I32(10)).unwrap();
thread.push(EmValue::I32(20)).unwrap();
assert_eq!(thread.stack().depth(), 2);
let val = thread.pop().unwrap();
assert_eq!(val, EmValue::I32(20));
}
#[test]
fn test_thread_completion() {
let mut thread = create_test_thread();
thread.set_return_value(Some(EmValue::I32(42)));
assert!(thread.is_completed());
assert_eq!(thread.state(), ThreadState::Completed);
let ret = thread.take_return_value();
assert_eq!(ret, Some(EmValue::I32(42)));
}
#[test]
fn test_thread_priority() {
let mut thread = create_test_thread();
assert_eq!(thread.priority(), ThreadPriority::Normal);
thread.set_priority(ThreadPriority::Highest);
assert_eq!(thread.priority(), ThreadPriority::Highest);
}
#[test]
fn test_tls() {
let mut thread = create_test_thread();
let key = Token::new(0x04000001);
assert!(thread.get_tls(key).is_none());
thread.set_tls(key, EmValue::I32(100));
assert_eq!(thread.get_tls(key), Some(&EmValue::I32(100)));
}
}