use crate::{
cilassembly::{ChangeRefRc, CilAssembly},
metadata::{
tables::{FieldLayoutRaw, TableDataOwned, TableId},
token::Token,
},
Error, Result,
};
pub struct FieldLayoutBuilder {
field_offset: Option<u32>,
field: Option<u32>,
}
impl Default for FieldLayoutBuilder {
fn default() -> Self {
Self::new()
}
}
impl FieldLayoutBuilder {
#[must_use]
pub fn new() -> Self {
Self {
field_offset: None,
field: None,
}
}
#[must_use]
pub fn field_offset(mut self, offset: u32) -> Self {
self.field_offset = Some(offset);
self
}
#[must_use]
pub fn field(mut self, field: u32) -> Self {
self.field = Some(field);
self
}
pub fn build(self, assembly: &mut CilAssembly) -> Result<ChangeRefRc> {
let field_offset = self
.field_offset
.ok_or_else(|| Error::ModificationInvalid("Field offset is required".to_string()))?;
let field = self
.field
.ok_or_else(|| Error::ModificationInvalid("Field reference is required".to_string()))?;
if field == 0 {
return Err(Error::ModificationInvalid(
"Field row index cannot be 0".to_string(),
));
}
if field_offset == u32::MAX {
return Err(Error::ModificationInvalid(
"Field offset cannot be 0xFFFFFFFF (reserved value)".to_string(),
));
}
let rid = assembly.next_rid(TableId::FieldLayout)?;
let token_value = ((TableId::FieldLayout as u32) << 24) | rid;
let token = Token::new(token_value);
let field_layout_raw = FieldLayoutRaw {
rid,
token,
offset: 0, field_offset,
field,
};
assembly.table_row_add(
TableId::FieldLayout,
TableDataOwned::FieldLayout(field_layout_raw),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cilassembly::{ChangeRefKind, CilAssembly},
metadata::cilassemblyview::CilAssemblyView,
};
use std::path::PathBuf;
#[test]
fn test_field_layout_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 field_row = 1u32;
let layout_ref = FieldLayoutBuilder::new()
.field(field_row)
.field_offset(0)
.build(&mut assembly)
.unwrap();
assert_eq!(
layout_ref.kind(),
ChangeRefKind::TableRow(TableId::FieldLayout)
);
}
}
#[test]
fn test_field_layout_builder_different_offsets() {
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 field1 = 1u32; let field2 = 2u32; let field3 = 3u32; let field4 = 4u32;
let layout1 = FieldLayoutBuilder::new()
.field(field1)
.field_offset(0)
.build(&mut assembly)
.unwrap();
let layout2 = FieldLayoutBuilder::new()
.field(field2)
.field_offset(4)
.build(&mut assembly)
.unwrap();
let layout3 = FieldLayoutBuilder::new()
.field(field3)
.field_offset(8)
.build(&mut assembly)
.unwrap();
let layout4 = FieldLayoutBuilder::new()
.field(field4)
.field_offset(64)
.build(&mut assembly)
.unwrap();
assert_eq!(
layout1.kind(),
ChangeRefKind::TableRow(TableId::FieldLayout)
);
assert_eq!(
layout2.kind(),
ChangeRefKind::TableRow(TableId::FieldLayout)
);
assert_eq!(
layout3.kind(),
ChangeRefKind::TableRow(TableId::FieldLayout)
);
assert_eq!(
layout4.kind(),
ChangeRefKind::TableRow(TableId::FieldLayout)
);
assert!(!std::sync::Arc::ptr_eq(&layout1, &layout2));
assert!(!std::sync::Arc::ptr_eq(&layout1, &layout3));
assert!(!std::sync::Arc::ptr_eq(&layout1, &layout4));
}
}
#[test]
fn test_field_layout_builder_union_layout() {
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 int_field = 1u32; let float_field = 2u32;
let int_layout = FieldLayoutBuilder::new()
.field(int_field)
.field_offset(0)
.build(&mut assembly)
.unwrap();
let float_layout = FieldLayoutBuilder::new()
.field(float_field)
.field_offset(0) .build(&mut assembly)
.unwrap();
assert!(!std::sync::Arc::ptr_eq(&int_layout, &float_layout));
assert_eq!(
int_layout.kind(),
ChangeRefKind::TableRow(TableId::FieldLayout)
);
assert_eq!(
float_layout.kind(),
ChangeRefKind::TableRow(TableId::FieldLayout)
);
}
}
#[test]
fn test_field_layout_builder_large_offsets() {
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 field_row = 1u32;
let large_offset = 1024 * 1024; let layout_ref = FieldLayoutBuilder::new()
.field(field_row)
.field_offset(large_offset)
.build(&mut assembly)
.unwrap();
assert_eq!(
layout_ref.kind(),
ChangeRefKind::TableRow(TableId::FieldLayout)
);
}
}
#[test]
fn test_field_layout_builder_missing_field_offset() {
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 field_row = 1u32;
let result = FieldLayoutBuilder::new()
.field(field_row)
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_field_layout_builder_missing_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 result = FieldLayoutBuilder::new()
.field_offset(4)
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_field_layout_builder_zero_field_rid() {
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_field = 0u32;
let result = FieldLayoutBuilder::new()
.field(invalid_field)
.field_offset(0)
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_field_layout_builder_reserved_offset() {
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 field_row = 1u32;
let result = FieldLayoutBuilder::new()
.field(field_row)
.field_offset(u32::MAX) .build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_field_layout_builder_multiple_layouts() {
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 field1 = 1u32; let field2 = 2u32; let field3 = 3u32; let field4 = 4u32;
let layout1 = FieldLayoutBuilder::new()
.field(field1)
.field_offset(0) .build(&mut assembly)
.unwrap();
let layout2 = FieldLayoutBuilder::new()
.field(field2)
.field_offset(4) .build(&mut assembly)
.unwrap();
let layout3 = FieldLayoutBuilder::new()
.field(field3)
.field_offset(8) .build(&mut assembly)
.unwrap();
let layout4 = FieldLayoutBuilder::new()
.field(field4)
.field_offset(16) .build(&mut assembly)
.unwrap();
assert!(!std::sync::Arc::ptr_eq(&layout1, &layout2));
assert!(!std::sync::Arc::ptr_eq(&layout1, &layout3));
assert!(!std::sync::Arc::ptr_eq(&layout1, &layout4));
assert!(!std::sync::Arc::ptr_eq(&layout2, &layout3));
assert!(!std::sync::Arc::ptr_eq(&layout2, &layout4));
assert!(!std::sync::Arc::ptr_eq(&layout3, &layout4));
assert_eq!(
layout1.kind(),
ChangeRefKind::TableRow(TableId::FieldLayout)
);
assert_eq!(
layout2.kind(),
ChangeRefKind::TableRow(TableId::FieldLayout)
);
assert_eq!(
layout3.kind(),
ChangeRefKind::TableRow(TableId::FieldLayout)
);
assert_eq!(
layout4.kind(),
ChangeRefKind::TableRow(TableId::FieldLayout)
);
}
}
#[test]
fn test_field_layout_builder_realistic_struct() {
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 x_field = 1u32; let y_field = 2u32; let z_field = 3u32; let flags_field = 4u32;
let x_layout_ref = FieldLayoutBuilder::new()
.field(x_field)
.field_offset(0) .build(&mut assembly)
.unwrap();
let y_layout_ref = FieldLayoutBuilder::new()
.field(y_field)
.field_offset(4) .build(&mut assembly)
.unwrap();
let z_layout_ref = FieldLayoutBuilder::new()
.field(z_field)
.field_offset(8) .build(&mut assembly)
.unwrap();
let flags_layout_ref = FieldLayoutBuilder::new()
.field(flags_field)
.field_offset(12) .build(&mut assembly)
.unwrap();
assert_eq!(
x_layout_ref.kind(),
ChangeRefKind::TableRow(TableId::FieldLayout)
);
assert_eq!(
y_layout_ref.kind(),
ChangeRefKind::TableRow(TableId::FieldLayout)
);
assert_eq!(
z_layout_ref.kind(),
ChangeRefKind::TableRow(TableId::FieldLayout)
);
assert_eq!(
flags_layout_ref.kind(),
ChangeRefKind::TableRow(TableId::FieldLayout)
);
assert!(!std::sync::Arc::ptr_eq(&x_layout_ref, &y_layout_ref));
assert!(!std::sync::Arc::ptr_eq(&x_layout_ref, &z_layout_ref));
assert!(!std::sync::Arc::ptr_eq(&x_layout_ref, &flags_layout_ref));
assert!(!std::sync::Arc::ptr_eq(&y_layout_ref, &z_layout_ref));
assert!(!std::sync::Arc::ptr_eq(&y_layout_ref, &flags_layout_ref));
assert!(!std::sync::Arc::ptr_eq(&z_layout_ref, &flags_layout_ref));
}
}
#[test]
fn test_field_layout_builder_performance_alignment() {
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 hot_field = 1u32; let cold_field = 2u32;
let hot_layout_ref = FieldLayoutBuilder::new()
.field(hot_field)
.field_offset(0)
.build(&mut assembly)
.unwrap();
let cold_layout_ref = FieldLayoutBuilder::new()
.field(cold_field)
.field_offset(64)
.build(&mut assembly)
.unwrap();
assert_eq!(
hot_layout_ref.kind(),
ChangeRefKind::TableRow(TableId::FieldLayout)
);
assert_eq!(
cold_layout_ref.kind(),
ChangeRefKind::TableRow(TableId::FieldLayout)
);
assert!(!std::sync::Arc::ptr_eq(&hot_layout_ref, &cold_layout_ref));
}
}
}