mod encoder;
mod parser;
mod types;
pub use encoder::*;
pub use parser::{
parse_custom_attribute_blob, parse_custom_attribute_blob_with_registry,
parse_custom_attribute_data, parse_custom_attribute_data_with_registry,
};
pub use types::*;
#[cfg(test)]
mod tests {
use crate::metadata::customattributes::{
encode_custom_attribute_value, parse_custom_attribute_data,
parse_custom_attribute_data_with_registry, CustomAttributeArgument,
CustomAttributeNamedArgument, CustomAttributeValue,
};
use crate::metadata::typesystem::CilFlavor;
use crate::test::factories::metadata::customattributes::{
create_constructor_with_params_and_registry, create_empty_method,
create_method_with_params, get_test_type_registry,
};
macro_rules! assert_arg_eq {
($parsed:expr, $original:expr, $variant:ident, $msg:expr) => {
match (&$parsed, &$original) {
(CustomAttributeArgument::$variant(p), CustomAttributeArgument::$variant(o)) => {
assert_eq!(p, o, "{}", $msg);
}
_ => panic!(
"{}: expected {} variant, got {:?} vs {:?}",
$msg,
stringify!($variant),
$parsed,
$original
),
}
};
}
#[test]
fn test_roundtrip_empty_custom_attribute() {
let original = CustomAttributeValue {
fixed_args: vec![],
named_args: vec![],
};
let encoded = encode_custom_attribute_value(&original).unwrap();
let method = create_empty_method();
let parsed = parse_custom_attribute_data(&encoded, &method.params).unwrap();
assert_eq!(parsed.fixed_args.len(), original.fixed_args.len());
assert_eq!(parsed.named_args.len(), original.named_args.len());
}
#[test]
fn test_roundtrip_boolean_arguments() {
let original = CustomAttributeValue {
fixed_args: vec![
CustomAttributeArgument::Bool(true),
CustomAttributeArgument::Bool(false),
],
named_args: vec![],
};
let encoded = encode_custom_attribute_value(&original).unwrap();
let method = create_method_with_params(vec![CilFlavor::Boolean, CilFlavor::Boolean]);
let parsed = parse_custom_attribute_data(&encoded, &method.params).unwrap();
assert_eq!(parsed.fixed_args.len(), 2);
assert_arg_eq!(
parsed.fixed_args[0],
original.fixed_args[0],
Bool,
"Bool arg 0"
);
assert_arg_eq!(
parsed.fixed_args[1],
original.fixed_args[1],
Bool,
"Bool arg 1"
);
}
#[test]
fn test_roundtrip_integer_arguments() {
let original = CustomAttributeValue {
fixed_args: vec![
CustomAttributeArgument::I1(-128),
CustomAttributeArgument::U1(255),
CustomAttributeArgument::I2(-32768),
CustomAttributeArgument::U2(65535),
CustomAttributeArgument::I4(-2147483648),
CustomAttributeArgument::U4(4294967295),
CustomAttributeArgument::I8(-9223372036854775808),
CustomAttributeArgument::U8(18446744073709551615),
],
named_args: vec![],
};
let encoded = encode_custom_attribute_value(&original).unwrap();
let method = create_method_with_params(vec![
CilFlavor::I1,
CilFlavor::U1,
CilFlavor::I2,
CilFlavor::U2,
CilFlavor::I4,
CilFlavor::U4,
CilFlavor::I8,
CilFlavor::U8,
]);
let parsed = parse_custom_attribute_data(&encoded, &method.params).unwrap();
assert_eq!(parsed.fixed_args.len(), 8);
assert_arg_eq!(parsed.fixed_args[0], original.fixed_args[0], I1, "I1");
assert_arg_eq!(parsed.fixed_args[1], original.fixed_args[1], U1, "U1");
assert_arg_eq!(parsed.fixed_args[2], original.fixed_args[2], I2, "I2");
assert_arg_eq!(parsed.fixed_args[3], original.fixed_args[3], U2, "U2");
assert_arg_eq!(parsed.fixed_args[4], original.fixed_args[4], I4, "I4");
assert_arg_eq!(parsed.fixed_args[5], original.fixed_args[5], U4, "U4");
assert_arg_eq!(parsed.fixed_args[6], original.fixed_args[6], I8, "I8");
assert_arg_eq!(parsed.fixed_args[7], original.fixed_args[7], U8, "U8");
}
#[test]
fn test_roundtrip_floating_point_arguments() {
let original = CustomAttributeValue {
fixed_args: vec![
CustomAttributeArgument::R4(std::f32::consts::PI),
CustomAttributeArgument::R8(std::f64::consts::E),
],
named_args: vec![],
};
let encoded = encode_custom_attribute_value(&original).unwrap();
let method = create_method_with_params(vec![CilFlavor::R4, CilFlavor::R8]);
let parsed = parse_custom_attribute_data(&encoded, &method.params).unwrap();
assert_eq!(parsed.fixed_args.len(), 2);
match (&parsed.fixed_args[0], &original.fixed_args[0]) {
(CustomAttributeArgument::R4(p), CustomAttributeArgument::R4(o)) => {
assert!((p - o).abs() < f32::EPSILON);
}
_ => panic!("R4 type mismatch"),
}
match (&parsed.fixed_args[1], &original.fixed_args[1]) {
(CustomAttributeArgument::R8(p), CustomAttributeArgument::R8(o)) => {
assert!((p - o).abs() < f64::EPSILON);
}
_ => panic!("R8 type mismatch"),
}
}
#[test]
fn test_roundtrip_character_argument() {
let original = CustomAttributeValue {
fixed_args: vec![
CustomAttributeArgument::Char('A'),
CustomAttributeArgument::Char('π'),
CustomAttributeArgument::Char('Z'), ],
named_args: vec![],
};
let encoded = encode_custom_attribute_value(&original).unwrap();
let method =
create_method_with_params(vec![CilFlavor::Char, CilFlavor::Char, CilFlavor::Char]);
let parsed = parse_custom_attribute_data(&encoded, &method.params).unwrap();
assert_eq!(parsed.fixed_args.len(), 3);
for (i, (parsed_arg, orig_arg)) in parsed
.fixed_args
.iter()
.zip(original.fixed_args.iter())
.enumerate()
{
match (parsed_arg, orig_arg) {
(CustomAttributeArgument::Char(p), CustomAttributeArgument::Char(o)) => {
assert_eq!(p, o, "Character mismatch at index {i}");
}
_ => panic!("Character type mismatch at index {i}"),
}
}
}
#[test]
fn test_roundtrip_string_arguments() {
let original = CustomAttributeValue {
fixed_args: vec![
CustomAttributeArgument::String("Hello, World!".to_string()),
CustomAttributeArgument::String("".to_string()), CustomAttributeArgument::String("Unicode: 你好世界 🌍".to_string()),
],
named_args: vec![],
};
let encoded = encode_custom_attribute_value(&original).unwrap();
let method = create_method_with_params(vec![
CilFlavor::String,
CilFlavor::String,
CilFlavor::String,
]);
let parsed = parse_custom_attribute_data(&encoded, &method.params).unwrap();
assert_eq!(parsed.fixed_args.len(), 3);
for (i, (parsed_arg, orig_arg)) in parsed
.fixed_args
.iter()
.zip(original.fixed_args.iter())
.enumerate()
{
match (parsed_arg, orig_arg) {
(CustomAttributeArgument::String(p), CustomAttributeArgument::String(o)) => {
assert_eq!(p, o, "String mismatch at index {i}");
}
_ => panic!("String type mismatch at index {i}"),
}
}
}
#[test]
fn test_roundtrip_type_arguments() {
let original = CustomAttributeValue {
fixed_args: vec![
CustomAttributeArgument::Type("System.String".to_string()),
CustomAttributeArgument::Type(
"System.Collections.Generic.List`1[System.Int32]".to_string(),
),
],
named_args: vec![],
};
let encoded = encode_custom_attribute_value(&original).unwrap();
let method = create_method_with_params(vec![CilFlavor::Class, CilFlavor::Class]);
let parsed = parse_custom_attribute_data(&encoded, &method.params).unwrap();
assert_eq!(parsed.fixed_args.len(), 2);
for (i, (parsed_arg, orig_arg)) in parsed
.fixed_args
.iter()
.zip(original.fixed_args.iter())
.enumerate()
{
match (parsed_arg, orig_arg) {
(CustomAttributeArgument::Type(p), CustomAttributeArgument::Type(o)) => {
assert_eq!(p, o, "Type mismatch at index {i}");
}
(CustomAttributeArgument::String(p), CustomAttributeArgument::Type(o)) => {
assert_eq!(p, o, "Type converted to string at index {i}");
}
_ => panic!(
"Type argument type mismatch at index {i}: {parsed_arg:?} vs {orig_arg:?}"
),
}
}
}
#[test]
fn test_roundtrip_array_arguments() {
let original = CustomAttributeValue {
fixed_args: vec![
CustomAttributeArgument::Array(vec![
CustomAttributeArgument::I4(1),
CustomAttributeArgument::I4(2),
CustomAttributeArgument::I4(3),
]),
CustomAttributeArgument::Array(vec![
CustomAttributeArgument::String("first".to_string()),
CustomAttributeArgument::String("second".to_string()),
]),
CustomAttributeArgument::Array(vec![]), ],
named_args: vec![],
};
let encoded = encode_custom_attribute_value(&original).unwrap();
assert!(
encoded.len() > 10,
"Encoded array should have substantial size"
);
assert_eq!(encoded[0], 0x01);
assert_eq!(encoded[1], 0x00);
}
#[test]
fn test_roundtrip_enum_arguments() {
let original = CustomAttributeValue {
fixed_args: vec![
CustomAttributeArgument::Enum(
"System.AttributeTargets".to_string(),
Box::new(CustomAttributeArgument::I4(1)),
),
CustomAttributeArgument::Enum(
"TestEnum".to_string(),
Box::new(CustomAttributeArgument::I4(42)),
),
],
named_args: vec![],
};
let encoded = encode_custom_attribute_value(&original).unwrap();
let test_registry = get_test_type_registry();
let method = create_constructor_with_params_and_registry(
vec![CilFlavor::ValueType, CilFlavor::ValueType],
&test_registry,
);
let parsed =
parse_custom_attribute_data_with_registry(&encoded, &method.params, &test_registry)
.unwrap();
assert_eq!(parsed.fixed_args.len(), 2);
for (i, (parsed_arg, orig_arg)) in parsed
.fixed_args
.iter()
.zip(original.fixed_args.iter())
.enumerate()
{
match (parsed_arg, orig_arg) {
(
CustomAttributeArgument::Enum(_, p_val),
CustomAttributeArgument::Enum(_, o_val),
) => {
match (p_val.as_ref(), o_val.as_ref()) {
(CustomAttributeArgument::I4(p), CustomAttributeArgument::I4(o)) => {
assert_eq!(p, o, "Enum value mismatch at index {i}");
}
_ => panic!("Enum underlying type mismatch at index {i}"),
}
}
_ => panic!("Enum type mismatch at index {i}: {parsed_arg:?} vs {orig_arg:?}"),
}
}
}
#[test]
fn test_roundtrip_named_arguments() {
let original = CustomAttributeValue {
fixed_args: vec![],
named_args: vec![
CustomAttributeNamedArgument {
is_field: true,
name: "FieldValue".to_string(),
arg_type: "I4".to_string(),
value: CustomAttributeArgument::I4(42),
},
CustomAttributeNamedArgument {
is_field: false, name: "PropertyName".to_string(),
arg_type: "String".to_string(),
value: CustomAttributeArgument::String("TestValue".to_string()),
},
CustomAttributeNamedArgument {
is_field: true,
name: "BoolFlag".to_string(),
arg_type: "Boolean".to_string(),
value: CustomAttributeArgument::Bool(true),
},
],
};
let encoded = encode_custom_attribute_value(&original).unwrap();
let method = create_empty_method();
let parsed = parse_custom_attribute_data(&encoded, &method.params).unwrap();
assert_eq!(parsed.named_args.len(), 3);
let arg0 = &parsed.named_args[0];
assert!(arg0.is_field);
assert_eq!(arg0.name, "FieldValue");
assert_eq!(arg0.arg_type, "I4");
match &arg0.value {
CustomAttributeArgument::I4(val) => assert_eq!(*val, 42),
_ => panic!("Expected I4 value"),
}
let arg1 = &parsed.named_args[1];
assert!(!arg1.is_field);
assert_eq!(arg1.name, "PropertyName");
assert_eq!(arg1.arg_type, "String");
match &arg1.value {
CustomAttributeArgument::String(val) => assert_eq!(val, "TestValue"),
_ => panic!("Expected String value"),
}
let arg2 = &parsed.named_args[2];
assert!(arg2.is_field);
assert_eq!(arg2.name, "BoolFlag");
assert_eq!(arg2.arg_type, "Boolean");
match &arg2.value {
CustomAttributeArgument::Bool(val) => assert!(*val),
_ => panic!("Expected Bool value"),
}
}
#[test]
fn test_roundtrip_mixed_fixed_and_named_arguments() {
let original = CustomAttributeValue {
fixed_args: vec![
CustomAttributeArgument::String("Constructor Arg".to_string()),
CustomAttributeArgument::I4(123),
],
named_args: vec![CustomAttributeNamedArgument {
is_field: false,
name: "AdditionalInfo".to_string(),
arg_type: "String".to_string(),
value: CustomAttributeArgument::String("Extra Data".to_string()),
}],
};
let encoded = encode_custom_attribute_value(&original).unwrap();
let method = create_method_with_params(vec![CilFlavor::String, CilFlavor::I4]);
let parsed = parse_custom_attribute_data(&encoded, &method.params).unwrap();
assert_eq!(parsed.fixed_args.len(), 2);
match &parsed.fixed_args[0] {
CustomAttributeArgument::String(val) => assert_eq!(val, "Constructor Arg"),
_ => panic!("Expected String in fixed args"),
}
match &parsed.fixed_args[1] {
CustomAttributeArgument::I4(val) => assert_eq!(*val, 123),
_ => panic!("Expected I4 in fixed args"),
}
assert_eq!(parsed.named_args.len(), 1);
let named_arg = &parsed.named_args[0];
assert!(!named_arg.is_field);
assert_eq!(named_arg.name, "AdditionalInfo");
assert_eq!(named_arg.arg_type, "String");
match &named_arg.value {
CustomAttributeArgument::String(val) => assert_eq!(val, "Extra Data"),
_ => panic!("Expected String in named args"),
}
}
#[test]
fn test_roundtrip_edge_cases() {
let original = CustomAttributeValue {
fixed_args: vec![
CustomAttributeArgument::I1(i8::MIN),
CustomAttributeArgument::I1(i8::MAX),
CustomAttributeArgument::U1(u8::MIN),
CustomAttributeArgument::U1(u8::MAX),
CustomAttributeArgument::R4(0.0),
CustomAttributeArgument::R4(-0.0),
CustomAttributeArgument::R8(f64::INFINITY),
CustomAttributeArgument::R8(f64::NEG_INFINITY),
],
named_args: vec![],
};
let encoded = encode_custom_attribute_value(&original).unwrap();
let method = create_method_with_params(vec![
CilFlavor::I1,
CilFlavor::I1,
CilFlavor::U1,
CilFlavor::U1,
CilFlavor::R4,
CilFlavor::R4,
CilFlavor::R8,
CilFlavor::R8,
]);
let parsed = parse_custom_attribute_data(&encoded, &method.params).unwrap();
assert_eq!(parsed.fixed_args.len(), 8);
match &parsed.fixed_args[0] {
CustomAttributeArgument::I1(val) => assert_eq!(*val, i8::MIN),
_ => panic!("Expected I1 MIN"),
}
match &parsed.fixed_args[1] {
CustomAttributeArgument::I1(val) => assert_eq!(*val, i8::MAX),
_ => panic!("Expected I1 MAX"),
}
match &parsed.fixed_args[2] {
CustomAttributeArgument::U1(val) => assert_eq!(*val, u8::MIN),
_ => panic!("Expected U1 MIN"),
}
match &parsed.fixed_args[3] {
CustomAttributeArgument::U1(val) => assert_eq!(*val, u8::MAX),
_ => panic!("Expected U1 MAX"),
}
match &parsed.fixed_args[4] {
CustomAttributeArgument::R4(val) => assert_eq!(*val, 0.0),
_ => panic!("Expected R4 zero"),
}
match &parsed.fixed_args[5] {
CustomAttributeArgument::R4(val) => assert_eq!(*val, -0.0),
_ => panic!("Expected R4 negative zero"),
}
match &parsed.fixed_args[6] {
CustomAttributeArgument::R8(val) => assert_eq!(*val, f64::INFINITY),
_ => panic!("Expected R8 infinity"),
}
match &parsed.fixed_args[7] {
CustomAttributeArgument::R8(val) => assert_eq!(*val, f64::NEG_INFINITY),
_ => panic!("Expected R8 negative infinity"),
}
}
#[test]
fn test_roundtrip_large_data() {
let large_string = "A".repeat(1000);
let large_array: Vec<CustomAttributeArgument> =
(0..100).map(CustomAttributeArgument::I4).collect();
let original = CustomAttributeValue {
fixed_args: vec![
CustomAttributeArgument::String(large_string.clone()),
CustomAttributeArgument::Array(large_array.clone()),
],
named_args: vec![CustomAttributeNamedArgument {
is_field: true,
name: "LargeField".to_string(),
arg_type: "String".to_string(),
value: CustomAttributeArgument::String(large_string.clone()),
}],
};
let encoded = encode_custom_attribute_value(&original).unwrap();
assert!(
encoded.len() > 2000,
"Large data should produce substantial encoding"
);
assert_eq!(encoded[0], 0x01); assert_eq!(encoded[1], 0x00);
}
}