use crate::{
cilassembly::{ChangeRefRc, CilAssembly},
metadata::{
signatures::{encode_field_signature, SignatureField, TypeSignature},
tables::{
CodedIndex, CodedIndexType, EventBuilder as EventTableBuilder, FieldBuilder, TableId,
},
token::Token,
},
Error, Result,
};
use super::method::MethodBuilder;
pub enum EventImplementation {
Auto {
backing_field_name: Option<String>,
backing_field_attributes: u32,
},
Custom {
add_method: Option<Box<dyn FnOnce(MethodBuilder) -> MethodBuilder + Send>>,
remove_method: Option<Box<dyn FnOnce(MethodBuilder) -> MethodBuilder + Send>>,
},
Manual,
}
pub struct EventBuilder {
name: String,
event_type: TypeSignature,
event_type_index: Option<CodedIndex>,
attributes: u32,
add_attributes: u32,
remove_attributes: u32,
implementation: EventImplementation,
}
impl EventBuilder {
#[must_use]
pub fn new(name: &str, event_type: TypeSignature) -> Self {
Self {
name: name.to_string(),
event_type,
event_type_index: None,
attributes: 0x0000, add_attributes: 0x0006, remove_attributes: 0x0006, implementation: EventImplementation::Auto {
backing_field_name: None,
backing_field_attributes: 0x0001, },
}
}
#[must_use]
pub fn auto_event(mut self) -> Self {
self.implementation = EventImplementation::Auto {
backing_field_name: None,
backing_field_attributes: 0x0001, };
self
}
#[must_use]
pub fn custom(mut self) -> Self {
self.implementation = EventImplementation::Custom {
add_method: None,
remove_method: None,
};
self
}
#[must_use]
pub fn manual(mut self) -> Self {
self.implementation = EventImplementation::Manual;
self
}
#[must_use]
pub fn backing_field(mut self, field_name: &str) -> Self {
if let EventImplementation::Auto {
backing_field_name, ..
} = &mut self.implementation
{
*backing_field_name = Some(field_name.to_string());
}
self
}
#[must_use]
pub fn private_backing_field(mut self) -> Self {
if let EventImplementation::Auto {
backing_field_attributes,
..
} = &mut self.implementation
{
*backing_field_attributes = 0x0001; }
self
}
#[must_use]
pub fn protected_backing_field(mut self) -> Self {
if let EventImplementation::Auto {
backing_field_attributes,
..
} = &mut self.implementation
{
*backing_field_attributes = 0x0004; }
self
}
#[must_use]
pub fn public_accessors(mut self) -> Self {
self.add_attributes = 0x0006; self.remove_attributes = 0x0006; self
}
#[must_use]
pub fn private_accessors(mut self) -> Self {
self.add_attributes = 0x0001; self.remove_attributes = 0x0001; self
}
#[must_use]
pub fn add_visibility(mut self, attributes: u32) -> Self {
self.add_attributes = attributes;
self
}
#[must_use]
pub fn remove_visibility(mut self, attributes: u32) -> Self {
self.remove_attributes = attributes;
self
}
#[must_use]
pub fn add_method<F>(mut self, implementation: F) -> Self
where
F: FnOnce(MethodBuilder) -> MethodBuilder + Send + 'static,
{
if let EventImplementation::Custom { add_method, .. } = &mut self.implementation {
*add_method = Some(Box::new(implementation));
}
self
}
#[must_use]
pub fn remove_method<F>(mut self, implementation: F) -> Self
where
F: FnOnce(MethodBuilder) -> MethodBuilder + Send + 'static,
{
if let EventImplementation::Custom { remove_method, .. } = &mut self.implementation {
*remove_method = Some(Box::new(implementation));
}
self
}
#[must_use]
pub fn attributes(mut self, attributes: u32) -> Self {
self.attributes = attributes;
self
}
#[must_use]
pub fn event_type_index(mut self, coded_index: CodedIndex) -> Self {
self.event_type_index = Some(coded_index);
self
}
pub fn build(self, assembly: &mut CilAssembly) -> Result<ChangeRefRc> {
let event_type_coded_index = self
.event_type_index
.unwrap_or_else(|| CodedIndex::new(TableId::TypeRef, 1, CodedIndexType::TypeDefOrRef));
let event_ref = EventTableBuilder::new()
.name(&self.name)
.flags(self.attributes)
.event_type(event_type_coded_index)
.build(assembly)?;
match self.implementation {
EventImplementation::Auto {
backing_field_name,
backing_field_attributes,
} => {
let field_name = backing_field_name.unwrap_or_else(|| self.name.clone());
let field_sig = SignatureField {
modifiers: Vec::new(),
base: self.event_type.clone(),
};
let sig_bytes = encode_field_signature(&field_sig)?;
let backing_field_ref = FieldBuilder::new()
.name(&field_name)
.flags(backing_field_attributes)
.signature(&sig_bytes)
.build(assembly)?;
let backing_field_token =
backing_field_ref.placeholder_token().ok_or_else(|| {
Error::ModificationInvalid(
"Failed to get placeholder token for backing field".to_string(),
)
})?;
let add_field_token = backing_field_token; let add_name = format!("add_{}", self.name);
let add_visibility = self.add_attributes;
let add_method = MethodBuilder::event_add(&add_name, self.event_type.clone());
let add_method = match add_visibility {
0x0001 => add_method.private(),
_ => add_method.public(),
};
add_method
.implementation(move |body| {
body.implementation(move |asm| {
asm.ldarg_0()? .ldfld(add_field_token)? .ldarg_1()? .call(Token::new(0x0A00_0001))? .stfld(add_field_token)? .ret()?;
Ok(())
})
})
.build(assembly)?;
let remove_field_token = backing_field_token; let remove_name = format!("remove_{}", self.name);
let remove_visibility = self.remove_attributes;
let remove_method =
MethodBuilder::event_remove(&remove_name, self.event_type.clone());
let remove_method = match remove_visibility {
0x0001 => remove_method.private(),
_ => remove_method.public(),
};
remove_method
.implementation(move |body| {
body.implementation(move |asm| {
asm.ldarg_0()? .ldfld(remove_field_token)? .ldarg_1()? .call(Token::new(0x0A00_0002))? .stfld(remove_field_token)? .ret()?;
Ok(())
})
})
.build(assembly)?;
Ok(event_ref)
}
EventImplementation::Custom {
add_method,
remove_method,
} => {
if let Some(add_impl) = add_method {
let add_method_builder = MethodBuilder::event_add(
&format!("add_{}", self.name),
self.event_type.clone(),
);
let add_method_builder = match self.add_attributes {
0x0001 => add_method_builder.private(),
_ => add_method_builder.public(),
};
let configured_add = add_impl(add_method_builder);
configured_add.build(assembly)?;
} else {
return Err(Error::ModificationInvalid(
"Custom event requires add method implementation".to_string(),
));
}
if let Some(remove_impl) = remove_method {
let remove_method_builder = MethodBuilder::event_remove(
&format!("remove_{}", self.name),
self.event_type.clone(),
);
let remove_method_builder = match self.remove_attributes {
0x0001 => remove_method_builder.private(),
_ => remove_method_builder.public(),
};
let configured_remove = remove_impl(remove_method_builder);
configured_remove.build(assembly)?;
} else {
return Err(Error::ModificationInvalid(
"Custom event requires remove method implementation".to_string(),
));
}
Ok(event_ref)
}
EventImplementation::Manual => {
Ok(event_ref)
}
}
}
}
impl Default for EventBuilder {
fn default() -> Self {
Self::new("DefaultEvent", TypeSignature::Object)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cilassembly::{changes::ChangeRefKind, CilAssembly},
metadata::{cilassemblyview::CilAssemblyView, signatures::TypeSignature, tables::TableId},
};
use std::path::PathBuf;
fn get_test_assembly() -> Result<CilAssembly> {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
let view = CilAssemblyView::from_path(&path)?;
Ok(CilAssembly::new(view))
}
#[test]
fn test_simple_auto_event() -> Result<()> {
let mut assembly = get_test_assembly()?;
let event_ref = EventBuilder::new("OnClick", TypeSignature::Object)
.auto_event()
.public_accessors()
.build(&mut assembly)?;
assert_eq!(event_ref.kind(), ChangeRefKind::TableRow(TableId::Event));
Ok(())
}
#[test]
fn test_custom_event() -> Result<()> {
let mut assembly = get_test_assembly()?;
let event_ref = EventBuilder::new("OnDataChanged", TypeSignature::Object)
.custom()
.add_method(|method| {
method.implementation(|body| {
body.implementation(|asm| {
asm.ldarg_0()?
.ldarg_1()?
.call(Token::new(0x0A000001))?
.ret()?;
Ok(())
})
})
})
.remove_method(|method| {
method.implementation(|body| {
body.implementation(|asm| {
asm.ldarg_0()?
.ldarg_1()?
.call(Token::new(0x0A000002))?
.ret()?;
Ok(())
})
})
})
.build(&mut assembly)?;
assert_eq!(event_ref.kind(), ChangeRefKind::TableRow(TableId::Event));
Ok(())
}
#[test]
fn test_manual_event() -> Result<()> {
let mut assembly = get_test_assembly()?;
let event_ref = EventBuilder::new("ManualEvent", TypeSignature::Object)
.manual()
.build(&mut assembly)?;
assert_eq!(event_ref.kind(), ChangeRefKind::TableRow(TableId::Event));
Ok(())
}
#[test]
fn test_custom_backing_field() -> Result<()> {
let mut assembly = get_test_assembly()?;
let event_ref = EventBuilder::new("OnValueChanged", TypeSignature::Object)
.auto_event()
.backing_field("_onValueChanged")
.private_backing_field()
.public_accessors()
.build(&mut assembly)?;
assert_eq!(event_ref.kind(), ChangeRefKind::TableRow(TableId::Event));
Ok(())
}
#[test]
fn test_event_with_different_accessor_visibility() -> Result<()> {
let mut assembly = get_test_assembly()?;
let event_ref = EventBuilder::new("MixedVisibility", TypeSignature::Object)
.auto_event()
.add_visibility(0x0006) .remove_visibility(0x0001) .build(&mut assembly)?;
assert_eq!(event_ref.kind(), ChangeRefKind::TableRow(TableId::Event));
Ok(())
}
#[test]
fn test_custom_event_missing_add_fails() {
let mut assembly = get_test_assembly().unwrap();
let result = EventBuilder::new("InvalidCustom", TypeSignature::Object)
.custom()
.remove_method(|method| {
method.implementation(|body| {
body.implementation(|asm| {
asm.ret()?;
Ok(())
})
})
})
.build(&mut assembly);
assert!(result.is_err());
}
#[test]
fn test_custom_event_missing_remove_fails() {
let mut assembly = get_test_assembly().unwrap();
let result = EventBuilder::new("InvalidCustom", TypeSignature::Object)
.custom()
.add_method(|method| {
method.implementation(|body| {
body.implementation(|asm| {
asm.ret()?;
Ok(())
})
})
})
.build(&mut assembly);
assert!(result.is_err());
}
#[test]
fn test_event_with_explicit_type_index() -> Result<()> {
let mut assembly = get_test_assembly()?;
let event_handler_ref = CodedIndex::new(TableId::TypeRef, 5, CodedIndexType::TypeDefOrRef);
let event_ref = EventBuilder::new("OnExplicitType", TypeSignature::Object)
.manual()
.event_type_index(event_handler_ref)
.build(&mut assembly)?;
assert_eq!(event_ref.kind(), ChangeRefKind::TableRow(TableId::Event));
Ok(())
}
}