cilium 0.0.0

An experimental Rust library for interfacing with Microsoft's CLI assembly format.
Documentation
use std::cell::Cell;
use std::fmt::{Debug, Display, Formatter};
use std::hash::{Hash, Hasher};
use std::io::ErrorKind;

use bumpalo::Bump;
use derivative::Derivative;

use crate::raw::heaps::{BlobHeap, StringHeap};
use crate::raw::heaps::table::{TableHeap, TypeAttributes, TypeDef, Field as FieldRow, FieldAttributes};
use crate::structured::Assembly;
use crate::structured::assembly::AssemblyLoadingError;
use crate::structured::methods::Method;

#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub enum Type<'l> {
	Primitive(Primitive),
	Class(&'l Class<'l>),
	Interface(&'l Interface<'l>),
}

impl Display for Type<'_> {
	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
		match self {
			Type::Primitive(t) => Display::fmt(t, f),
			Type::Class(t) => Display::fmt(t, f),
			Type::Interface(t) => Display::fmt(t, f),
		}
	}
}

impl Debug for Type<'_> {
	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
		match self {
			Type::Primitive(t) => Debug::fmt(t, f),
			Type::Class(t) => Debug::fmt(t, f),
			Type::Interface(t) => Debug::fmt(t, f),
		}
	}
}

#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Primitive {
	#[default]
	Void,
	Bool,
	Char,
	Int8,
	UInt8,
	Int16,
	UInt16,
	Int32,
	UInt32,
	Int64,
	UInt64,
	Float,
	Double,
	String,
	IntPtr,
	UIntPtr,
	Object,
}

impl Display for Primitive {
	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
		match self {
			Primitive::Void => write!(f, "void"),
			Primitive::Bool => write!(f, "bool"),
			Primitive::Char => write!(f, "char"),
			Primitive::Int8 => write!(f, "sbyte"),
			Primitive::UInt8 => write!(f, "byte"),
			Primitive::Int16 => write!(f, "short"),
			Primitive::UInt16 => write!(f, "ushort"),
			Primitive::Int32 => write!(f, "int"),
			Primitive::UInt32 => write!(f, "uint"),
			Primitive::Int64 => write!(f, "long"),
			Primitive::UInt64 => write!(f, "ulong"),
			Primitive::Float => write!(f, "float"),
			Primitive::Double => write!(f, "double"),
			Primitive::String => write!(f, "string"),
			Primitive::IntPtr => write!(f, "System.IntPtr"),
			Primitive::UIntPtr => write!(f, "System.UIntPtr"),
			Primitive::Object => write!(f, "object"),
		}
	}
}

#[derive(Derivative)]
#[derivative(Debug)]
pub struct Class<'l> {
	name: &'l str,
	namespace: &'l str,
	#[derivative(Debug(format_with = "crate::utilities::fmt_debug_cell"))]
	fields: Cell<&'l [Field<'l>]>,
	#[derivative(Debug(format_with = "crate::utilities::fmt_debug_cell"))]
	methods: Cell<&'l [Method]>,
}

impl Display for Class<'_> {
	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
		match self.namespace {
			"" => write!(f, "{}", self.name),
			_ => write!(f, "{}.{}", self.namespace, self.name),
		}
	}
}

#[derive(Derivative)]
#[derivative(Debug)]
pub struct Interface<'l> {
	name: &'l str,
	namespace: &'l str,
	#[derivative(Debug(format_with = "crate::utilities::fmt_debug_cell"))]
	fields: Cell<&'l [Field<'l>]>,
	#[derivative(Debug(format_with = "crate::utilities::fmt_debug_cell"))]
	methods: Cell<&'l [Method]>,
}

impl Display for Interface<'_> {
	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
		match self.namespace {
			"" => write!(f, "{}", self.name),
			_ => write!(f, "{}.{}", self.namespace, self.name),
		}
	}
}

#[derive(Derivative, Copy, Clone)]
#[derivative(Debug)]
pub struct Field<'l> {
	name: &'l str,
	flags: FieldAttributes,
	#[derivative(Debug(format_with = "Display::fmt"))]
	ty: Type<'l>,
}

macro_rules! impl_identity {
    ($($ty: ident),*) => {
		$(
			impl Eq for $ty<'_> {}

			impl PartialEq<Self> for $ty<'_> {
				#[inline]
				fn eq(&self, other: &Self) -> bool {
					std::ptr::eq(self, other)
				}
			}

			impl Hash for $ty<'_> {
				fn hash<H: Hasher>(&self, state: &mut H) {
					(self as *const Self).hash(state)
				}
			}
		)*
	};
}

impl_identity!(Class, Interface);

#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
pub(super) fn load_type_defs<'l>(
	bump: &'l Bump,
	tables: &TableHeap,
	strings: StringHeap,
) -> Result<&'l [Type<'l>], AssemblyLoadingError> {
	let mut types = bumpalo::vec![in bump];

	if let Some(table) = tables.get_table::<TypeDef>() {
		types.reserve(table.len());
		for type_def in table.rows() {
			let type_def = type_def?;
			let name = strings.get(type_def.name).ok_or(ErrorKind::NotFound)?;
			let namespace = strings.get(type_def.namespace).ok_or(ErrorKind::NotFound)?;

			if type_def.flags.contains(TypeAttributes::INTERFACE) {
				types.push(Type::Interface(bump.alloc(Interface {
					name: bump.alloc_str(name),
					namespace: bump.alloc_str(namespace),
					fields: Cell::new(&[]),
					methods: Cell::new(&[]),
				})));
			}
			else if type_def.flags.contains(TypeAttributes::CLASS) {
				types.push(Type::Class(bump.alloc(Class {
					name: bump.alloc_str(name),
					namespace: bump.alloc_str(namespace),
					fields: Cell::new(&[]),
					methods: Cell::new(&[]),
				})));
			}
		}

		assert_eq!(types.len(), table.len());
	}

	Ok(types.into_bump_slice())
}

#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
pub(super) fn populate_type_defs<'l>(
	bump: &'l Bump,
	_blobs: BlobHeap,
	strings: StringHeap,
	tables: &TableHeap,
	assembly: &'l Assembly<'l>,
) -> Result<(), AssemblyLoadingError> {
	let mut fields = bumpalo::vec![in bump];
	if let Some(table) = tables.get_table::<FieldRow>() {
		fields.reserve_exact(fields.len());
		for field in table.rows() {
			let field = field?;
			let name = strings.get(field.name).ok_or(ErrorKind::NotFound)?;
			fields.push(Field {
				name: bump.alloc_str(name),
				flags: field.flags,
				ty: Type::Primitive(Primitive::Void),
			});
		}
	}

	let fields = fields.into_bump_slice();
	if let Some(table) = tables.get_table::<TypeDef>() {
		let mut rows = table.rows();
		if let Some(row) = rows.next() { let _ = row?; }

		let mut start = 0;
		for type_def in assembly.types() {
			let end = match rows.next() {
				None => fields.len(),
				Some(type_def) => type_def?.field_list.idx().unwrap(),
			};
			let range = start..end;
			match type_def {
				Type::Class(class) => {
					class.fields.set(&fields[range]);
				}
				Type::Interface(interface) => {
					interface.fields.set(&fields[range]);
				}
				Type::Primitive(_) => {
					assert_eq!(range.len(), 0);
				}
			}
			start = end;
		}
	}

	Ok(())
}