use std::collections::BTreeMap;
use crate::{
formatting::helpers::assembly_scoped_name,
metadata::{
method::{ExceptionHandler, ExceptionHandlerFlags},
typesystem::CilTypeRc,
},
CilObject,
};
#[derive(Debug)]
pub(crate) enum BlockEvent {
TryOpen,
TryClose,
HandlerOpen {
kind: HandlerKind,
},
HandlerClose,
FilterOpen,
FilterClose,
}
#[derive(Debug)]
pub(crate) enum HandlerKind {
Catch(String),
Finally,
Fault,
Filter,
}
pub(crate) struct ExceptionBlockLayout {
pub events: BTreeMap<u32, Vec<BlockEvent>>,
}
impl ExceptionBlockLayout {
pub fn build(handlers: &[ExceptionHandler], asm: &CilObject) -> Self {
if handlers.is_empty() {
return Self {
events: BTreeMap::new(),
};
}
let mut try_groups: BTreeMap<(u32, u32), Vec<&ExceptionHandler>> = BTreeMap::new();
for handler in handlers {
let try_end = handler.try_offset + handler.try_length;
try_groups
.entry((handler.try_offset, try_end))
.or_default()
.push(handler);
}
let mut close_events: BTreeMap<u32, Vec<BlockEvent>> = BTreeMap::new();
let mut open_events: BTreeMap<u32, Vec<BlockEvent>> = BTreeMap::new();
for ((try_offset, try_end), group) in &try_groups {
open_events
.entry(*try_offset)
.or_default()
.push(BlockEvent::TryOpen);
close_events
.entry(*try_end)
.or_default()
.push(BlockEvent::TryClose);
for handler in group {
let handler_end = handler.handler_offset + handler.handler_length;
let kind = handler_kind(handler, asm);
if handler.flags == ExceptionHandlerFlags::FILTER {
open_events
.entry(handler.filter_offset)
.or_default()
.push(BlockEvent::FilterOpen);
close_events
.entry(handler.handler_offset)
.or_default()
.push(BlockEvent::FilterClose);
}
open_events
.entry(handler.handler_offset)
.or_default()
.push(BlockEvent::HandlerOpen { kind });
close_events
.entry(handler_end)
.or_default()
.push(BlockEvent::HandlerClose);
}
}
let mut events: BTreeMap<u32, Vec<BlockEvent>> = BTreeMap::new();
let all_offsets: std::collections::BTreeSet<u32> = close_events
.keys()
.chain(open_events.keys())
.copied()
.collect();
for offset in all_offsets {
let entry = events.entry(offset).or_default();
if let Some(closes) = close_events.remove(&offset) {
entry.extend(closes);
}
if let Some(opens) = open_events.remove(&offset) {
entry.extend(opens);
}
}
Self { events }
}
pub fn format_event(event: &BlockEvent, indent: usize) -> String {
let pad = " ".repeat(indent);
match event {
BlockEvent::TryOpen => format!("{pad}.try\n{pad}{{"),
BlockEvent::TryClose => format!("{pad}}} // end .try"),
BlockEvent::HandlerOpen { kind } => match kind {
HandlerKind::Catch(type_name) => format!("{pad}catch {type_name}\n{pad}{{"),
HandlerKind::Finally => format!("{pad}finally\n{pad}{{"),
HandlerKind::Fault => format!("{pad}fault\n{pad}{{"),
HandlerKind::Filter => format!("{pad}{{"),
},
BlockEvent::HandlerClose => format!("{pad}}} // end handler"),
BlockEvent::FilterOpen => format!("{pad}filter\n{pad}{{"),
BlockEvent::FilterClose => format!("{pad}}} // end filter"),
}
}
}
fn handler_kind(handler: &ExceptionHandler, asm: &CilObject) -> HandlerKind {
match handler.flags {
ExceptionHandlerFlags::EXCEPTION => {
let type_name = exception_type_name(handler.handler.as_ref(), asm);
HandlerKind::Catch(type_name)
}
ExceptionHandlerFlags::FINALLY => HandlerKind::Finally,
ExceptionHandlerFlags::FAULT => HandlerKind::Fault,
ExceptionHandlerFlags::FILTER => HandlerKind::Filter,
_ => HandlerKind::Catch("[unknown]".to_string()),
}
}
fn exception_type_name(handler_type: Option<&CilTypeRc>, asm: &CilObject) -> String {
handler_type.map_or_else(
|| "[mscorlib]System.Object".to_string(),
|t| assembly_scoped_name(t, asm),
)
}