use crate::target::Target;
#[derive(Debug, Clone)]
pub struct SsaExceptionHandler<T: Target> {
pub flags: T::ExceptionKind,
pub try_offset: u32,
pub try_length: u32,
pub handler_offset: u32,
pub handler_length: u32,
pub class_token_or_filter: u32,
pub try_start_block: Option<usize>,
pub try_end_block: Option<usize>,
pub handler_start_block: Option<usize>,
pub handler_end_block: Option<usize>,
pub filter_start_block: Option<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NativeExceptionKind {
Catch,
Filter,
Finally,
Fault,
}
#[must_use]
pub fn native_is_filter_handler(flags: &NativeExceptionKind) -> bool {
matches!(flags, NativeExceptionKind::Filter)
}
fn find_next_surviving(block_remap: &[Option<usize>], start: usize) -> Option<usize> {
block_remap.get(start..)?.iter().find_map(|entry| *entry)
}
impl<T: Target> SsaExceptionHandler<T> {
#[must_use]
pub fn filter_offset(&self) -> Option<u32> {
if T::is_filter_handler(&self.flags) {
Some(self.class_token_or_filter)
} else {
None
}
}
#[must_use]
pub fn has_block_mapping(&self) -> bool {
self.try_start_block.is_some() && self.handler_start_block.is_some()
}
pub fn remap_block_indices(&mut self, block_remap: &[Option<usize>]) {
self.try_start_block = self
.try_start_block
.and_then(|idx| block_remap.get(idx).copied().flatten());
self.try_end_block = self.try_end_block.and_then(|idx| {
block_remap
.get(idx)
.copied()
.flatten()
.or_else(|| find_next_surviving(block_remap, idx))
});
self.handler_start_block = self
.handler_start_block
.and_then(|idx| block_remap.get(idx).copied().flatten());
self.handler_end_block = self.handler_end_block.and_then(|idx| {
block_remap
.get(idx)
.copied()
.flatten()
.or_else(|| find_next_surviving(block_remap, idx))
});
self.filter_start_block = self
.filter_start_block
.and_then(|idx| block_remap.get(idx).copied().flatten());
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::MockTarget;
fn handler(flags: u32) -> SsaExceptionHandler<MockTarget> {
SsaExceptionHandler {
flags,
try_offset: 10,
try_length: 20,
handler_offset: 30,
handler_length: 40,
class_token_or_filter: 50,
try_start_block: Some(0),
try_end_block: Some(2),
handler_start_block: Some(3),
handler_end_block: Some(5),
filter_start_block: Some(4),
}
}
#[test]
fn mock_target_never_reports_filter_offsets() {
assert_eq!(handler(0).filter_offset(), None);
}
#[test]
fn block_mapping_requires_try_and_handler_starts() {
let mut mapped = handler(0);
assert!(mapped.has_block_mapping());
mapped.handler_start_block = None;
assert!(!mapped.has_block_mapping());
}
#[test]
fn remap_updates_surviving_blocks_and_exclusive_end_boundaries() {
let mut mapped = handler(0);
mapped.remap_block_indices(&[Some(0), None, None, Some(1), Some(2), Some(3)]);
assert_eq!(mapped.try_start_block, Some(0));
assert_eq!(mapped.try_end_block, Some(1));
assert_eq!(mapped.handler_start_block, Some(1));
assert_eq!(mapped.handler_end_block, Some(3));
assert_eq!(mapped.filter_start_block, Some(2));
}
#[test]
fn remap_clears_removed_starts_and_out_of_bounds_indices() {
let mut mapped = handler(0);
mapped.remap_block_indices(&[None, Some(0), Some(1)]);
assert_eq!(mapped.try_start_block, None);
assert_eq!(mapped.try_end_block, Some(1));
assert_eq!(mapped.handler_start_block, None);
assert_eq!(mapped.handler_end_block, None);
assert_eq!(mapped.filter_start_block, None);
}
#[test]
fn native_exception_kind_variants_are_distinct() {
assert_ne!(NativeExceptionKind::Catch, NativeExceptionKind::Filter);
assert_ne!(NativeExceptionKind::Catch, NativeExceptionKind::Finally);
assert_ne!(NativeExceptionKind::Catch, NativeExceptionKind::Fault);
assert_ne!(NativeExceptionKind::Filter, NativeExceptionKind::Finally);
assert_ne!(NativeExceptionKind::Filter, NativeExceptionKind::Fault);
assert_ne!(NativeExceptionKind::Finally, NativeExceptionKind::Fault);
}
#[test]
fn native_filter_handler_is_correctly_identified() {
assert!(!native_is_filter_handler(&NativeExceptionKind::Catch));
assert!(native_is_filter_handler(&NativeExceptionKind::Filter));
assert!(!native_is_filter_handler(&NativeExceptionKind::Finally));
assert!(!native_is_filter_handler(&NativeExceptionKind::Fault));
}
#[test]
fn native_exception_kind_is_clone_and_eq() {
let a = NativeExceptionKind::Catch;
let b = a;
assert_eq!(a, b);
}
#[test]
fn native_filter_handler_identified_by_helper() {
assert!(!native_is_filter_handler(&NativeExceptionKind::Catch));
assert!(native_is_filter_handler(&NativeExceptionKind::Filter));
assert!(!native_is_filter_handler(&NativeExceptionKind::Finally));
assert!(!native_is_filter_handler(&NativeExceptionKind::Fault));
}
}