mod body;
mod encode;
mod exceptions;
mod iter;
mod types;
use crossbeam_skiplist::SkipMap;
use std::sync::{atomic::AtomicU32, Arc, OnceLock, Weak};
pub use body::*;
pub use encode::encode_method_body_header;
pub use exceptions::*;
pub use iter::InstructionIterator;
pub use types::*;
use crate::{
analysis::{ControlFlowGraph, SsaConverter, SsaExceptionHandler, SsaFunction, TypeContext},
assembly::{self, BasicBlock, InstructionEncoder},
file::File,
metadata::{
customattributes::CustomAttributeValueList,
security::Security,
signatures::{
parse_local_var_signature, SignatureLocalVariable, SignatureMethod, TypeSignature,
},
streams::Blob,
tables::{GenericParamList, MetadataTable, MethodSpecList, ParamList, StandAloneSigRaw},
token::Token,
typesystem::{CilModifier, CilTypeRc, CilTypeRef, TypeRegistry, TypeResolver},
},
utils::VisitedMap,
CilObject, Result,
};
pub type MethodMap = SkipMap<Token, MethodRc>;
pub type MethodList = Arc<boxcar::Vec<MethodRc>>;
pub type MethodRefList = Arc<boxcar::Vec<MethodRef>>;
pub type MethodRc = Arc<Method>;
pub fn method_name_by_token(map: &MethodMap, token: &Token) -> Option<String> {
map.get(token).map(|e| e.value().name.clone())
}
#[derive(Clone, Debug)]
pub struct MethodRef {
weak_ref: Weak<Method>,
}
impl MethodRef {
pub fn new(strong_ref: &MethodRc) -> Self {
Self {
weak_ref: Arc::downgrade(strong_ref),
}
}
#[must_use]
pub fn upgrade(&self) -> Option<MethodRc> {
self.weak_ref.upgrade()
}
#[must_use]
pub fn expect(&self, msg: &str) -> MethodRc {
self.weak_ref.upgrade().expect(msg)
}
#[must_use]
pub fn is_valid(&self) -> bool {
self.weak_ref.strong_count() > 0
}
#[must_use]
pub fn token(&self) -> Option<Token> {
self.upgrade().map(|m| m.token)
}
#[must_use]
pub fn name(&self) -> Option<String> {
self.upgrade().map(|m| m.name.clone())
}
#[must_use]
pub fn is_constructor(&self) -> bool {
if let Some(method) = self.upgrade() {
method.is_constructor()
} else {
false
}
}
}
impl From<MethodRc> for MethodRef {
fn from(strong_ref: MethodRc) -> Self {
Self::new(&strong_ref)
}
}
pub struct Method {
pub rid: u32,
pub token: Token,
pub meta_offset: usize,
pub name: String,
pub impl_code_type: MethodImplCodeType,
pub impl_management: MethodImplManagement,
pub impl_options: MethodImplOptions,
pub flags_access: MethodAccessFlags,
pub flags_vtable: MethodVtableFlags,
pub flags_modifiers: MethodModifiers,
pub flags_pinvoke: AtomicU32,
pub params: ParamList,
pub varargs: Arc<boxcar::Vec<VarArg>>,
pub generic_params: GenericParamList,
pub generic_args: MethodSpecList,
pub signature: SignatureMethod,
pub rva: Option<u32>,
pub body: OnceLock<MethodBody>,
pub local_vars: Arc<boxcar::Vec<LocalVariable>>,
pub overrides: OnceLock<MethodRef>,
pub interface_impls: MethodRefList,
pub security: OnceLock<Security>,
pub blocks: OnceLock<Vec<BasicBlock>>,
pub custom_attributes: CustomAttributeValueList,
pub declaring_type: OnceLock<CilTypeRef>,
}
impl Method {
pub fn instructions(&self) -> InstructionIterator<'_> {
if let Some(blocks) = self.blocks.get() {
InstructionIterator::new(blocks.as_slice())
} else {
InstructionIterator::new(&[])
}
}
pub fn blocks(&self) -> Box<dyn Iterator<Item = (usize, &BasicBlock)> + '_> {
if let Some(blocks) = self.blocks.get() {
Box::new(blocks.iter().enumerate())
} else {
Box::new([].iter().enumerate())
}
}
pub fn block_count(&self) -> usize {
if let Some(blocks) = self.blocks.get() {
blocks.len()
} else {
0
}
}
pub fn instruction_count(&self) -> usize {
if let Some(blocks) = self.blocks.get() {
blocks.iter().map(|block| block.instructions.len()).sum()
} else {
0
}
}
pub fn is_code_il(&self) -> bool {
self.impl_code_type.contains(MethodImplCodeType::IL)
}
pub fn is_code_native(&self) -> bool {
self.impl_code_type.contains(MethodImplCodeType::NATIVE)
}
pub fn is_code_opt_il(&self) -> bool {
self.impl_code_type.contains(MethodImplCodeType::OPTIL)
}
pub fn is_code_runtime(&self) -> bool {
self.impl_code_type.contains(MethodImplCodeType::RUNTIME)
}
pub fn is_code_unmanaged(&self) -> bool {
self.impl_management
.contains(MethodImplManagement::UNMANAGED)
}
pub fn is_forward_ref(&self) -> bool {
self.impl_options.contains(MethodImplOptions::FORWARD_REF)
}
pub fn is_synchronized(&self) -> bool {
self.impl_options.contains(MethodImplOptions::SYNCHRONIZED)
}
pub fn is_pinvoke(&self) -> bool {
self.impl_options.contains(MethodImplOptions::PRESERVE_SIG)
}
pub fn is_internal_call(&self) -> bool {
self.impl_options.contains(MethodImplOptions::INTERNAL_CALL)
}
#[must_use]
pub fn is_forwarded_pinvoke(&self) -> bool {
self.impl_options
.contains(MethodImplOptions::MAX_METHOD_IMPL_VAL)
}
#[must_use]
pub fn is_constructor(&self) -> bool {
self.name.starts_with(".ctor") || self.name.starts_with(".cctor")
}
#[must_use]
pub fn is_ctor(&self) -> bool {
self.name == ".ctor"
}
#[must_use]
pub fn is_cctor(&self) -> bool {
self.name == ".cctor"
}
#[must_use]
pub fn has_body(&self) -> bool {
self.body.get().is_some()
}
#[must_use]
pub fn is_static(&self) -> bool {
self.flags_modifiers.contains(MethodModifiers::STATIC)
}
#[must_use]
pub fn is_virtual(&self) -> bool {
self.flags_modifiers.contains(MethodModifiers::VIRTUAL)
}
#[must_use]
pub fn is_abstract(&self) -> bool {
self.flags_modifiers.contains(MethodModifiers::ABSTRACT)
}
#[must_use]
pub fn is_public(&self) -> bool {
self.flags_access == MethodAccessFlags::PUBLIC
}
#[must_use]
pub fn declaring_type_rc(&self) -> Option<CilTypeRc> {
self.declaring_type.get().and_then(CilTypeRef::upgrade)
}
#[must_use]
pub fn declaring_type_fullname(&self) -> Option<String> {
self.declaring_type_rc().map(|t| t.fullname())
}
#[must_use]
pub fn fullname(&self) -> String {
match self.declaring_type_fullname() {
Some(type_name) if !type_name.is_empty() => format!("{}::{}", type_name, self.name),
_ => self.name.clone(),
}
}
#[must_use]
pub fn cfg(&self) -> Option<ControlFlowGraph<'_>> {
let blocks = self.blocks.get()?;
if blocks.is_empty() {
return None;
}
ControlFlowGraph::from_blocks_ref(blocks).ok()
}
pub fn parse(
&self,
file: &File,
blobs: &Blob,
sigs: Option<&MetadataTable<StandAloneSigRaw>>,
types: &Arc<TypeRegistry>,
shared_visited: Arc<VisitedMap>,
) -> Result<()> {
let has_il_body = !self.is_code_native() && !self.is_code_runtime();
if has_il_body {
if let Some(rva) = self.rva {
let body = self.parse_method_body(file, blobs, sigs, types, rva)?;
self.body.set(body).ok();
}
}
self.resolve_parameter_signatures(types)?;
self.parse_varargs(types)?;
if has_il_body {
assembly::decode_method(self, file, shared_visited)?;
}
Ok(())
}
fn parse_method_body(
&self,
file: &File,
blobs: &Blob,
sigs: Option<&MetadataTable<StandAloneSigRaw>>,
types: &Arc<TypeRegistry>,
rva: u32,
) -> Result<MethodBody> {
let method_offset = file.rva_to_offset(rva as usize)?;
if method_offset == 0 || method_offset >= file.data().len() {
return Err(malformed_error!(
"Method offset is invalid - {}",
method_offset
));
}
let mut body = MethodBody::from(&file.data()[method_offset..])?;
if body.local_var_sig_token != 0 {
self.parse_local_variables(&body, blobs, sigs, types)?;
}
Self::resolve_exception_handlers(&mut body, types)?;
Ok(body)
}
fn parse_local_variables(
&self,
body: &MethodBody,
blobs: &Blob,
sigs: Option<&MetadataTable<StandAloneSigRaw>>,
types: &Arc<TypeRegistry>,
) -> Result<()> {
let Some(sigs_table) = sigs else {
return Ok(());
};
let local_var_sig_data = match sigs_table.get(body.local_var_sig_token & 0x00FF_FFFF) {
Some(var_sig_row) => blobs.get(var_sig_row.signature as usize)?,
None => {
return Err(malformed_error!(
"Failed to resolve local variable signature - token 0x{:08X}",
body.local_var_sig_token
))
}
};
let mut resolver = TypeResolver::new(types.clone());
let local_var_sig = parse_local_var_signature(local_var_sig_data)?;
for local_var in &local_var_sig.locals {
let mut modifiers = Vec::with_capacity(local_var.modifiers.len());
for var_mod in &local_var.modifiers {
match types.get(&var_mod.modifier_type) {
Some(var_mod_type) => modifiers.push(CilModifier {
required: var_mod.is_required,
modifier: var_mod_type.into(),
}),
None => {
return Err(malformed_error!(
"Failed to resolve local variable modifier type - token 0x{:08X}",
var_mod.modifier_type.value()
))
}
}
}
self.local_vars.push(LocalVariable {
modifiers,
is_byref: local_var.is_byref,
is_pinned: local_var.is_pinned,
base: resolver.resolve(&local_var.base)?.into(),
});
}
Ok(())
}
fn resolve_exception_handlers(body: &mut MethodBody, types: &Arc<TypeRegistry>) -> Result<()> {
for (index, exception_handler) in body.exception_handlers.iter_mut().enumerate() {
if exception_handler.flags == ExceptionHandlerFlags::EXCEPTION {
let type_token = Token::new(exception_handler.filter_offset);
let Some(handler_type) = types.get(&type_token) else {
return Err(malformed_error!(
"Failed to resolve exception handler {} type - token 0x{:08X}",
index,
exception_handler.filter_offset
));
};
exception_handler.handler = Some(handler_type);
exception_handler.filter_offset = 0;
}
}
Ok(())
}
fn resolve_parameter_signatures(&self, types: &Arc<TypeRegistry>) -> Result<()> {
let method_param_count = Some(self.signature.params.len());
for (_, parameter) in self.params.iter() {
if parameter.sequence == 0 {
parameter.apply_signature(
&self.signature.return_type,
types.clone(),
method_param_count,
)?;
} else {
let index = (parameter.sequence - 1) as usize;
if let Some(param_signature) = self.signature.params.get(index) {
parameter.apply_signature(
param_signature,
types.clone(),
method_param_count,
)?;
}
}
}
Ok(())
}
fn parse_varargs(&self, types: &Arc<TypeRegistry>) -> Result<()> {
for vararg in &self.signature.varargs {
let modifiers = Arc::new(boxcar::Vec::with_capacity(vararg.modifiers.len()));
for modifier in &vararg.modifiers {
match types.get(&modifier.modifier_type) {
Some(new_mod) => _ = modifiers.push(new_mod.into()),
None => {
return Err(malformed_error!(
"Failed to resolve vararg modifier type - token 0x{:08X}",
modifier.modifier_type.value()
))
}
}
}
let mut resolver = TypeResolver::new(types.clone());
self.varargs.push(VarArg {
modifiers,
by_ref: vararg.by_ref,
base: resolver.resolve(&vararg.base)?.into(),
});
}
Ok(())
}
#[must_use]
pub fn is_event_handler(&self) -> bool {
let has_handler_name = self.name.starts_with("On")
|| self.name.ends_with("Handler")
|| self.name.ends_with("_Click")
|| self.name.ends_with("_Load")
|| self.name.ends_with("_Changed")
|| self.name.contains('_');
let has_two_params = self.signature.params.len() == 2;
let is_void = matches!(self.signature.return_type.base, TypeSignature::Void);
has_handler_name && has_two_params && is_void
}
#[must_use]
pub fn ssa(&self, assembly: &CilObject) -> Option<SsaFunction> {
let blocks = self.blocks.get()?;
let cfg = self.cfg()?;
let num_args = self.signature.params.len() + usize::from(self.signature.has_this);
let num_locals = self.local_vars.count();
let type_context = TypeContext::new(self, assembly);
let mut ssa = SsaConverter::build(&cfg, num_args, num_locals, Some(&type_context)).ok()?;
if let Some(body) = self.body.get() {
if !body.exception_handlers.is_empty() {
let base_offset = blocks.first().map_or(0, |b| b.offset);
let ssa_handlers: Vec<SsaExceptionHandler> = body
.exception_handlers
.iter()
.enumerate()
.map(|(handler_idx, eh)| {
let try_start_block = Self::find_block_at_offset(
blocks,
base_offset + eh.try_offset as usize,
);
let try_end_block = Self::find_block_at_offset(
blocks,
base_offset + (eh.try_offset + eh.try_length) as usize,
);
let handler_start_block =
Self::find_handler_entry_block(blocks, handler_idx).or_else(|| {
Self::find_block_at_offset(
blocks,
base_offset + eh.handler_offset as usize,
)
});
let handler_end_block = Self::find_block_at_offset(
blocks,
base_offset + (eh.handler_offset + eh.handler_length) as usize,
);
let filter_start_block = if eh.flags == ExceptionHandlerFlags::FILTER {
Self::find_block_at_offset(
blocks,
base_offset + eh.filter_offset as usize,
)
} else {
None
};
let class_token_or_filter = if eh.flags == ExceptionHandlerFlags::EXCEPTION
{
eh.handler.as_ref().map_or(0, |t| t.token.value())
} else if eh.flags == ExceptionHandlerFlags::FILTER {
eh.filter_offset
} else {
0
};
SsaExceptionHandler {
flags: eh.flags,
try_offset: eh.try_offset,
try_length: eh.try_length,
handler_offset: eh.handler_offset,
handler_length: eh.handler_length,
class_token_or_filter,
try_start_block,
try_end_block,
handler_start_block,
handler_end_block,
filter_start_block,
}
})
.collect();
ssa.set_exception_handlers(ssa_handlers);
}
}
Some(ssa)
}
fn find_block_at_offset(blocks: &[BasicBlock], offset: usize) -> Option<usize> {
if let Some(idx) = blocks.iter().position(|b| b.offset == offset) {
return Some(idx);
}
blocks
.iter()
.position(|b| offset >= b.offset && offset < b.offset + b.size)
}
fn find_handler_entry_block(blocks: &[BasicBlock], handler_index: usize) -> Option<usize> {
blocks.iter().position(|b| {
b.handler_entry
.as_ref()
.is_some_and(|info| info.handler_index == handler_index)
})
}
#[must_use]
pub fn get_local_type_signatures(&self) -> Option<Vec<SignatureLocalVariable>> {
if self.local_vars.is_empty() {
return None;
}
self.local_vars
.iter()
.map(|(_, local)| local.to_signature_local())
.collect()
}
pub fn encode_body(&self) -> Result<Vec<u8>> {
let body = self
.body
.get()
.ok_or_else(|| malformed_error!("Method {} has no body to encode", self.name))?;
let blocks = self
.blocks
.get()
.ok_or_else(|| malformed_error!("Method {} has no decoded blocks", self.name))?;
let mut encoder = InstructionEncoder::new();
for block in blocks {
for instruction in &block.instructions {
let operand = match &instruction.operand {
crate::assembly::Operand::None => None,
op => Some(op.clone()),
};
encoder.emit_instruction(instruction.mnemonic, operand)?;
}
}
let (il_code, max_stack, _) = encoder.finalize()?;
let has_exceptions = !body.exception_handlers.is_empty();
let il_code_size = u32::try_from(il_code.len())
.map_err(|_| malformed_error!("IL code size {} exceeds u32 range", il_code.len()))?;
let header = encode_method_body_header(
il_code_size,
max_stack,
body.local_var_sig_token,
has_exceptions,
body.is_init_local,
)?;
let mut result = header;
result.extend_from_slice(&il_code);
if has_exceptions {
let padding = (4 - (result.len() % 4)) % 4;
result.extend(std::iter::repeat_n(0u8, padding));
let exception_data = encode_exception_handlers(&body.exception_handlers)?;
result.extend_from_slice(&exception_data);
}
Ok(result)
}
#[must_use]
pub fn encoded_size(&self) -> Option<usize> {
let body = self.body.get()?;
let blocks = self.blocks.get()?;
let mut code_size = 0usize;
for block in blocks {
for instruction in &block.instructions {
code_size += usize::try_from(instruction.size).ok()?;
}
}
let has_exceptions = !body.exception_handlers.is_empty();
let has_locals = body.local_var_sig_token != 0;
let needs_fat = code_size > 63 || body.max_stack > 8 || has_locals || has_exceptions;
let header_size = if needs_fat { 12 } else { 1 };
let mut total = header_size + code_size;
if has_exceptions {
total = (total + 3) & !3;
let handler_count = body.exception_handlers.len();
let needs_fat_exceptions = handler_count > 20
|| body.exception_handlers.iter().any(|h| {
h.try_offset > 0xFFFF
|| h.try_length > 0xFF
|| h.handler_offset > 0xFFFF
|| h.handler_length > 0xFF
});
if needs_fat_exceptions {
total += 4 + handler_count * 24;
} else {
total += 4 + handler_count * 12;
}
}
Some(total)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::assembly::{
BasicBlock, FlowType, Instruction, InstructionCategory, Operand, StackBehavior,
};
use crate::test::builders::MethodBuilder;
#[test]
fn test_instructions_iterator_empty_method() {
let blocks = Vec::new();
let method = create_test_method(blocks);
let mut instruction_count = 0;
for _instruction in method.instructions() {
instruction_count += 1;
}
assert_eq!(instruction_count, 0);
assert_eq!(method.instruction_count(), 0);
}
#[test]
fn test_instructions_iterator_single_block() {
let block = BasicBlock {
id: 0,
rva: 0x1000,
offset: 0,
size: 10,
instructions: vec![
create_test_instruction(0x00, "nop"), create_test_instruction(0x02, "ldarg.0"), create_test_instruction(0x2A, "ret"), ],
predecessors: vec![],
successors: vec![],
exceptions: vec![],
handler_entry: None,
exception_successors: vec![],
};
let blocks = vec![block];
let method = create_test_method(blocks);
let instructions: Vec<_> = method.instructions().collect();
assert_eq!(instructions.len(), 3);
assert_eq!(method.instruction_count(), 3);
assert_eq!(instructions[0].mnemonic, "nop");
assert_eq!(instructions[1].mnemonic, "ldarg.0");
assert_eq!(instructions[2].mnemonic, "ret");
}
#[test]
fn test_instructions_iterator_multiple_blocks() {
let block1 = BasicBlock {
id: 0,
rva: 0x1000,
offset: 0,
size: 5,
instructions: vec![
create_test_instruction(0x02, "ldarg.0"),
create_test_instruction(0x03, "ldarg.1"),
],
predecessors: vec![],
successors: vec![1],
exceptions: vec![],
handler_entry: None,
exception_successors: vec![],
};
let block2 = BasicBlock {
id: 1,
rva: 0x1010,
offset: 5,
size: 5,
instructions: vec![
create_test_instruction(0x58, "add"),
create_test_instruction(0x2A, "ret"),
],
predecessors: vec![0],
successors: vec![],
exceptions: vec![],
handler_entry: None,
exception_successors: vec![],
};
let blocks = vec![block1, block2];
let method = create_test_method(blocks);
let instructions: Vec<_> = method.instructions().collect();
assert_eq!(instructions.len(), 4);
assert_eq!(method.instruction_count(), 4);
assert_eq!(instructions[0].mnemonic, "ldarg.0");
assert_eq!(instructions[1].mnemonic, "ldarg.1");
assert_eq!(instructions[2].mnemonic, "add");
assert_eq!(instructions[3].mnemonic, "ret");
}
#[test]
fn test_instruction_iterator_size_hint() {
let block = BasicBlock {
id: 0,
rva: 0x1000,
offset: 0,
size: 3,
instructions: vec![
create_test_instruction(0x00, "nop"),
create_test_instruction(0x00, "nop"),
create_test_instruction(0x2A, "ret"),
],
predecessors: vec![],
successors: vec![],
exceptions: vec![],
handler_entry: None,
exception_successors: vec![],
};
let blocks = vec![block];
let method = create_test_method(blocks);
let mut iter = method.instructions();
assert_eq!(iter.size_hint(), (3, Some(3)));
iter.next();
assert_eq!(iter.size_hint(), (2, Some(2)));
iter.next();
iter.next();
assert_eq!(iter.size_hint(), (0, Some(0)));
}
fn create_test_method(blocks: Vec<BasicBlock>) -> MethodRc {
let method = MethodBuilder::new().with_name("TestMethod").build();
method.blocks.set(blocks).ok();
method
}
fn create_test_instruction(opcode: u8, mnemonic: &'static str) -> Instruction {
Instruction {
rva: 0x1000,
offset: 0,
size: 1,
opcode,
prefix: 0,
mnemonic,
category: InstructionCategory::Misc,
flow_type: FlowType::Sequential,
operand: Operand::None,
stack_behavior: StackBehavior {
pops: 0,
pushes: 0,
net_effect: 0,
},
branch_targets: vec![],
}
}
fn create_empty_block(id: usize) -> BasicBlock {
BasicBlock {
id,
rva: 0x1000 + (id as u64 * 0x10),
offset: id * 10,
size: 0,
instructions: vec![],
predecessors: vec![],
successors: vec![],
exceptions: vec![],
handler_entry: None,
exception_successors: vec![],
}
}
#[test]
fn test_instructions_iterator_with_empty_blocks() {
let block1 = create_empty_block(0);
let block2 = BasicBlock {
id: 1,
rva: 0x1010,
offset: 10,
size: 3,
instructions: vec![
create_test_instruction(0x00, "nop"),
create_test_instruction(0x2A, "ret"),
],
predecessors: vec![],
successors: vec![],
exceptions: vec![],
handler_entry: None,
exception_successors: vec![],
};
let block3 = create_empty_block(2);
let blocks = vec![block1, block2, block3];
let method = create_test_method(blocks);
let instructions: Vec<_> = method.instructions().collect();
assert_eq!(instructions.len(), 2);
assert_eq!(instructions[0].mnemonic, "nop");
assert_eq!(instructions[1].mnemonic, "ret");
}
#[test]
fn test_instructions_iterator_many_empty_blocks() {
let mut blocks: Vec<BasicBlock> = (0..100).map(create_empty_block).collect();
blocks.push(BasicBlock {
id: 100,
rva: 0x2000,
offset: 1000,
size: 1,
instructions: vec![create_test_instruction(0x2A, "ret")],
predecessors: vec![],
successors: vec![],
exceptions: vec![],
handler_entry: None,
exception_successors: vec![],
});
let method = create_test_method(blocks);
let instructions: Vec<_> = method.instructions().collect();
assert_eq!(instructions.len(), 1);
assert_eq!(instructions[0].mnemonic, "ret");
}
#[test]
fn test_instructions_iterator_all_empty_blocks() {
let blocks: Vec<BasicBlock> = (0..10).map(create_empty_block).collect();
let method = create_test_method(blocks);
let instructions: Vec<_> = method.instructions().collect();
assert_eq!(instructions.len(), 0);
assert_eq!(method.instruction_count(), 0);
}
#[test]
fn test_method_ref_basic_operations() {
let method = MethodBuilder::new().with_name("TestMethod").build();
let method_ref = MethodRef::new(&method);
assert!(method_ref.is_valid());
let upgraded = method_ref.upgrade();
assert!(upgraded.is_some());
assert_eq!(upgraded.unwrap().name, "TestMethod");
assert_eq!(method_ref.token(), Some(Token::new(0x06000001)));
assert_eq!(method_ref.name(), Some("TestMethod".to_string()));
}
#[test]
fn test_method_ref_after_drop() {
let method_ref = {
let method = MethodBuilder::new().with_name("TemporaryMethod").build();
MethodRef::new(&method)
};
assert!(!method_ref.is_valid());
assert!(method_ref.upgrade().is_none());
assert!(method_ref.token().is_none());
assert!(method_ref.name().is_none());
}
#[test]
fn test_method_is_constructor() {
let ctor = MethodBuilder::new().with_name(".ctor").build();
let cctor = MethodBuilder::new().with_name(".cctor").build();
let regular = MethodBuilder::new().with_name("DoSomething").build();
assert!(ctor.is_constructor());
assert!(cctor.is_constructor());
assert!(!regular.is_constructor());
}
#[test]
fn test_is_forwarded_pinvoke() {
let method = MethodBuilder::new().with_name("TestMethod").build();
assert!(!method.is_forwarded_pinvoke());
}
}