use dotnetdll::prelude::*;
#[macro_use]
mod common;
#[test]
pub fn read() {
common::read_fixture(
"exceptions",
r#"
.class public Program extends [mscorlib]System.Object {
.method public static void Main() {
.try {
nop
leave afterCatchFinally
} catch [mscorlib]System.Exception {
pop
leave afterCatchFinally
} finally {
endfinally
}
afterCatchFinally:
.try {
nop
leave afterFilterFault
} filter {
pop
ldc.i4.1
endfilter
} {
pop
leave afterFilterFault
} fault {
endfault
}
afterFilterFault:
ret
}
}
"#,
|res| {
let main = res.type_definitions[1].methods[0].body.as_ref().unwrap();
let exceptions = match &main.data_sections[0] {
body::DataSection::ExceptionHandlers(e) => e,
rest => panic!("bad data section {:?}, expected exception handlers", rest),
};
assert_inner_eq!(exceptions[0], {
kind => body::ExceptionKind::TypedException(MethodType::Base(ref b)) if matches!(&**b,
BaseType::Type { source: TypeSource::User(u), .. } if u.type_name(&res) == "System.Exception"
),
try_offset: 0,
try_length: 2,
handler_offset: 2,
handler_length: 2
});
assert_inner_eq!(exceptions[1], {
kind => body::ExceptionKind::Finally,
try_offset: 0,
try_length: 2,
handler_offset => h if main.instructions[h] == Instruction::EndFinally,
handler_length: 1
});
assert_inner_eq!(exceptions[2], {
kind => body::ExceptionKind::Filter { offset } if main.instructions[offset + 2] == Instruction::EndFilter,
try_offset: 5,
try_length: 2
});
assert_inner_eq!(exceptions[3], {
kind => body::ExceptionKind::Fault,
try_offset: 5,
try_length: 2,
handler_offset => h if main.instructions[h] == Instruction::EndFinally
});
},
)
.unwrap();
}
#[test]
pub fn write() {
common::write_fixture(
"exceptions",
|ctx| {
let mscorlib = ctx.mscorlib;
let console: MethodType = BaseType::class(ctx.console).into();
let exception = ctx
.resolution
.push_type_reference(type_ref! { System.Exception in #mscorlib });
let my_exception = ctx
.resolution
.push_type_definition(TypeDefinition::new(None, "MyException"));
let my_exception_t: MethodType = BaseType::class(my_exception).into();
ctx.resolution[my_exception].set_extends(exception);
let code = ctx.resolution.push_field(
my_exception,
Field::new(false, Accessibility::Public, "code", ctype! { int }),
);
let object_ctor = ctx.ctor_cache.get_object_ctor(&mut ctx.resolution);
let my_exception_ctor = ctx.resolution.push_method(
my_exception,
Method::constructor(
Accessibility::Public,
vec![Parameter::value(ctype! { int })],
Some(body::Method::new(asm! {
LoadArgument 0;
call object_ctor;
LoadArgument 0;
LoadArgument 1;
store_field code;
Return;
})),
),
);
let write_str = ctx.resolution.push_method_reference(method_ref! { static void @console::WriteLine(string) });
let (
instructions,
try_start,
filter_start,
handler_start,
finally_start,
ret
) = asm! {
+try_start
load_string "before throw";
call write_str;
LoadConstantInt32 1;
new_object my_exception_ctor;
Throw;
load_string "after throw";
call write_str;
Leave ret;
+filter_start
Duplicate;
IsInstance my_exception_t.clone();
BranchFalsy pop;
cast_class my_exception_t.clone();
load_field code;
Branch end_filter;
@pop
Pop;
LoadConstantInt32 0; @end_filter
EndFilter;
+handler_start
StoreLocal 0;
load_string "error code {0}";
LoadLocal 0;
cast_class my_exception_t;
load_field code;
BoxValue ctype! { int };
call ctx.resolution.push_method_reference(method_ref! { static void #console::WriteLine(string, object) });
Leave ret;
+finally_start
load_string "finally";
call write_str;
EndFinally;
+ret
Return;
};
common::MainMethod::WithExceptions {
exceptions: vec![
body::Exception {
kind: body::ExceptionKind::Filter { offset: filter_start },
try_offset: try_start,
try_length: filter_start - try_start,
handler_offset: handler_start,
handler_length: finally_start - handler_start
},
body::Exception {
kind: body::ExceptionKind::Finally,
try_offset: try_start,
try_length: finally_start - try_start,
handler_offset: finally_start,
handler_length: ret - finally_start
}
],
locals: vec![LocalVariable::new(ctype! { object })],
body: instructions
}
},
b"before throw\nerror code 1\nfinally\n",
)
.unwrap();
}