use crate::{
cilassembly::{ChangeRefRc, CilAssembly},
metadata::{
tables::{CodedIndex, CodedIndexType, MethodSemanticsRaw, TableDataOwned, TableId},
token::Token,
},
Error, Result,
};
#[derive(Debug, Clone, Copy)]
enum AssociationTarget {
Property(u32),
Event(u32),
}
pub struct MethodSemanticsBuilder {
semantics: Option<u32>,
method: Option<u32>,
association: Option<AssociationTarget>,
}
impl MethodSemanticsBuilder {
#[must_use]
pub fn new() -> Self {
Self {
semantics: None,
method: None,
association: None,
}
}
#[must_use]
pub fn semantics(mut self, semantics: u32) -> Self {
self.semantics = Some(semantics);
self
}
#[must_use]
pub fn method(mut self, method: u32) -> Self {
self.method = Some(method);
self
}
#[must_use]
pub fn association_from_property(mut self, property: u32) -> Self {
self.association = Some(AssociationTarget::Property(property));
self
}
#[must_use]
pub fn association_from_event(mut self, event: u32) -> Self {
self.association = Some(AssociationTarget::Event(event));
self
}
pub fn build(self, assembly: &mut CilAssembly) -> Result<ChangeRefRc> {
let semantics = self.semantics.ok_or_else(|| {
Error::ModificationInvalid("MethodSemantics semantics field is required".to_string())
})?;
let method = self.method.ok_or_else(|| {
Error::ModificationInvalid("MethodSemantics method field is required".to_string())
})?;
let association_target = self.association.ok_or_else(|| {
Error::ModificationInvalid("MethodSemantics association field is required".to_string())
})?;
let association = match association_target {
AssociationTarget::Property(row) => {
CodedIndex::new(TableId::Property, row, CodedIndexType::HasSemantics)
}
AssociationTarget::Event(row) => {
CodedIndex::new(TableId::Event, row, CodedIndexType::HasSemantics)
}
};
let method_semantics_raw = MethodSemanticsRaw {
rid: 0,
token: Token::new(0),
offset: 0,
semantics,
method,
association,
};
assembly.table_row_add(
TableId::MethodSemantics,
TableDataOwned::MethodSemantics(method_semantics_raw),
)
}
}
impl Default for MethodSemanticsBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cilassembly::{ChangeRefKind, CilAssembly},
metadata::{cilassemblyview::CilAssemblyView, tables::MethodSemanticsAttributes},
};
use std::{env, path::PathBuf};
#[test]
fn test_methodsemantics_builder_creation() {
let builder = MethodSemanticsBuilder::new();
assert!(builder.semantics.is_none());
assert!(builder.method.is_none());
assert!(builder.association.is_none());
}
#[test]
fn test_methodsemantics_builder_default() {
let builder = MethodSemanticsBuilder::default();
assert!(builder.semantics.is_none());
assert!(builder.method.is_none());
assert!(builder.association.is_none());
}
#[test]
fn test_property_getter_semantic() -> Result<()> {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let semantic_ref = MethodSemanticsBuilder::new()
.semantics(MethodSemanticsAttributes::GETTER)
.method(1)
.association_from_property(1)
.build(&mut assembly)?;
assert_eq!(
semantic_ref.kind(),
ChangeRefKind::TableRow(TableId::MethodSemantics)
);
}
Ok(())
}
#[test]
fn test_property_setter_semantic() -> Result<()> {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let semantic_ref = MethodSemanticsBuilder::new()
.semantics(MethodSemanticsAttributes::SETTER)
.method(2)
.association_from_property(1)
.build(&mut assembly)?;
assert_eq!(
semantic_ref.kind(),
ChangeRefKind::TableRow(TableId::MethodSemantics)
);
}
Ok(())
}
#[test]
fn test_event_add_semantic() -> Result<()> {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let semantic_ref = MethodSemanticsBuilder::new()
.semantics(MethodSemanticsAttributes::ADD_ON)
.method(3)
.association_from_event(1)
.build(&mut assembly)?;
assert_eq!(
semantic_ref.kind(),
ChangeRefKind::TableRow(TableId::MethodSemantics)
);
}
Ok(())
}
#[test]
fn test_event_remove_semantic() -> Result<()> {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let semantic_ref = MethodSemanticsBuilder::new()
.semantics(MethodSemanticsAttributes::REMOVE_ON)
.method(4)
.association_from_event(1)
.build(&mut assembly)?;
assert_eq!(
semantic_ref.kind(),
ChangeRefKind::TableRow(TableId::MethodSemantics)
);
}
Ok(())
}
#[test]
fn test_event_fire_semantic() -> Result<()> {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let semantic_ref = MethodSemanticsBuilder::new()
.semantics(MethodSemanticsAttributes::FIRE)
.method(5)
.association_from_event(1)
.build(&mut assembly)?;
assert_eq!(
semantic_ref.kind(),
ChangeRefKind::TableRow(TableId::MethodSemantics)
);
}
Ok(())
}
#[test]
fn test_combined_semantics() -> Result<()> {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let semantic_ref = MethodSemanticsBuilder::new()
.semantics(MethodSemanticsAttributes::GETTER | MethodSemanticsAttributes::OTHER)
.method(6)
.association_from_property(2)
.build(&mut assembly)?;
assert_eq!(
semantic_ref.kind(),
ChangeRefKind::TableRow(TableId::MethodSemantics)
);
}
Ok(())
}
#[test]
fn test_association_with_row_index() -> Result<()> {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let semantic_ref = MethodSemanticsBuilder::new()
.semantics(MethodSemanticsAttributes::GETTER)
.method(7)
.association_from_property(1)
.build(&mut assembly)?;
assert_eq!(
semantic_ref.kind(),
ChangeRefKind::TableRow(TableId::MethodSemantics)
);
}
Ok(())
}
#[test]
fn test_multiple_method_semantics() -> Result<()> {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let getter_ref = MethodSemanticsBuilder::new()
.semantics(MethodSemanticsAttributes::GETTER)
.method(1)
.association_from_property(1)
.build(&mut assembly)?;
let setter_ref = MethodSemanticsBuilder::new()
.semantics(MethodSemanticsAttributes::SETTER)
.method(2)
.association_from_property(1)
.build(&mut assembly)?;
let other_ref = MethodSemanticsBuilder::new()
.semantics(MethodSemanticsAttributes::OTHER)
.method(3)
.association_from_property(1)
.build(&mut assembly)?;
assert_eq!(
getter_ref.kind(),
ChangeRefKind::TableRow(TableId::MethodSemantics)
);
assert_eq!(
setter_ref.kind(),
ChangeRefKind::TableRow(TableId::MethodSemantics)
);
assert_eq!(
other_ref.kind(),
ChangeRefKind::TableRow(TableId::MethodSemantics)
);
assert!(!std::sync::Arc::ptr_eq(&getter_ref, &setter_ref));
assert!(!std::sync::Arc::ptr_eq(&setter_ref, &other_ref));
}
Ok(())
}
#[test]
fn test_build_without_semantics_fails() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let result = MethodSemanticsBuilder::new()
.method(1)
.association_from_property(1)
.build(&mut assembly);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("semantics field is required"));
}
}
#[test]
fn test_build_without_method_fails() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let result = MethodSemanticsBuilder::new()
.semantics(MethodSemanticsAttributes::GETTER)
.association_from_property(1)
.build(&mut assembly);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("method field is required"));
}
}
#[test]
fn test_build_without_association_fails() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let result = MethodSemanticsBuilder::new()
.semantics(MethodSemanticsAttributes::GETTER)
.method(1)
.build(&mut assembly);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("association field is required"));
}
}
}