use crate::error::{Error, Result};
use crate::reader::Reader;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
#[repr(u8)]
pub enum ElementType {
End = 0x00,
Void = 0x01,
Boolean = 0x02,
Char = 0x03,
I1 = 0x04,
U1 = 0x05,
I2 = 0x06,
U2 = 0x07,
I4 = 0x08,
U4 = 0x09,
I8 = 0x0A,
U8 = 0x0B,
R4 = 0x0C,
R8 = 0x0D,
String = 0x0E,
Ptr = 0x0F,
ByRef = 0x10,
ValueType = 0x11,
Class = 0x12,
Var = 0x13,
Array = 0x14,
GenericInst = 0x15,
TypedByRef = 0x16,
IntPtr = 0x18,
UIntPtr = 0x19,
FnPtr = 0x1B,
Object = 0x1C,
SzArray = 0x1D,
MVar = 0x1E,
CModReqd = 0x1F,
CModOpt = 0x20,
Internal = 0x21,
Modifier = 0x40,
Sentinel = 0x41,
Pinned = 0x45,
}
impl ElementType {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0x00 => Some(Self::End),
0x01 => Some(Self::Void),
0x02 => Some(Self::Boolean),
0x03 => Some(Self::Char),
0x04 => Some(Self::I1),
0x05 => Some(Self::U1),
0x06 => Some(Self::I2),
0x07 => Some(Self::U2),
0x08 => Some(Self::I4),
0x09 => Some(Self::U4),
0x0A => Some(Self::I8),
0x0B => Some(Self::U8),
0x0C => Some(Self::R4),
0x0D => Some(Self::R8),
0x0E => Some(Self::String),
0x0F => Some(Self::Ptr),
0x10 => Some(Self::ByRef),
0x11 => Some(Self::ValueType),
0x12 => Some(Self::Class),
0x13 => Some(Self::Var),
0x14 => Some(Self::Array),
0x15 => Some(Self::GenericInst),
0x16 => Some(Self::TypedByRef),
0x18 => Some(Self::IntPtr),
0x19 => Some(Self::UIntPtr),
0x1B => Some(Self::FnPtr),
0x1C => Some(Self::Object),
0x1D => Some(Self::SzArray),
0x1E => Some(Self::MVar),
0x1F => Some(Self::CModReqd),
0x20 => Some(Self::CModOpt),
0x21 => Some(Self::Internal),
0x40 => Some(Self::Modifier),
0x41 => Some(Self::Sentinel),
0x45 => Some(Self::Pinned),
_ => None,
}
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::End => "end",
Self::Void => "void",
Self::Boolean => "bool",
Self::Char => "char",
Self::I1 => "sbyte",
Self::U1 => "byte",
Self::I2 => "short",
Self::U2 => "ushort",
Self::I4 => "int",
Self::U4 => "uint",
Self::I8 => "long",
Self::U8 => "ulong",
Self::R4 => "float",
Self::R8 => "double",
Self::String => "string",
Self::Ptr => "ptr",
Self::ByRef => "byref",
Self::ValueType => "valuetype",
Self::Class => "class",
Self::Var => "!T",
Self::Array => "array",
Self::GenericInst => "generic",
Self::TypedByRef => "typedref",
Self::IntPtr => "nint",
Self::UIntPtr => "nuint",
Self::FnPtr => "fnptr",
Self::Object => "object",
Self::SzArray => "[]",
Self::MVar => "!!T",
Self::CModReqd => "modreq",
Self::CModOpt => "modopt",
Self::Internal => "internal",
Self::Modifier => "modifier",
Self::Sentinel => "...",
Self::Pinned => "pinned",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct CallingConvention(pub u8);
impl CallingConvention {
pub const DEFAULT: u8 = 0x00;
pub const VARARG: u8 = 0x05;
pub const FIELD: u8 = 0x06;
pub const LOCAL_SIG: u8 = 0x07;
pub const PROPERTY: u8 = 0x08;
pub const GENERIC: u8 = 0x10;
pub const HAS_THIS: u8 = 0x20;
pub const EXPLICIT_THIS: u8 = 0x40;
#[must_use]
pub fn is_method(self) -> bool {
let base = self.0 & 0x0F;
base == Self::DEFAULT || base == Self::VARARG
}
#[must_use]
pub fn is_field(self) -> bool {
(self.0 & 0x0F) == Self::FIELD
}
#[must_use]
pub fn is_property(self) -> bool {
(self.0 & 0x0F) == Self::PROPERTY
}
#[must_use]
pub fn has_this(self) -> bool {
(self.0 & Self::HAS_THIS) != 0
}
#[must_use]
pub fn is_generic(self) -> bool {
(self.0 & Self::GENERIC) != 0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum TypeSig {
Primitive(ElementType),
Class(u32),
ValueType(u32),
SzArray(Box<TypeSig>),
Array {
element_type: Box<TypeSig>,
rank: u32,
sizes: Vec<u32>,
lo_bounds: Vec<i32>,
},
Ptr(Box<TypeSig>),
ByRef(Box<TypeSig>),
GenericInst {
is_value_type: bool,
type_ref: u32,
type_args: Vec<TypeSig>,
},
Var(u32),
MVar(u32),
FnPtr(Box<MethodSig>),
Modified {
required: bool,
modifier: u32,
inner: Box<TypeSig>,
},
Pinned(Box<TypeSig>),
}
impl TypeSig {
pub fn parse(reader: &mut Reader<'_>) -> Result<Self> {
let elem = reader.read_u8()?;
match elem {
0x01 => Ok(TypeSig::Primitive(ElementType::Void)),
0x02 => Ok(TypeSig::Primitive(ElementType::Boolean)),
0x03 => Ok(TypeSig::Primitive(ElementType::Char)),
0x04 => Ok(TypeSig::Primitive(ElementType::I1)),
0x05 => Ok(TypeSig::Primitive(ElementType::U1)),
0x06 => Ok(TypeSig::Primitive(ElementType::I2)),
0x07 => Ok(TypeSig::Primitive(ElementType::U2)),
0x08 => Ok(TypeSig::Primitive(ElementType::I4)),
0x09 => Ok(TypeSig::Primitive(ElementType::U4)),
0x0A => Ok(TypeSig::Primitive(ElementType::I8)),
0x0B => Ok(TypeSig::Primitive(ElementType::U8)),
0x0C => Ok(TypeSig::Primitive(ElementType::R4)),
0x0D => Ok(TypeSig::Primitive(ElementType::R8)),
0x0E => Ok(TypeSig::Primitive(ElementType::String)),
0x16 => Ok(TypeSig::Primitive(ElementType::TypedByRef)),
0x18 => Ok(TypeSig::Primitive(ElementType::IntPtr)),
0x19 => Ok(TypeSig::Primitive(ElementType::UIntPtr)),
0x1C => Ok(TypeSig::Primitive(ElementType::Object)),
0x12 => {
let token = reader.read_compressed_uint()?;
Ok(TypeSig::Class(token))
}
0x11 => {
let token = reader.read_compressed_uint()?;
Ok(TypeSig::ValueType(token))
}
0x1D => {
let elem_type = TypeSig::parse(reader)?;
Ok(TypeSig::SzArray(Box::new(elem_type)))
}
0x14 => {
let elem_type = TypeSig::parse(reader)?;
let rank = reader.read_compressed_uint()?;
let num_sizes = reader.read_compressed_uint()?;
let mut sizes = Vec::with_capacity(num_sizes as usize);
for _ in 0..num_sizes {
sizes.push(reader.read_compressed_uint()?);
}
let num_lo_bounds = reader.read_compressed_uint()?;
let mut lo_bounds = Vec::with_capacity(num_lo_bounds as usize);
for _ in 0..num_lo_bounds {
lo_bounds.push(reader.read_compressed_int()?);
}
Ok(TypeSig::Array {
element_type: Box::new(elem_type),
rank,
sizes,
lo_bounds,
})
}
0x0F => {
let inner = TypeSig::parse(reader)?;
Ok(TypeSig::Ptr(Box::new(inner)))
}
0x10 => {
let inner = TypeSig::parse(reader)?;
Ok(TypeSig::ByRef(Box::new(inner)))
}
0x15 => {
let is_value_type = reader.read_u8()? == 0x11;
let type_ref = reader.read_compressed_uint()?;
let gen_arg_count = reader.read_compressed_uint()?;
let mut type_args = Vec::with_capacity(gen_arg_count as usize);
for _ in 0..gen_arg_count {
type_args.push(TypeSig::parse(reader)?);
}
Ok(TypeSig::GenericInst {
is_value_type,
type_ref,
type_args,
})
}
0x13 => {
let index = reader.read_compressed_uint()?;
Ok(TypeSig::Var(index))
}
0x1E => {
let index = reader.read_compressed_uint()?;
Ok(TypeSig::MVar(index))
}
0x1B => {
let method_sig = MethodSig::parse(reader)?;
Ok(TypeSig::FnPtr(Box::new(method_sig)))
}
0x1F => {
let modifier = reader.read_compressed_uint()?;
let inner = TypeSig::parse(reader)?;
Ok(TypeSig::Modified {
required: true,
modifier,
inner: Box::new(inner),
})
}
0x20 => {
let modifier = reader.read_compressed_uint()?;
let inner = TypeSig::parse(reader)?;
Ok(TypeSig::Modified {
required: false,
modifier,
inner: Box::new(inner),
})
}
0x45 => {
let inner = TypeSig::parse(reader)?;
Ok(TypeSig::Pinned(Box::new(inner)))
}
_ => Err(Error::InvalidBlob(reader.position())),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MethodSig {
pub calling_convention: CallingConvention,
pub generic_param_count: u32,
pub return_type: TypeSig,
pub params: Vec<TypeSig>,
pub sentinel: Option<usize>,
}
impl MethodSig {
pub fn parse(reader: &mut Reader<'_>) -> Result<Self> {
let cc = reader.read_u8()?;
let calling_convention = CallingConvention(cc);
let generic_param_count = if (cc & CallingConvention::GENERIC) != 0 {
reader.read_compressed_uint()?
} else {
0
};
let param_count = reader.read_compressed_uint()?;
let return_type = TypeSig::parse(reader)?;
let mut params = Vec::with_capacity(param_count as usize);
let mut sentinel = None;
for i in 0..param_count as usize {
if reader.remaining() > 0 {
let peek = reader.peek_u8()?;
if peek == 0x41 {
reader.read_u8()?; sentinel = Some(i);
}
}
params.push(TypeSig::parse(reader)?);
}
Ok(Self {
calling_convention,
generic_param_count,
return_type,
params,
sentinel,
})
}
pub fn parse_blob(data: &[u8]) -> Result<Self> {
let mut reader = Reader::new(data);
Self::parse(&mut reader)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FieldSig {
pub field_type: TypeSig,
}
impl FieldSig {
pub fn parse(reader: &mut Reader<'_>) -> Result<Self> {
let cc = reader.read_u8()?;
if cc != CallingConvention::FIELD {
return Err(Error::InvalidBlob(reader.position()));
}
let field_type = TypeSig::parse(reader)?;
Ok(Self { field_type })
}
pub fn parse_blob(data: &[u8]) -> Result<Self> {
let mut reader = Reader::new(data);
Self::parse(&mut reader)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PropertySig {
pub has_this: bool,
pub property_type: TypeSig,
pub params: Vec<TypeSig>,
}
impl PropertySig {
pub fn parse(reader: &mut Reader<'_>) -> Result<Self> {
let cc = reader.read_u8()?;
if (cc & 0x0F) != CallingConvention::PROPERTY {
return Err(Error::InvalidBlob(reader.position()));
}
let has_this = (cc & CallingConvention::HAS_THIS) != 0;
let param_count = reader.read_compressed_uint()?;
let property_type = TypeSig::parse(reader)?;
let mut params = Vec::with_capacity(param_count as usize);
for _ in 0..param_count {
params.push(TypeSig::parse(reader)?);
}
Ok(Self {
has_this,
property_type,
params,
})
}
pub fn parse_blob(data: &[u8]) -> Result<Self> {
let mut reader = Reader::new(data);
Self::parse(&mut reader)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LocalVarSig {
pub locals: Vec<TypeSig>,
}
impl LocalVarSig {
pub fn parse(reader: &mut Reader<'_>) -> Result<Self> {
let cc = reader.read_u8()?;
if cc != CallingConvention::LOCAL_SIG {
return Err(Error::InvalidBlob(reader.position()));
}
let count = reader.read_compressed_uint()?;
let mut locals = Vec::with_capacity(count as usize);
for _ in 0..count {
locals.push(TypeSig::parse(reader)?);
}
Ok(Self { locals })
}
pub fn parse_blob(data: &[u8]) -> Result<Self> {
let mut reader = Reader::new(data);
Self::parse(&mut reader)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_type_sig_primitives() {
let sig = TypeSig::parse(&mut Reader::new(&[0x01])).unwrap();
assert_eq!(sig, TypeSig::Primitive(ElementType::Void));
let sig = TypeSig::parse(&mut Reader::new(&[0x02])).unwrap();
assert_eq!(sig, TypeSig::Primitive(ElementType::Boolean));
let sig = TypeSig::parse(&mut Reader::new(&[0x08])).unwrap();
assert_eq!(sig, TypeSig::Primitive(ElementType::I4));
let sig = TypeSig::parse(&mut Reader::new(&[0x0E])).unwrap();
assert_eq!(sig, TypeSig::Primitive(ElementType::String));
let sig = TypeSig::parse(&mut Reader::new(&[0x1C])).unwrap();
assert_eq!(sig, TypeSig::Primitive(ElementType::Object));
}
#[test]
fn test_type_sig_class() {
let sig = TypeSig::parse(&mut Reader::new(&[0x12, 0x05])).unwrap();
assert_eq!(sig, TypeSig::Class(5));
}
#[test]
fn test_type_sig_valuetype() {
let sig = TypeSig::parse(&mut Reader::new(&[0x11, 0x09])).unwrap();
assert_eq!(sig, TypeSig::ValueType(9));
}
#[test]
fn test_type_sig_szarray() {
let sig = TypeSig::parse(&mut Reader::new(&[0x1D, 0x08])).unwrap();
assert_eq!(
sig,
TypeSig::SzArray(Box::new(TypeSig::Primitive(ElementType::I4)))
);
}
#[test]
fn test_type_sig_ptr() {
let sig = TypeSig::parse(&mut Reader::new(&[0x0F, 0x08])).unwrap();
assert_eq!(
sig,
TypeSig::Ptr(Box::new(TypeSig::Primitive(ElementType::I4)))
);
}
#[test]
fn test_type_sig_byref() {
let sig = TypeSig::parse(&mut Reader::new(&[0x10, 0x08])).unwrap();
assert_eq!(
sig,
TypeSig::ByRef(Box::new(TypeSig::Primitive(ElementType::I4)))
);
}
#[test]
fn test_type_sig_var() {
let sig = TypeSig::parse(&mut Reader::new(&[0x13, 0x00])).unwrap();
assert_eq!(sig, TypeSig::Var(0));
let sig = TypeSig::parse(&mut Reader::new(&[0x13, 0x02])).unwrap();
assert_eq!(sig, TypeSig::Var(2));
}
#[test]
fn test_type_sig_mvar() {
let sig = TypeSig::parse(&mut Reader::new(&[0x1E, 0x00])).unwrap();
assert_eq!(sig, TypeSig::MVar(0));
}
#[test]
fn test_type_sig_generic_inst() {
let sig = TypeSig::parse(&mut Reader::new(&[0x15, 0x12, 0x05, 0x01, 0x08])).unwrap();
assert_eq!(
sig,
TypeSig::GenericInst {
is_value_type: false,
type_ref: 5,
type_args: vec![TypeSig::Primitive(ElementType::I4)]
}
);
}
#[test]
fn test_type_sig_pinned() {
let sig = TypeSig::parse(&mut Reader::new(&[0x45, 0x08])).unwrap();
assert_eq!(
sig,
TypeSig::Pinned(Box::new(TypeSig::Primitive(ElementType::I4)))
);
}
#[test]
fn test_method_sig_void_no_params() {
let sig = MethodSig::parse_blob(&[0x00, 0x00, 0x01]).unwrap();
assert_eq!(sig.calling_convention, CallingConvention(0x00));
assert_eq!(sig.generic_param_count, 0);
assert_eq!(sig.return_type, TypeSig::Primitive(ElementType::Void));
assert!(sig.params.is_empty());
assert!(sig.sentinel.is_none());
}
#[test]
fn test_method_sig_with_params() {
let sig = MethodSig::parse_blob(&[0x20, 0x02, 0x08, 0x0E, 0x02]).unwrap();
assert!(sig.calling_convention.has_this());
assert_eq!(sig.params.len(), 2);
assert_eq!(sig.return_type, TypeSig::Primitive(ElementType::I4));
assert_eq!(sig.params[0], TypeSig::Primitive(ElementType::String));
assert_eq!(sig.params[1], TypeSig::Primitive(ElementType::Boolean));
}
#[test]
fn test_method_sig_generic() {
let sig = MethodSig::parse_blob(&[0x10, 0x01, 0x01, 0x01, 0x13, 0x00]).unwrap();
assert!(sig.calling_convention.is_generic());
assert_eq!(sig.generic_param_count, 1);
assert_eq!(sig.return_type, TypeSig::Primitive(ElementType::Void));
assert_eq!(sig.params[0], TypeSig::Var(0));
}
#[test]
fn test_field_sig_int() {
let sig = FieldSig::parse_blob(&[0x06, 0x08]).unwrap();
assert_eq!(sig.field_type, TypeSig::Primitive(ElementType::I4));
}
#[test]
fn test_field_sig_string() {
let sig = FieldSig::parse_blob(&[0x06, 0x0E]).unwrap();
assert_eq!(sig.field_type, TypeSig::Primitive(ElementType::String));
}
#[test]
fn test_field_sig_array() {
let sig = FieldSig::parse_blob(&[0x06, 0x1D, 0x08]).unwrap();
assert_eq!(
sig.field_type,
TypeSig::SzArray(Box::new(TypeSig::Primitive(ElementType::I4)))
);
}
#[test]
fn test_field_sig_invalid() {
let result = FieldSig::parse_blob(&[0x00, 0x08]);
assert!(result.is_err());
}
#[test]
fn test_property_sig_simple() {
let sig = PropertySig::parse_blob(&[0x08, 0x00, 0x08]).unwrap();
assert!(!sig.has_this);
assert_eq!(sig.property_type, TypeSig::Primitive(ElementType::I4));
assert!(sig.params.is_empty());
}
#[test]
fn test_property_sig_instance() {
let sig = PropertySig::parse_blob(&[0x28, 0x00, 0x0E]).unwrap();
assert!(sig.has_this);
assert_eq!(sig.property_type, TypeSig::Primitive(ElementType::String));
}
#[test]
fn test_property_sig_indexed() {
let sig = PropertySig::parse_blob(&[0x28, 0x01, 0x0E, 0x08]).unwrap();
assert!(sig.has_this);
assert_eq!(sig.property_type, TypeSig::Primitive(ElementType::String));
assert_eq!(sig.params.len(), 1);
assert_eq!(sig.params[0], TypeSig::Primitive(ElementType::I4));
}
#[test]
fn test_property_sig_invalid() {
let result = PropertySig::parse_blob(&[0x00, 0x00, 0x08]);
assert!(result.is_err());
}
#[test]
fn test_local_var_sig_empty() {
let sig = LocalVarSig::parse_blob(&[0x07, 0x00]).unwrap();
assert!(sig.locals.is_empty());
}
#[test]
fn test_local_var_sig_single() {
let sig = LocalVarSig::parse_blob(&[0x07, 0x01, 0x08]).unwrap();
assert_eq!(sig.locals.len(), 1);
assert_eq!(sig.locals[0], TypeSig::Primitive(ElementType::I4));
}
#[test]
fn test_local_var_sig_multiple() {
let sig = LocalVarSig::parse_blob(&[0x07, 0x03, 0x08, 0x0E, 0x02]).unwrap();
assert_eq!(sig.locals.len(), 3);
assert_eq!(sig.locals[0], TypeSig::Primitive(ElementType::I4));
assert_eq!(sig.locals[1], TypeSig::Primitive(ElementType::String));
assert_eq!(sig.locals[2], TypeSig::Primitive(ElementType::Boolean));
}
#[test]
fn test_local_var_sig_pinned() {
let sig = LocalVarSig::parse_blob(&[0x07, 0x01, 0x45, 0x08]).unwrap();
assert_eq!(sig.locals.len(), 1);
assert_eq!(
sig.locals[0],
TypeSig::Pinned(Box::new(TypeSig::Primitive(ElementType::I4)))
);
}
#[test]
fn test_local_var_sig_invalid() {
let result = LocalVarSig::parse_blob(&[0x00, 0x01, 0x08]);
assert!(result.is_err());
}
#[test]
fn test_calling_convention_flags() {
let cc = CallingConvention(0x00);
assert!(cc.is_method());
assert!(!cc.is_field());
assert!(!cc.has_this());
assert!(!cc.is_generic());
let cc = CallingConvention(0x20); assert!(cc.has_this());
let cc = CallingConvention(0x10); assert!(cc.is_generic());
let cc = CallingConvention(0x06); assert!(cc.is_field());
let cc = CallingConvention(0x08); assert!(cc.is_property());
}
#[test]
fn test_element_type_from_u8() {
assert_eq!(ElementType::from_u8(0x01), Some(ElementType::Void));
assert_eq!(ElementType::from_u8(0x08), Some(ElementType::I4));
assert_eq!(ElementType::from_u8(0x0E), Some(ElementType::String));
assert_eq!(ElementType::from_u8(0x1C), Some(ElementType::Object));
assert_eq!(ElementType::from_u8(0xFF), None); }
#[test]
fn test_element_type_name() {
assert_eq!(ElementType::Void.name(), "void");
assert_eq!(ElementType::I4.name(), "int");
assert_eq!(ElementType::String.name(), "string");
assert_eq!(ElementType::Object.name(), "object");
assert_eq!(ElementType::SzArray.name(), "[]");
}
}