use log::warn;
use crate::{
emulation::{
memory::HeapObject,
runtime::hook::{Hook, HookContext, HookManager, PreHookResult},
thread::EmulationThread,
tokens, EmValue,
},
metadata::typesystem::CilFlavor,
Result,
};
pub fn register(manager: &HookManager) -> Result<()> {
manager.register(
Hook::new("System.Collections.Generic.List..ctor")
.match_name("System.Collections.Generic", "List`1", ".ctor")
.pre(list_ctor_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.Add")
.match_name("System.Collections.Generic", "List`1", "Add")
.pre(list_add_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.Insert")
.match_name("System.Collections.Generic", "List`1", "Insert")
.pre(list_insert_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.Remove")
.match_name("System.Collections.Generic", "List`1", "Remove")
.pre(list_remove_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.RemoveAt")
.match_name("System.Collections.Generic", "List`1", "RemoveAt")
.pre(list_remove_at_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.get_Item")
.match_name("System.Collections.Generic", "List`1", "get_Item")
.pre(list_get_item_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.set_Item")
.match_name("System.Collections.Generic", "List`1", "set_Item")
.pre(list_set_item_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.get_Count")
.match_name("System.Collections.Generic", "List`1", "get_Count")
.pre(list_get_count_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.Contains")
.match_name("System.Collections.Generic", "List`1", "Contains")
.pre(list_contains_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.IndexOf")
.match_name("System.Collections.Generic", "List`1", "IndexOf")
.pre(list_index_of_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.Clear")
.match_name("System.Collections.Generic", "List`1", "Clear")
.pre(list_clear_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.ToArray")
.match_name("System.Collections.Generic", "List`1", "ToArray")
.pre(list_to_array_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.Reverse")
.match_name("System.Collections.Generic", "List`1", "Reverse")
.pre(list_reverse_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.Sort")
.match_name("System.Collections.Generic", "List`1", "Sort")
.pre(list_sort_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.AddRange")
.match_name("System.Collections.Generic", "List`1", "AddRange")
.pre(list_add_range_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.get_Capacity")
.match_name("System.Collections.Generic", "List`1", "get_Capacity")
.pre(list_get_capacity_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.set_Capacity")
.match_name("System.Collections.Generic", "List`1", "set_Capacity")
.pre(list_set_capacity_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.GetEnumerator")
.match_name("System.Collections.Generic", "List`1", "GetEnumerator")
.pre(list_get_enumerator_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.Enumerator.MoveNext")
.match_name("System.Collections.Generic", "Enumerator", "MoveNext")
.pre(enumerator_move_next_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.Enumerator`1.MoveNext")
.match_name("System.Collections.Generic", "Enumerator`1", "MoveNext")
.pre(enumerator_move_next_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.Enumerator.get_Current")
.match_name("System.Collections.Generic", "Enumerator", "get_Current")
.pre(enumerator_get_current_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.Enumerator`1.get_Current")
.match_name("System.Collections.Generic", "Enumerator`1", "get_Current")
.pre(enumerator_get_current_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.Enumerator.Dispose")
.match_name("System.Collections.Generic", "Enumerator", "Dispose")
.pre(enumerator_dispose_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.Enumerator`1.Dispose")
.match_name("System.Collections.Generic", "Enumerator`1", "Dispose")
.pre(enumerator_dispose_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.CopyTo")
.match_name("System.Collections.Generic", "List`1", "CopyTo")
.pre(list_copy_to_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.GetRange")
.match_name("System.Collections.Generic", "List`1", "GetRange")
.pre(list_get_range_pre),
)?;
manager.register(
Hook::new("System.Collections.Generic.List.AsReadOnly")
.match_name("System.Collections.Generic", "List`1", "AsReadOnly")
.pre(list_as_read_only_pre),
)?;
Ok(())
}
fn list_ctor_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(list_ref)) = ctx.this {
try_hook!(thread.heap().replace_with_list(*list_ref));
}
PreHookResult::Bypass(None)
}
fn list_add_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(list_ref)) = ctx.this {
let value = match ctx.args.first() {
Some(v) => v.clone(),
None => {
warn!("List.Add: missing value argument — possible stack misalignment");
EmValue::Null
}
};
try_hook!(thread.heap().list_add(*list_ref, value));
}
PreHookResult::Bypass(None)
}
fn list_insert_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(list_ref)) = ctx.this {
if let Some(EmValue::I32(index)) = ctx.args.first() {
let value = match ctx.args.get(1) {
Some(v) => v.clone(),
None => {
warn!("List.Insert: missing value argument — possible stack misalignment");
EmValue::Null
}
};
#[allow(clippy::cast_sign_loss)]
let idx = *index as usize;
try_hook!(thread.heap().list_insert(*list_ref, idx, value));
}
}
PreHookResult::Bypass(None)
}
fn list_remove_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(list_ref)) = ctx.this {
if let Some(needle) = ctx.args.first() {
let elements = try_hook!(thread.heap().list_to_vec(*list_ref));
for (i, elem) in elements.iter().enumerate() {
if elem.clr_equals(needle) {
try_hook!(thread.heap().list_remove(*list_ref, i));
return PreHookResult::Bypass(Some(EmValue::I32(1)));
}
}
}
}
PreHookResult::Bypass(Some(EmValue::I32(0)))
}
fn list_remove_at_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(list_ref)) = ctx.this {
if let Some(EmValue::I32(index)) = ctx.args.first() {
#[allow(clippy::cast_sign_loss)]
let idx = *index as usize;
try_hook!(thread.heap().list_remove_at(*list_ref, idx));
}
}
PreHookResult::Bypass(None)
}
fn list_get_item_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(list_ref)) = ctx.this {
if let Some(EmValue::I32(index)) = ctx.args.first() {
#[allow(clippy::cast_sign_loss)]
let idx = *index as usize;
if let Some(value) = try_hook!(thread.heap().list_get(*list_ref, idx)) {
return PreHookResult::Bypass(Some(value));
}
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn list_set_item_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(list_ref)) = ctx.this {
if let Some(EmValue::I32(index)) = ctx.args.first() {
let value = match ctx.args.get(1) {
Some(v) => v.clone(),
None => {
warn!("List.set_Item: missing value argument — possible stack misalignment");
EmValue::Null
}
};
#[allow(clippy::cast_sign_loss)]
let idx = *index as usize;
try_hook!(thread.heap().list_set(*list_ref, idx, value));
}
}
PreHookResult::Bypass(None)
}
fn list_get_count_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(list_ref)) = ctx.this {
let count = try_hook!(thread.heap().list_count(*list_ref));
#[allow(clippy::cast_possible_truncation)]
return PreHookResult::Bypass(Some(EmValue::I32(count as i32)));
}
PreHookResult::Bypass(Some(EmValue::I32(0)))
}
fn list_contains_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(list_ref)) = ctx.this {
if let Some(needle) = ctx.args.first() {
let elements = try_hook!(thread.heap().list_to_vec(*list_ref));
let found = elements.iter().any(|e| e.clr_equals(needle));
return PreHookResult::Bypass(Some(EmValue::I32(i32::from(found))));
}
}
PreHookResult::Bypass(Some(EmValue::I32(0)))
}
fn list_index_of_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(list_ref)) = ctx.this {
if let Some(needle) = ctx.args.first() {
let elements = try_hook!(thread.heap().list_to_vec(*list_ref));
for (i, elem) in elements.iter().enumerate() {
if elem.clr_equals(needle) {
#[allow(clippy::cast_possible_truncation)]
return PreHookResult::Bypass(Some(EmValue::I32(i as i32)));
}
}
}
}
PreHookResult::Bypass(Some(EmValue::I32(-1)))
}
fn list_clear_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(list_ref)) = ctx.this {
try_hook!(thread.heap().list_clear(*list_ref));
}
PreHookResult::Bypass(None)
}
fn list_to_array_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(list_ref)) = ctx.this {
let elements = try_hook!(thread.heap().list_to_vec(*list_ref));
let array_ref = try_hook!(thread
.heap()
.alloc_array_with_values(CilFlavor::Object, elements));
return PreHookResult::Bypass(Some(EmValue::ObjectRef(array_ref)));
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn list_reverse_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(list_ref)) = ctx.this {
try_hook!(thread.heap().list_reverse(*list_ref));
}
PreHookResult::Bypass(None)
}
fn list_sort_pre(_ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
PreHookResult::Bypass(None)
}
fn list_add_range_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(list_ref)) = ctx.this {
if let Some(EmValue::ObjectRef(source_ref)) = ctx.args.first() {
let source_obj = try_hook!(thread.heap().get(*source_ref));
let source_elements: Vec<EmValue> = match source_obj {
HeapObject::Array { elements, .. } => elements.clone(),
HeapObject::List { elements } => elements.clone(),
_ => Vec::new(),
};
for elem in source_elements {
try_hook!(thread.heap().list_add(*list_ref, elem));
}
}
}
PreHookResult::Bypass(None)
}
fn list_get_capacity_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(list_ref)) = ctx.this {
let count = try_hook!(thread.heap().list_count(*list_ref));
#[allow(clippy::cast_possible_truncation)]
return PreHookResult::Bypass(Some(EmValue::I32(count as i32)));
}
PreHookResult::Bypass(Some(EmValue::I32(0)))
}
fn list_set_capacity_pre(_ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
PreHookResult::Bypass(None)
}
fn list_get_enumerator_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(list_ref)) = ctx.this {
let list_field = tokens::enumerator_fields::LIST_REF;
let pos_field = tokens::enumerator_fields::POSITION;
let type_token = tokens::helpers::LIST_ENUMERATOR;
let fields = vec![(list_field, CilFlavor::Object), (pos_field, CilFlavor::I4)];
let enum_ref = try_hook!(thread
.heap_mut()
.alloc_object_with_fields(type_token, &fields));
try_hook!(thread
.heap()
.set_field(enum_ref, list_field, EmValue::ObjectRef(*list_ref)));
try_hook!(thread
.heap()
.set_field(enum_ref, pos_field, EmValue::I32(-1)));
return PreHookResult::Bypass(Some(EmValue::ObjectRef(enum_ref)));
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn enumerator_move_next_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(enum_ref)) = ctx.this {
let list_field = tokens::enumerator_fields::LIST_REF;
let pos_field = tokens::enumerator_fields::POSITION;
let list_href = try_hook!(thread.heap().get_field(*enum_ref, list_field));
let current_pos = try_hook!(thread.heap().get_field(*enum_ref, pos_field));
if let (EmValue::ObjectRef(list_ref), EmValue::I32(pos)) = (list_href, current_pos) {
let new_pos = pos + 1;
let count = try_hook!(thread.heap().list_count(list_ref));
try_hook!(thread
.heap()
.set_field(*enum_ref, pos_field, EmValue::I32(new_pos)));
#[allow(clippy::cast_sign_loss)]
let has_next = (new_pos >= 0) && (new_pos as usize) < count;
return PreHookResult::Bypass(Some(EmValue::I32(i32::from(has_next))));
}
}
PreHookResult::Bypass(Some(EmValue::I32(0)))
}
fn enumerator_get_current_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(EmValue::ObjectRef(enum_ref)) = ctx.this {
let list_field = tokens::enumerator_fields::LIST_REF;
let pos_field = tokens::enumerator_fields::POSITION;
let list_href = try_hook!(thread.heap().get_field(*enum_ref, list_field));
let current_pos = try_hook!(thread.heap().get_field(*enum_ref, pos_field));
if let (EmValue::ObjectRef(list_ref), EmValue::I32(pos)) = (list_href, current_pos) {
#[allow(clippy::cast_sign_loss)]
if pos >= 0 {
if let Some(value) = try_hook!(thread.heap().list_get(list_ref, pos as usize)) {
return PreHookResult::Bypass(Some(value));
}
}
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn enumerator_dispose_pre(_ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
PreHookResult::Bypass(None)
}
fn list_copy_to_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(list_ref)) = ctx.this {
if let Some(EmValue::ObjectRef(arr_ref)) = ctx.args.first() {
let offset = ctx
.args
.get(1)
.and_then(EmValue::as_i32)
.map_or(0, |v| v.max(0) as usize);
let elements = try_hook!(thread.heap().list_to_vec(*list_ref));
for (i, elem) in elements.into_iter().enumerate() {
try_hook!(thread.heap().set_array_element(*arr_ref, offset + i, elem));
}
}
}
PreHookResult::Bypass(None)
}
fn list_get_range_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(list_ref)) = ctx.this {
if let (Some(EmValue::I32(start)), Some(EmValue::I32(count))) =
(ctx.args.first(), ctx.args.get(1))
{
let elements = try_hook!(thread.heap().list_to_vec(*list_ref));
let start_idx = (*start).max(0) as usize;
let cnt = (*count).max(0) as usize;
let end = (start_idx + cnt).min(elements.len());
let sub = elements[start_idx.min(elements.len())..end].to_vec();
let new_list = try_hook!(thread.heap().alloc_list_with_elements(sub));
return PreHookResult::Bypass(Some(EmValue::ObjectRef(new_list)));
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn list_as_read_only_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(list_ref)) = ctx.this {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(*list_ref)));
}
PreHookResult::Bypass(Some(EmValue::Null))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
emulation::runtime::hook::HookManager,
metadata::{token::Token, typesystem::PointerSize},
test::emulation::create_test_thread,
};
fn ctx<'a>(method: &'a str, this: Option<&'a EmValue>, args: &'a [EmValue]) -> HookContext<'a> {
HookContext::new(
Token::new(0x0A000001),
"System.Collections.Generic",
"List`1",
method,
PointerSize::Bit64,
)
.with_this(this)
.with_args(args)
}
fn enum_ctx<'a>(
method: &'a str,
this: Option<&'a EmValue>,
args: &'a [EmValue],
) -> HookContext<'a> {
HookContext::new(
Token::new(0x0A000001),
"System.Collections.Generic",
"Enumerator",
method,
PointerSize::Bit64,
)
.with_this(this)
.with_args(args)
}
fn make_list(
thread: &mut crate::emulation::thread::EmulationThread,
) -> crate::emulation::HeapRef {
let obj = thread
.heap_mut()
.alloc_object(Token::new(0x02000001))
.unwrap();
let this = EmValue::ObjectRef(obj);
list_ctor_pre(&ctx(".ctor", Some(&this), &[]), thread);
obj
}
#[test]
fn test_register_hooks() {
let manager = HookManager::new();
crate::emulation::runtime::bcl::collections::list::register(&manager).unwrap();
assert_eq!(manager.len(), 27);
}
#[test]
fn test_list_ctor() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
let result = list_get_count_pre(&ctx("get_Count", Some(&this), &[]), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(0)))
));
}
#[test]
fn test_list_add_and_get_item() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
let args = [EmValue::I32(42)];
list_add_pre(&ctx("Add", Some(&this), &args), &mut thread);
let args = [EmValue::I32(0)];
let result = list_get_item_pre(&ctx("get_Item", Some(&this), &args), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(42)))
));
}
#[test]
fn test_list_insert_at_middle() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
for v in [10, 30] {
let args = [EmValue::I32(v)];
list_add_pre(&ctx("Add", Some(&this), &args), &mut thread);
}
let args = [EmValue::I32(1), EmValue::I32(20)];
list_insert_pre(&ctx("Insert", Some(&this), &args), &mut thread);
for (i, expected) in [10, 20, 30].iter().enumerate() {
let args = [EmValue::I32(i as i32)];
let result = list_get_item_pre(&ctx("get_Item", Some(&this), &args), &mut thread);
assert!(
matches!(result, PreHookResult::Bypass(Some(EmValue::I32(v))) if v == *expected)
);
}
}
#[test]
fn test_list_remove_found() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
for v in [1, 2, 3] {
let args = [EmValue::I32(v)];
list_add_pre(&ctx("Add", Some(&this), &args), &mut thread);
}
let args = [EmValue::I32(2)];
let result = list_remove_pre(&ctx("Remove", Some(&this), &args), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(1)))
));
let result = list_get_count_pre(&ctx("get_Count", Some(&this), &[]), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(2)))
));
}
#[test]
fn test_list_remove_not_found() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
let add_args = [EmValue::I32(1)];
list_add_pre(&ctx("Add", Some(&this), &add_args), &mut thread);
let args = [EmValue::I32(99)];
let result = list_remove_pre(&ctx("Remove", Some(&this), &args), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(0)))
));
}
#[test]
fn test_list_remove_at() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
for v in [10, 20, 30] {
let args = [EmValue::I32(v)];
list_add_pre(&ctx("Add", Some(&this), &args), &mut thread);
}
let args = [EmValue::I32(1)];
list_remove_at_pre(&ctx("RemoveAt", Some(&this), &args), &mut thread);
let args = [EmValue::I32(1)];
let result = list_get_item_pre(&ctx("get_Item", Some(&this), &args), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(30)))
));
}
#[test]
fn test_list_get_item_out_of_bounds() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
let args = [EmValue::I32(5)];
let result = list_get_item_pre(&ctx("get_Item", Some(&this), &args), &mut thread);
assert!(matches!(result, PreHookResult::Bypass(Some(EmValue::Null))));
}
#[test]
fn test_list_set_item() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
let args = [EmValue::I32(0)];
list_add_pre(&ctx("Add", Some(&this), &args), &mut thread);
let args = [EmValue::I32(0), EmValue::I32(99)];
list_set_item_pre(&ctx("set_Item", Some(&this), &args), &mut thread);
let args = [EmValue::I32(0)];
let result = list_get_item_pre(&ctx("get_Item", Some(&this), &args), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(99)))
));
}
#[test]
fn test_list_contains() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
let args = [EmValue::I32(42)];
list_add_pre(&ctx("Add", Some(&this), &args), &mut thread);
let args = [EmValue::I32(42)];
let result = list_contains_pre(&ctx("Contains", Some(&this), &args), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(1)))
));
let args = [EmValue::I32(99)];
let result = list_contains_pre(&ctx("Contains", Some(&this), &args), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(0)))
));
}
#[test]
fn test_list_index_of() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
for v in [10, 20, 30] {
let args = [EmValue::I32(v)];
list_add_pre(&ctx("Add", Some(&this), &args), &mut thread);
}
let args = [EmValue::I32(20)];
let result = list_index_of_pre(&ctx("IndexOf", Some(&this), &args), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(1)))
));
let args = [EmValue::I32(99)];
let result = list_index_of_pre(&ctx("IndexOf", Some(&this), &args), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(-1)))
));
}
#[test]
fn test_list_clear() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
let args = [EmValue::I32(1)];
list_add_pre(&ctx("Add", Some(&this), &args), &mut thread);
list_clear_pre(&ctx("Clear", Some(&this), &[]), &mut thread);
let result = list_get_count_pre(&ctx("get_Count", Some(&this), &[]), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(0)))
));
}
#[test]
fn test_list_to_array() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
for v in [1, 2, 3] {
let args = [EmValue::I32(v)];
list_add_pre(&ctx("Add", Some(&this), &args), &mut thread);
}
let result = list_to_array_pre(&ctx("ToArray", Some(&this), &[]), &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(arr))) = result {
assert_eq!(
thread.heap().get_array_element(arr, 0).unwrap(),
EmValue::I32(1)
);
assert_eq!(
thread.heap().get_array_element(arr, 1).unwrap(),
EmValue::I32(2)
);
assert_eq!(
thread.heap().get_array_element(arr, 2).unwrap(),
EmValue::I32(3)
);
} else {
panic!("Expected Bypass with ObjectRef");
}
}
#[test]
fn test_list_reverse() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
for v in [1, 2, 3] {
let args = [EmValue::I32(v)];
list_add_pre(&ctx("Add", Some(&this), &args), &mut thread);
}
list_reverse_pre(&ctx("Reverse", Some(&this), &[]), &mut thread);
let args = [EmValue::I32(0)];
let result = list_get_item_pre(&ctx("get_Item", Some(&this), &args), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(3)))
));
}
#[test]
fn test_list_add_range_from_array() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
let arr = thread
.heap_mut()
.alloc_array_with_values(CilFlavor::Object, vec![EmValue::I32(10), EmValue::I32(20)])
.unwrap();
let args = [EmValue::ObjectRef(arr)];
list_add_range_pre(&ctx("AddRange", Some(&this), &args), &mut thread);
let result = list_get_count_pre(&ctx("get_Count", Some(&this), &[]), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(2)))
));
}
#[test]
fn test_list_add_range_from_list() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
let source = thread
.heap_mut()
.alloc_list_with_elements(vec![EmValue::I32(1), EmValue::I32(2)])
.unwrap();
let args = [EmValue::ObjectRef(source)];
list_add_range_pre(&ctx("AddRange", Some(&this), &args), &mut thread);
let result = list_get_count_pre(&ctx("get_Count", Some(&this), &[]), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(2)))
));
}
#[test]
fn test_list_get_capacity() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
let args = [EmValue::I32(42)];
list_add_pre(&ctx("Add", Some(&this), &args), &mut thread);
let result = list_get_capacity_pre(&ctx("get_Capacity", Some(&this), &[]), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(1)))
));
}
#[test]
fn test_list_copy_to() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
for v in [10, 20] {
let args = [EmValue::I32(v)];
list_add_pre(&ctx("Add", Some(&this), &args), &mut thread);
}
let arr = thread.heap_mut().alloc_array(CilFlavor::Object, 4).unwrap();
let args = [EmValue::ObjectRef(arr), EmValue::I32(1)];
list_copy_to_pre(&ctx("CopyTo", Some(&this), &args), &mut thread);
assert_eq!(
thread.heap().get_array_element(arr, 1).unwrap(),
EmValue::I32(10)
);
assert_eq!(
thread.heap().get_array_element(arr, 2).unwrap(),
EmValue::I32(20)
);
}
#[test]
fn test_list_get_range() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
for v in [10, 20, 30, 40] {
let args = [EmValue::I32(v)];
list_add_pre(&ctx("Add", Some(&this), &args), &mut thread);
}
let args = [EmValue::I32(1), EmValue::I32(2)];
let result = list_get_range_pre(&ctx("GetRange", Some(&this), &args), &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(sub))) = result {
assert_eq!(thread.heap().list_count(sub).unwrap(), 2);
assert_eq!(
thread.heap().list_get(sub, 0).unwrap(),
Some(EmValue::I32(20))
);
assert_eq!(
thread.heap().list_get(sub, 1).unwrap(),
Some(EmValue::I32(30))
);
} else {
panic!("Expected Bypass with ObjectRef");
}
}
#[test]
fn test_list_as_read_only() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
let result = list_as_read_only_pre(&ctx("AsReadOnly", Some(&this), &[]), &mut thread);
assert!(matches!(result, PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) if r == obj));
}
#[test]
fn test_list_sort_noop() {
let mut thread = create_test_thread();
let result = list_sort_pre(&ctx("Sort", None, &[]), &mut thread);
assert!(matches!(result, PreHookResult::Bypass(None)));
}
#[test]
fn test_list_ctor_with_capacity() {
let mut thread = create_test_thread();
let obj = thread
.heap_mut()
.alloc_object(Token::new(0x02000001))
.unwrap();
let this = EmValue::ObjectRef(obj);
let args = [EmValue::I32(100)];
list_ctor_pre(&ctx(".ctor", Some(&this), &args), &mut thread);
let result = list_get_count_pre(&ctx("get_Count", Some(&this), &[]), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(0)))
));
}
#[test]
fn test_list_enumerator_iterate() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
for v in [10, 20, 30] {
let args = [EmValue::I32(v)];
list_add_pre(&ctx("Add", Some(&this), &args), &mut thread);
}
let result = list_get_enumerator_pre(&ctx("GetEnumerator", Some(&this), &[]), &mut thread);
let enum_ref = if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
r
} else {
panic!("Expected ObjectRef enumerator");
};
let enum_this = EmValue::ObjectRef(enum_ref);
let mut collected = Vec::new();
loop {
let result =
enumerator_move_next_pre(&enum_ctx("MoveNext", Some(&enum_this), &[]), &mut thread);
if let PreHookResult::Bypass(Some(EmValue::I32(has_next))) = result {
if has_next == 0 {
break;
}
let current = enumerator_get_current_pre(
&enum_ctx("get_Current", Some(&enum_this), &[]),
&mut thread,
);
if let PreHookResult::Bypass(Some(val)) = current {
collected.push(val);
}
} else {
break;
}
}
assert_eq!(
collected,
vec![EmValue::I32(10), EmValue::I32(20), EmValue::I32(30)]
);
}
#[test]
fn test_list_enumerator_move_next_returns_false_after_last() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
let args = [EmValue::I32(1)];
list_add_pre(&ctx("Add", Some(&this), &args), &mut thread);
let result = list_get_enumerator_pre(&ctx("GetEnumerator", Some(&this), &[]), &mut thread);
let enum_ref = if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
r
} else {
panic!()
};
let enum_this = EmValue::ObjectRef(enum_ref);
let result =
enumerator_move_next_pre(&enum_ctx("MoveNext", Some(&enum_this), &[]), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(1)))
));
let result =
enumerator_move_next_pre(&enum_ctx("MoveNext", Some(&enum_this), &[]), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(0)))
));
}
#[test]
fn test_list_enumerator_get_current_before_move_next() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
let args = [EmValue::I32(42)];
list_add_pre(&ctx("Add", Some(&this), &args), &mut thread);
let result = list_get_enumerator_pre(&ctx("GetEnumerator", Some(&this), &[]), &mut thread);
let enum_ref = if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
r
} else {
panic!()
};
let enum_this = EmValue::ObjectRef(enum_ref);
let result = enumerator_get_current_pre(
&enum_ctx("get_Current", Some(&enum_this), &[]),
&mut thread,
);
assert!(matches!(result, PreHookResult::Bypass(Some(EmValue::Null))));
}
#[test]
fn test_list_enumerator_get_current_after_exhaustion() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
let args = [EmValue::I32(1)];
list_add_pre(&ctx("Add", Some(&this), &args), &mut thread);
let result = list_get_enumerator_pre(&ctx("GetEnumerator", Some(&this), &[]), &mut thread);
let enum_ref = if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
r
} else {
panic!()
};
let enum_this = EmValue::ObjectRef(enum_ref);
enumerator_move_next_pre(&enum_ctx("MoveNext", Some(&enum_this), &[]), &mut thread);
enumerator_move_next_pre(&enum_ctx("MoveNext", Some(&enum_this), &[]), &mut thread);
let result = enumerator_get_current_pre(
&enum_ctx("get_Current", Some(&enum_this), &[]),
&mut thread,
);
assert!(matches!(result, PreHookResult::Bypass(Some(EmValue::Null))));
}
#[test]
fn test_list_enumerator_dispose() {
let mut thread = create_test_thread();
let result = enumerator_dispose_pre(&enum_ctx("Dispose", None, &[]), &mut thread);
assert!(matches!(result, PreHookResult::Bypass(None)));
}
#[test]
fn test_list_empty_enumeration() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
let result = list_get_enumerator_pre(&ctx("GetEnumerator", Some(&this), &[]), &mut thread);
let enum_ref = if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
r
} else {
panic!()
};
let enum_this = EmValue::ObjectRef(enum_ref);
let result =
enumerator_move_next_pre(&enum_ctx("MoveNext", Some(&enum_this), &[]), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(0)))
));
}
#[test]
fn test_list_get_enumerator_returns_object_ref() {
let mut thread = create_test_thread();
let obj = make_list(&mut thread);
let this = EmValue::ObjectRef(obj);
let result = list_get_enumerator_pre(&ctx("GetEnumerator", Some(&this), &[]), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::ObjectRef(_)))
));
}
}