use crate::{
cilassembly::{ChangeRefRc, CilAssembly},
metadata::{
tables::{FieldRaw, TableDataOwned, TableId},
token::Token,
},
Error, Result,
};
pub struct FieldBuilder {
name: Option<String>,
flags: Option<u32>,
signature: Option<Vec<u8>>,
}
impl Default for FieldBuilder {
fn default() -> Self {
Self::new()
}
}
impl FieldBuilder {
#[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("Field name is required".to_string()))?;
let flags = self
.flags
.ok_or_else(|| Error::ModificationInvalid("Field flags are required".to_string()))?;
let signature = self
.signature
.ok_or_else(|| Error::ModificationInvalid("Field 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::Field)?;
let token = Token::from_parts(TableId::Field, rid);
let field_raw = FieldRaw {
rid,
token,
offset: 0, flags,
name: name_index,
signature: signature_index,
};
assembly.table_row_add(TableId::Field, TableDataOwned::Field(field_raw))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cilassembly::{ChangeRefKind, CilAssembly},
metadata::cilassemblyview::CilAssemblyView,
prelude::FieldAttributes,
};
use std::path::PathBuf;
#[test]
fn test_field_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 existing_field_count = assembly.original_table_row_count(TableId::Field);
let _expected_rid = existing_field_count + 1;
let string_signature = &[0x0E];
let ref_ = FieldBuilder::new()
.name("testField")
.flags(FieldAttributes::PRIVATE)
.signature(string_signature)
.build(&mut assembly)
.unwrap();
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::Field));
}
}
#[test]
fn test_field_builder_with_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 int32_signature = &[0x08];
let ref_ = FieldBuilder::new()
.name("PublicStaticField")
.flags(
FieldAttributes::PUBLIC | FieldAttributes::STATIC | FieldAttributes::INIT_ONLY,
)
.signature(int32_signature)
.build(&mut assembly)
.unwrap();
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::Field));
}
}
#[test]
fn test_field_builder_literal_field() {
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_signature = &[0x02];
let ref_ = FieldBuilder::new()
.name("ConstField")
.flags(
FieldAttributes::PRIVATE | FieldAttributes::LITERAL | FieldAttributes::STATIC,
)
.signature(bool_signature)
.build(&mut assembly)
.unwrap();
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::Field));
}
}
#[test]
fn test_field_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 = FieldBuilder::new()
.flags(FieldAttributes::PRIVATE)
.signature(&[0x08])
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_field_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 = FieldBuilder::new()
.name("testField")
.signature(&[0x08])
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_field_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 = FieldBuilder::new()
.name("testField")
.flags(FieldAttributes::PRIVATE)
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_field_builder_multiple_fields() {
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 signature = &[0x08];
let ref1 = FieldBuilder::new()
.name("Field1")
.flags(FieldAttributes::PRIVATE)
.signature(signature)
.build(&mut assembly)
.unwrap();
let ref2 = FieldBuilder::new()
.name("Field2")
.flags(FieldAttributes::PUBLIC)
.signature(signature)
.build(&mut assembly)
.unwrap();
assert!(!std::sync::Arc::ptr_eq(&ref1, &ref2));
assert_eq!(ref1.kind(), ChangeRefKind::TableRow(TableId::Field));
assert_eq!(ref2.kind(), ChangeRefKind::TableRow(TableId::Field));
}
}
}