#![allow(clippy::indexing_slicing)]
use crate::canon::RefKind;
use crate::exception::ThrownException;
use crate::extern_::Tag;
use crate::instance::Instance;
use crate::module::code::Code;
use crate::module::handler::HandlerRec;
use crate::store::{ExnEntity, StoreInner};
use crate::trap::Trap;
use crate::value::{ExnRef, Rooted, Val, ValType};
use crate::{Error, Result};
use super::outcome::StepOutcome;
use super::{cell, Execution};
#[derive(Debug)]
pub(crate) struct PendingException {
pub exn: Rooted<ExnRef>,
}
impl core::fmt::Display for PendingException {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("uncaught exception")
}
}
impl std::error::Error for PendingException {}
pub(super) fn surface_exception(inner: &mut StoreInner, err: Error) -> Error {
match err.downcast_ref::<PendingException>() {
Some(p) => {
let exn = p.exn;
let backtrace = inner.exn(exn).backtrace.clone();
inner.set_pending_exception(exn);
match backtrace {
Some(bt) => Error::new(bt).context(ThrownException),
None => ThrownException.into(),
}
}
None => err,
}
}
impl Execution {
pub(super) fn throw(
&mut self,
inner: &mut StoreInner,
instance: Instance,
tag_idx: u32,
ip: u32,
) -> Result<StepOutcome> {
let tag = inner.instance(instance).tags[tag_idx as usize];
let params: Vec<ValType> = inner.tag(tag).ty.ty().params().collect();
if let Some(out) = self.gc_reserve(inner, crate::store::exn_charge(params.len()), ip) {
return Ok(out);
}
let mut args: Vec<Val> = params
.iter()
.rev()
.map(|ty| cell::decode(self.pop(), ty))
.collect();
args.reverse();
let backtrace = inner
.engine()
.wasm_backtrace_enabled()
.then(|| self.capture_backtrace(inner, ip));
let exn = inner.alloc_exn(ExnEntity {
tag,
args,
backtrace,
})?;
Err(PendingException { exn }.into())
}
pub(super) fn throw_ref(&mut self) -> Result<StepOutcome> {
match self.pop_ref(RefKind::Exn) {
Val::ExnRef(Some(exn)) => Err(PendingException { exn }.into()),
Val::ExnRef(None) => Err(Trap::NullReference.into()),
_ => unreachable!("throw_ref operand is an exnref (validated)"),
}
}
pub(super) fn unwind(
&mut self,
inner: &mut StoreInner,
err: Error,
mut fault_ip: u32,
stop_depth: usize,
) -> Result<()> {
let exn = match err.downcast_ref::<PendingException>() {
Some(p) => p.exn,
None => return Err(err),
};
let thrown = inner.exn(exn).tag;
loop {
let frame = self.frames.last().expect("unwind: empty frame stack");
let (code, base, instance) = (frame.code.clone(), frame.locals_base, frame.instance);
if let Some(rec) = find_clause(&code, fault_ip, inner, instance, thrown) {
let floor = base + code.n_params() + code.local_types().len() as u32;
let restore = (floor + rec.restore_height) as usize;
self.values.truncate(restore);
self.shadow.truncate(restore);
if rec.payload_args {
for a in inner.exn(exn).args.clone() {
self.push(a);
}
}
if rec.payload_ref {
self.push(Val::ExnRef(Some(exn)));
}
self.frames.last_mut().expect("frame").ip = rec.landing_ip;
return Ok(());
}
self.values.truncate(base as usize);
self.shadow.truncate(base as usize);
self.frames.pop();
if self.frames.len() == stop_depth {
return Err(err);
}
fault_ip = self
.frames
.last()
.expect("frame above the delimiter")
.ip
.saturating_sub(1);
}
}
pub(super) fn raise_host_exception(
&mut self,
inner: &mut StoreInner,
exn: Rooted<ExnRef>,
stop_depth: usize,
) -> Result<()> {
let fault_ip = self
.frames
.last()
.expect("host call frame")
.ip
.saturating_sub(1);
if inner.exn(exn).backtrace.is_none() && inner.engine().wasm_backtrace_enabled() {
let bt = self.capture_backtrace(inner, fault_ip);
inner.exn_mut(exn).backtrace = Some(bt);
}
self.unwind(inner, PendingException { exn }.into(), fault_ip, stop_depth)
}
}
fn find_clause(
code: &Code,
ip: u32,
inner: &StoreInner,
instance: Instance,
thrown: Tag,
) -> Option<HandlerRec> {
for span in code.handlers() {
if ip >= span.start_ip && ip < span.end_ip {
for rec in &span.clauses {
let matches = match rec.tag {
None => true,
Some(idx) => inner.instance(instance).tags[idx as usize].index == thrown.index,
};
if matches {
return Some(*rec);
}
}
}
}
None
}