use crate::{
cilassembly::{ChangeRefRc, CilAssembly},
metadata::{
signatures::{encode_field_signature, SignatureField, TypeSignature},
tables::{
CodedIndex, CodedIndexType, ConstantBuilder, FieldAttributes, FieldBuilder, TableId,
TypeAttributes, TypeDefBuilder, TypeRefBuilder,
},
typesystem::ELEMENT_TYPE,
},
Error, Result,
};
struct EnumValueDefinition {
name: String,
value: i64,
}
pub struct EnumBuilder {
name: String,
namespace: Option<String>,
visibility: u32,
attributes: u32,
underlying_type: TypeSignature,
values: Vec<EnumValueDefinition>,
}
impl EnumBuilder {
#[must_use]
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
namespace: None,
visibility: TypeAttributes::PUBLIC,
attributes: TypeAttributes::SEALED,
underlying_type: TypeSignature::I4, values: Vec::new(),
}
}
#[must_use]
pub fn namespace(mut self, namespace: &str) -> Self {
self.namespace = Some(namespace.to_string());
self
}
#[must_use]
pub fn public(mut self) -> Self {
self.visibility = TypeAttributes::PUBLIC;
self
}
#[must_use]
pub fn internal(mut self) -> Self {
self.visibility = TypeAttributes::NOT_PUBLIC;
self
}
#[must_use]
pub fn underlying_type(mut self, underlying_type: TypeSignature) -> Self {
self.underlying_type = underlying_type;
self
}
#[must_use]
pub fn value(mut self, name: &str, value: i64) -> Self {
self.values.push(EnumValueDefinition {
name: name.to_string(),
value,
});
self
}
pub fn build(self, assembly: &mut CilAssembly) -> Result<ChangeRefRc> {
if self.name.is_empty() {
return Err(Error::ModificationInvalid(
"Enum name cannot be empty".to_string(),
));
}
let mut typedef_builder = TypeDefBuilder::new()
.name(&self.name)
.flags(self.visibility | self.attributes);
if let Some(namespace) = &self.namespace {
typedef_builder = typedef_builder.namespace(namespace);
}
if let Some(core_lib_ref) = assembly.find_core_library_ref() {
let system_enum_typeref = TypeRefBuilder::new()
.name("Enum")
.namespace("System")
.resolution_scope(core_lib_ref)
.build(assembly)?;
let typeref_placeholder = system_enum_typeref.placeholder_token().ok_or_else(|| {
Error::ModificationInvalid(
"Failed to get placeholder token for System.Enum typeref".to_string(),
)
})?;
let extends_index = CodedIndex::new(
TableId::TypeRef,
typeref_placeholder.row(),
CodedIndexType::TypeDefOrRef,
);
typedef_builder = typedef_builder.extends(extends_index);
}
let enum_ref = typedef_builder.build(assembly)?;
let value_field_signature = SignatureField {
modifiers: Vec::new(),
base: self.underlying_type.clone(),
};
let value_field_sig_bytes = encode_field_signature(&value_field_signature)?;
FieldBuilder::new()
.name("value__")
.flags(
FieldAttributes::PRIVATE
| FieldAttributes::SPECIAL_NAME
| FieldAttributes::RTSPECIAL_NAME,
)
.signature(&value_field_sig_bytes)
.build(assembly)?;
for enum_value in self.values {
let enum_field_signature = SignatureField {
modifiers: Vec::new(),
base: self.underlying_type.clone(),
};
let enum_field_sig_bytes = encode_field_signature(&enum_field_signature)?;
let field_ref = FieldBuilder::new()
.name(&enum_value.name)
.flags(FieldAttributes::PUBLIC | FieldAttributes::STATIC | FieldAttributes::LITERAL)
.signature(&enum_field_sig_bytes)
.build(assembly)?;
let field_placeholder = field_ref.placeholder_token().ok_or_else(|| {
Error::ModificationInvalid(
"Failed to get placeholder token for enum field".to_string(),
)
})?;
let constant_value = match self.underlying_type {
TypeSignature::I1 => {
let val = i8::try_from(enum_value.value).map_err(|_| {
malformed_error!("Enum value {} exceeds i8 range", enum_value.value)
})?;
vec![val.to_le_bytes()[0]]
}
TypeSignature::U1 => {
let val = u8::try_from(enum_value.value).map_err(|_| {
malformed_error!("Enum value {} exceeds u8 range", enum_value.value)
})?;
vec![val]
}
TypeSignature::I2 => {
let val = i16::try_from(enum_value.value).map_err(|_| {
malformed_error!("Enum value {} exceeds i16 range", enum_value.value)
})?;
val.to_le_bytes().to_vec()
}
TypeSignature::U2 => {
let val = u16::try_from(enum_value.value).map_err(|_| {
malformed_error!("Enum value {} exceeds u16 range", enum_value.value)
})?;
val.to_le_bytes().to_vec()
}
TypeSignature::I4 => {
let val = i32::try_from(enum_value.value).map_err(|_| {
malformed_error!("Enum value {} exceeds i32 range", enum_value.value)
})?;
val.to_le_bytes().to_vec()
}
TypeSignature::U4 => {
let val = u32::try_from(enum_value.value).map_err(|_| {
malformed_error!("Enum value {} exceeds u32 range", enum_value.value)
})?;
val.to_le_bytes().to_vec()
}
TypeSignature::I8 => {
let val = enum_value.value;
val.to_le_bytes().to_vec()
}
TypeSignature::U8 => {
let val = u64::try_from(enum_value.value).map_err(|_| {
malformed_error!("Enum value {} exceeds u64 range", enum_value.value)
})?;
val.to_le_bytes().to_vec()
}
_ => {
return Err(Error::ModificationInvalid(format!(
"Unsupported enum underlying type: {:?}",
self.underlying_type
)));
}
};
let element_type = match self.underlying_type {
TypeSignature::I1 => ELEMENT_TYPE::I1,
TypeSignature::U1 => ELEMENT_TYPE::U1,
TypeSignature::I2 => ELEMENT_TYPE::I2,
TypeSignature::U2 => ELEMENT_TYPE::U2,
TypeSignature::I4 => ELEMENT_TYPE::I4,
TypeSignature::U4 => ELEMENT_TYPE::U4,
TypeSignature::I8 => ELEMENT_TYPE::I8,
TypeSignature::U8 => ELEMENT_TYPE::U8,
_ => {
return Err(Error::ModificationInvalid(format!(
"Unsupported enum underlying type: {:?}",
self.underlying_type
)));
}
};
ConstantBuilder::new()
.element_type(element_type)
.parent(CodedIndex::new(
TableId::Field,
field_placeholder.row(),
CodedIndexType::HasConstant,
))
.value(&constant_value)
.build(assembly)?;
}
Ok(enum_ref)
}
}
impl Default for EnumBuilder {
fn default() -> Self {
Self::new("DefaultEnum")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cilassembly::{changes::ChangeRefKind, CilAssembly},
metadata::{cilassemblyview::CilAssemblyView, signatures::TypeSignature, tables::TableId},
};
use std::path::PathBuf;
fn get_test_assembly() -> Result<CilAssembly> {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
let view = CilAssemblyView::from_path(&path)?;
Ok(CilAssembly::new(view))
}
#[test]
fn test_simple_enum() -> Result<()> {
let mut assembly = get_test_assembly()?;
let enum_ref = EnumBuilder::new("Color")
.public()
.namespace("MyApp.Enums")
.value("Red", 0)
.value("Green", 1)
.value("Blue", 2)
.build(&mut assembly)?;
assert_eq!(enum_ref.kind(), ChangeRefKind::TableRow(TableId::TypeDef));
Ok(())
}
#[test]
fn test_byte_enum() -> Result<()> {
let mut assembly = get_test_assembly()?;
let enum_ref = EnumBuilder::new("Status")
.public()
.underlying_type(TypeSignature::U1) .value("Unknown", 0)
.value("Pending", 1)
.value("Complete", 255)
.build(&mut assembly)?;
assert_eq!(enum_ref.kind(), ChangeRefKind::TableRow(TableId::TypeDef));
Ok(())
}
#[test]
fn test_flags_enum() -> Result<()> {
let mut assembly = get_test_assembly()?;
let enum_ref = EnumBuilder::new("FileAccess")
.public()
.value("None", 0)
.value("Read", 1)
.value("Write", 2)
.value("ReadWrite", 3)
.build(&mut assembly)?;
assert_eq!(enum_ref.kind(), ChangeRefKind::TableRow(TableId::TypeDef));
Ok(())
}
#[test]
fn test_long_enum() -> Result<()> {
let mut assembly = get_test_assembly()?;
let enum_ref = EnumBuilder::new("LargeValues")
.public()
.underlying_type(TypeSignature::I8) .value("Small", 1)
.value("Large", 9223372036854775807) .build(&mut assembly)?;
assert_eq!(enum_ref.kind(), ChangeRefKind::TableRow(TableId::TypeDef));
Ok(())
}
#[test]
fn test_internal_enum() -> Result<()> {
let mut assembly = get_test_assembly()?;
let enum_ref = EnumBuilder::new("InternalEnum")
.internal()
.value("Value1", 10)
.value("Value2", 20)
.build(&mut assembly)?;
assert_eq!(enum_ref.kind(), ChangeRefKind::TableRow(TableId::TypeDef));
Ok(())
}
#[test]
fn test_empty_enum() -> Result<()> {
let mut assembly = get_test_assembly()?;
let enum_ref = EnumBuilder::new("EmptyEnum")
.public()
.build(&mut assembly)?;
assert_eq!(enum_ref.kind(), ChangeRefKind::TableRow(TableId::TypeDef));
Ok(())
}
#[test]
fn test_empty_name_fails() {
let mut assembly = get_test_assembly().unwrap();
let result = EnumBuilder::new("").public().build(&mut assembly);
assert!(result.is_err());
}
}