use crate::{
cilassembly::{ChangeRefRc, CilAssembly},
metadata::{
tables::{PropertyRaw, TableDataOwned, TableId},
token::Token,
},
Error, Result,
};
pub struct PropertyBuilder {
name: Option<String>,
flags: Option<u32>,
signature: Option<Vec<u8>>,
}
impl Default for PropertyBuilder {
fn default() -> Self {
Self::new()
}
}
impl PropertyBuilder {
#[must_use]
pub fn new() -> Self {
Self {
name: None,
flags: None,
signature: None,
}
}
#[must_use]
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn flags(mut self, flags: u32) -> Self {
self.flags = Some(flags);
self
}
#[must_use]
pub fn signature(mut self, signature: &[u8]) -> Self {
self.signature = Some(signature.to_vec());
self
}
pub fn build(self, assembly: &mut CilAssembly) -> Result<ChangeRefRc> {
let name = self
.name
.ok_or_else(|| Error::ModificationInvalid("Property name is required".to_string()))?;
let flags = self
.flags
.ok_or_else(|| Error::ModificationInvalid("Property flags are required".to_string()))?;
let signature = self.signature.ok_or_else(|| {
Error::ModificationInvalid("Property signature is required".to_string())
})?;
let name_index = assembly.string_get_or_add(&name)?.placeholder();
let signature_index = assembly.blob_add(&signature)?.placeholder();
let rid = assembly.next_rid(TableId::Property)?;
let token = Token::from_parts(TableId::Property, rid);
let property_raw = PropertyRaw {
rid,
token,
offset: 0, flags,
name: name_index,
signature: signature_index,
};
assembly.table_row_add(TableId::Property, TableDataOwned::Property(property_raw))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cilassembly::{ChangeRefKind, CilAssembly},
metadata::{
cilassemblyview::CilAssemblyView,
tables::{PropertyAttributes, TableId},
},
};
use std::path::PathBuf;
#[test]
fn test_property_builder_basic() {
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 string_property_sig = &[0x08, 0x0E];
let ref_ = PropertyBuilder::new()
.name("TestProperty")
.flags(0)
.signature(string_property_sig)
.build(&mut assembly)
.unwrap();
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::Property));
}
}
#[test]
fn test_property_builder_with_special_name() {
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 int32_property_sig = &[0x08, 0x08];
let ref_ = PropertyBuilder::new()
.name("Item")
.flags(PropertyAttributes::SPECIAL_NAME)
.signature(int32_property_sig)
.build(&mut assembly)
.unwrap();
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::Property));
let token = ref_.placeholder_token().unwrap();
assert_eq!(token.value() & 0xFF000000, 0x17000000);
}
}
#[test]
fn test_property_builder_indexer_signature() {
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 indexer_sig = &[0x28, 0x01, 0x0E, 0x08];
let ref_ = PropertyBuilder::new()
.name("Item")
.flags(PropertyAttributes::SPECIAL_NAME)
.signature(indexer_sig)
.build(&mut assembly)
.unwrap();
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::Property));
}
}
#[test]
fn test_property_builder_with_default() {
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 bool_property_sig = &[0x08, 0x02];
let ref_ = PropertyBuilder::new()
.name("DefaultProperty")
.flags(PropertyAttributes::HAS_DEFAULT)
.signature(bool_property_sig)
.build(&mut assembly)
.unwrap();
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::Property));
}
}
#[test]
fn test_property_builder_missing_name() {
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 = PropertyBuilder::new()
.flags(0)
.signature(&[0x08, 0x08])
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_property_builder_missing_flags() {
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 = PropertyBuilder::new()
.name("TestProperty")
.signature(&[0x08, 0x08])
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_property_builder_missing_signature() {
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 = PropertyBuilder::new()
.name("TestProperty")
.flags(0)
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_property_builder_multiple_properties() {
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 string_sig = &[0x08, 0x0E]; let int_sig = &[0x08, 0x08];
let ref1 = PropertyBuilder::new()
.name("Property1")
.flags(0)
.signature(string_sig)
.build(&mut assembly)
.unwrap();
let ref2 = PropertyBuilder::new()
.name("Property2")
.flags(PropertyAttributes::SPECIAL_NAME)
.signature(int_sig)
.build(&mut assembly)
.unwrap();
let ref3 = PropertyBuilder::new()
.name("Property3")
.flags(PropertyAttributes::HAS_DEFAULT)
.signature(string_sig)
.build(&mut assembly)
.unwrap();
assert!(!std::sync::Arc::ptr_eq(&ref1, &ref2));
assert!(!std::sync::Arc::ptr_eq(&ref1, &ref3));
assert!(!std::sync::Arc::ptr_eq(&ref2, &ref3));
assert_eq!(ref1.kind(), ChangeRefKind::TableRow(TableId::Property));
assert_eq!(ref2.kind(), ChangeRefKind::TableRow(TableId::Property));
assert_eq!(ref3.kind(), ChangeRefKind::TableRow(TableId::Property));
}
}
}