use crate::{
cilassembly::{ChangeRefRc, CilAssembly},
metadata::{
tables::{LocalScopeRaw, TableDataOwned, TableId},
token::Token,
},
Error, Result,
};
#[derive(Debug, Clone, Default)]
pub struct LocalScopeBuilder {
method: Option<u32>,
import_scope: Option<u32>,
variable_list: Option<u32>,
constant_list: Option<u32>,
start_offset: Option<u32>,
length: Option<u32>,
}
impl LocalScopeBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn method(mut self, method: u32) -> Self {
self.method = Some(method);
self
}
#[must_use]
pub fn import_scope(mut self, import_scope: u32) -> Self {
self.import_scope = Some(import_scope);
self
}
#[must_use]
pub fn variable_list(mut self, variable_list: u32) -> Self {
self.variable_list = Some(variable_list);
self
}
#[must_use]
pub fn constant_list(mut self, constant_list: u32) -> Self {
self.constant_list = Some(constant_list);
self
}
#[must_use]
pub fn start_offset(mut self, start_offset: u32) -> Self {
self.start_offset = Some(start_offset);
self
}
#[must_use]
pub fn length(mut self, length: u32) -> Self {
self.length = Some(length);
self
}
pub fn build(self, assembly: &mut CilAssembly) -> Result<ChangeRefRc> {
let method = self.method.ok_or_else(|| {
Error::ModificationInvalid("Method row is required for LocalScope".to_string())
})?;
let start_offset = self.start_offset.ok_or_else(|| {
Error::ModificationInvalid("Start offset is required for LocalScope".to_string())
})?;
let length = self.length.ok_or_else(|| {
Error::ModificationInvalid("Length is required for LocalScope".to_string())
})?;
if method == 0 {
return Err(Error::ModificationInvalid(
"Method row cannot be 0".to_string(),
));
}
if length == 0 {
return Err(Error::ModificationInvalid(
"LocalScope length cannot be zero".to_string(),
));
}
let rid = assembly.next_rid(TableId::LocalScope)?;
let token = Token::from_parts(TableId::LocalScope, rid);
let local_scope_raw = LocalScopeRaw {
rid,
token,
offset: 0, method,
import_scope: self.import_scope.unwrap_or(0),
variable_list: self.variable_list.unwrap_or(0),
constant_list: self.constant_list.unwrap_or(0),
start_offset,
length,
};
assembly.table_row_add(
TableId::LocalScope,
TableDataOwned::LocalScope(local_scope_raw),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cilassembly::ChangeRefKind, test::factories::table::assemblyref::get_test_assembly,
};
#[test]
fn test_localscope_builder_basic() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = LocalScopeBuilder::new()
.method(1)
.start_offset(0x10)
.length(0x50)
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::LocalScope));
Ok(())
}
#[test]
fn test_localscope_builder_default() -> Result<()> {
let builder = LocalScopeBuilder::default();
assert!(builder.method.is_none());
assert!(builder.import_scope.is_none());
assert!(builder.variable_list.is_none());
assert!(builder.constant_list.is_none());
assert!(builder.start_offset.is_none());
assert!(builder.length.is_none());
Ok(())
}
#[test]
fn test_localscope_builder_with_all_fields() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = LocalScopeBuilder::new()
.method(2)
.import_scope(1)
.variable_list(5)
.constant_list(2)
.start_offset(0x00)
.length(0x100)
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::LocalScope));
Ok(())
}
#[test]
fn test_localscope_builder_missing_method() -> Result<()> {
let mut assembly = get_test_assembly()?;
let result = LocalScopeBuilder::new()
.start_offset(0x10)
.length(0x50)
.build(&mut assembly);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Method row is required"));
Ok(())
}
#[test]
fn test_localscope_builder_missing_start_offset() -> Result<()> {
let mut assembly = get_test_assembly()?;
let result = LocalScopeBuilder::new()
.method(1)
.length(0x50)
.build(&mut assembly);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Start offset is required"));
Ok(())
}
#[test]
fn test_localscope_builder_missing_length() -> Result<()> {
let mut assembly = get_test_assembly()?;
let result = LocalScopeBuilder::new()
.method(1)
.start_offset(0x10)
.build(&mut assembly);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Length is required"));
Ok(())
}
#[test]
fn test_localscope_builder_zero_length() -> Result<()> {
let mut assembly = get_test_assembly()?;
let result = LocalScopeBuilder::new()
.method(1)
.start_offset(0x10)
.length(0)
.build(&mut assembly);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("length cannot be zero"));
Ok(())
}
#[test]
fn test_localscope_builder_zero_method_row() -> Result<()> {
let mut assembly = get_test_assembly()?;
let result = LocalScopeBuilder::new()
.method(0) .start_offset(0x10)
.length(0x50)
.build(&mut assembly);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Method row cannot be 0"));
Ok(())
}
#[test]
fn test_localscope_builder_clone() {
let builder1 = LocalScopeBuilder::new()
.method(1)
.start_offset(0x10)
.length(0x50);
let builder2 = builder1.clone();
assert_eq!(builder1.method, builder2.method);
assert_eq!(builder1.start_offset, builder2.start_offset);
assert_eq!(builder1.length, builder2.length);
}
#[test]
fn test_localscope_builder_debug() {
let builder = LocalScopeBuilder::new().method(1).start_offset(0x10);
let debug_str = format!("{builder:?}");
assert!(debug_str.contains("LocalScopeBuilder"));
}
}