use crate::{
cilassembly::{ChangeRefRc, CilAssembly},
metadata::{
tables::{CodedIndex, CodedIndexType, CustomAttributeRaw, TableDataOwned, TableId},
token::Token,
},
Error, Result,
};
pub struct CustomAttributeBuilder {
parent: Option<CodedIndex>,
constructor: Option<CodedIndex>,
value: Option<Vec<u8>>,
}
impl Default for CustomAttributeBuilder {
fn default() -> Self {
Self::new()
}
}
impl CustomAttributeBuilder {
#[must_use]
pub fn new() -> Self {
Self {
parent: None,
constructor: None,
value: None,
}
}
#[must_use]
pub fn parent(mut self, parent: CodedIndex) -> Self {
self.parent = Some(parent);
self
}
#[must_use]
pub fn constructor(mut self, constructor: CodedIndex) -> Self {
self.constructor = Some(constructor);
self
}
#[must_use]
pub fn value(mut self, value: &[u8]) -> Self {
self.value = Some(value.to_vec());
self
}
pub fn build(self, assembly: &mut CilAssembly) -> Result<ChangeRefRc> {
let parent = self.parent.ok_or_else(|| {
Error::ModificationInvalid("CustomAttribute parent is required".to_string())
})?;
let constructor = self.constructor.ok_or_else(|| {
Error::ModificationInvalid("CustomAttribute constructor is required".to_string())
})?;
let valid_parent_tables = CodedIndexType::HasCustomAttribute.tables();
if !valid_parent_tables.contains(&parent.tag) {
return Err(Error::ModificationInvalid(format!(
"Parent must be a HasCustomAttribute coded index, got {:?}",
parent.tag
)));
}
let valid_constructor_tables = CodedIndexType::CustomAttributeType.tables();
if !valid_constructor_tables.contains(&constructor.tag) {
return Err(Error::ModificationInvalid(format!(
"Constructor must be a CustomAttributeType coded index (MethodDef/MemberRef), got {:?}",
constructor.tag
)));
}
let value_index = if let Some(value) = self.value {
if value.is_empty() {
0 } else {
assembly.blob_add(&value)?.placeholder()
}
} else {
0 };
let rid = assembly.next_rid(TableId::CustomAttribute)?;
let token = Token::from_parts(TableId::CustomAttribute, rid);
let custom_attribute_raw = CustomAttributeRaw {
rid,
token,
offset: 0, parent,
constructor,
value: value_index,
};
assembly.table_row_add(
TableId::CustomAttribute,
TableDataOwned::CustomAttribute(custom_attribute_raw),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cilassembly::{ChangeRefKind, CilAssembly},
metadata::cilassemblyview::CilAssemblyView,
};
use std::path::PathBuf;
#[test]
fn test_custom_attribute_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 target_type =
CodedIndex::new(TableId::TypeDef, 1, CodedIndexType::HasCustomAttribute); let constructor =
CodedIndex::new(TableId::MethodDef, 1, CodedIndexType::CustomAttributeType);
let ref_ = CustomAttributeBuilder::new()
.parent(target_type)
.constructor(constructor)
.value(&[]) .build(&mut assembly)
.unwrap();
assert_eq!(
ref_.kind(),
ChangeRefKind::TableRow(TableId::CustomAttribute)
);
}
}
#[test]
fn test_custom_attribute_builder_with_value() {
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 target_field =
CodedIndex::new(TableId::Field, 1, CodedIndexType::HasCustomAttribute); let constructor =
CodedIndex::new(TableId::MemberRef, 1, CodedIndexType::CustomAttributeType);
let attribute_blob = &[0x01, 0x00, 0x00, 0x00];
let ref_ = CustomAttributeBuilder::new()
.parent(target_field)
.constructor(constructor)
.value(attribute_blob)
.build(&mut assembly)
.unwrap();
assert_eq!(
ref_.kind(),
ChangeRefKind::TableRow(TableId::CustomAttribute)
);
}
}
#[test]
fn test_custom_attribute_builder_no_value() {
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 target_method =
CodedIndex::new(TableId::MethodDef, 2, CodedIndexType::HasCustomAttribute); let constructor =
CodedIndex::new(TableId::MethodDef, 3, CodedIndexType::CustomAttributeType);
let ref_ = CustomAttributeBuilder::new()
.parent(target_method)
.constructor(constructor)
.build(&mut assembly)
.unwrap();
assert_eq!(
ref_.kind(),
ChangeRefKind::TableRow(TableId::CustomAttribute)
);
}
}
#[test]
fn test_custom_attribute_builder_missing_parent() {
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 constructor =
CodedIndex::new(TableId::MethodDef, 1, CodedIndexType::CustomAttributeType);
let result = CustomAttributeBuilder::new()
.constructor(constructor)
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_custom_attribute_builder_missing_constructor() {
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 target_type =
CodedIndex::new(TableId::TypeDef, 1, CodedIndexType::HasCustomAttribute);
let result = CustomAttributeBuilder::new()
.parent(target_type)
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_custom_attribute_builder_invalid_parent_type() {
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 invalid_parent =
CodedIndex::new(TableId::Constant, 1, CodedIndexType::HasCustomAttribute); let constructor =
CodedIndex::new(TableId::MethodDef, 1, CodedIndexType::CustomAttributeType);
let result = CustomAttributeBuilder::new()
.parent(invalid_parent)
.constructor(constructor)
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_custom_attribute_builder_invalid_constructor_type() {
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 target_type =
CodedIndex::new(TableId::TypeDef, 1, CodedIndexType::HasCustomAttribute);
let invalid_constructor =
CodedIndex::new(TableId::Field, 1, CodedIndexType::CustomAttributeType);
let result = CustomAttributeBuilder::new()
.parent(target_type)
.constructor(invalid_constructor)
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_custom_attribute_builder_multiple_attributes() {
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 target1 = CodedIndex::new(TableId::TypeDef, 1, CodedIndexType::HasCustomAttribute);
let target2 =
CodedIndex::new(TableId::MethodDef, 1, CodedIndexType::HasCustomAttribute);
let target3 = CodedIndex::new(TableId::Field, 1, CodedIndexType::HasCustomAttribute);
let constructor1 =
CodedIndex::new(TableId::MethodDef, 1, CodedIndexType::CustomAttributeType);
let constructor2 =
CodedIndex::new(TableId::MemberRef, 1, CodedIndexType::CustomAttributeType);
let ref1 = CustomAttributeBuilder::new()
.parent(target1)
.constructor(constructor1.clone())
.value(&[0x01, 0x00])
.build(&mut assembly)
.unwrap();
let ref2 = CustomAttributeBuilder::new()
.parent(target2)
.constructor(constructor2.clone())
.build(&mut assembly)
.unwrap();
let ref3 = CustomAttributeBuilder::new()
.parent(target3)
.constructor(constructor1)
.value(&[0x01, 0x00, 0x00, 0x00])
.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::CustomAttribute)
);
assert_eq!(
ref2.kind(),
ChangeRefKind::TableRow(TableId::CustomAttribute)
);
assert_eq!(
ref3.kind(),
ChangeRefKind::TableRow(TableId::CustomAttribute)
);
}
}
}