#![allow(clippy::indexing_slicing)]
use crate::canon::RefKind;
use crate::error::InternalError;
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::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 = match inner.exn(exn) {
Ok(entity) => entity.backtrace.clone(),
Err(error) => return error,
};
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 args = self.pop_params(¶ms)?;
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 {
if self.frames.len() <= stop_depth {
return Err(err);
}
let frame = self.frames.last().ok_or(InternalError::FrameStack(
"missing frame during exception unwind",
))?;
let (code, base, floor, instance) = (
frame.code.clone(),
frame.locals_base,
frame.operand_base,
frame.instance,
);
if let Some(rec) = find_clause(&code, fault_ip, inner, instance, thrown)? {
return self.enter_exception_handler(inner, exn, rec, floor);
}
self.pop_unwound_frame(base)?;
if self.frames.len() == stop_depth {
return Err(err);
}
fault_ip = self
.frames
.last()
.ok_or(InternalError::FrameStack(
"missing caller frame during unwind",
))?
.ip
.checked_sub(1)
.ok_or(InternalError::FrameStack("caller return address underflow"))?;
}
}
fn enter_exception_handler(
&mut self,
inner: &StoreInner,
exn: Rooted<ExnRef>,
rec: HandlerRec,
floor: u32,
) -> Result<()> {
let restore = floor
.checked_add(rec.restore_height)
.ok_or(InternalError::OperandStack(
"exception restore height overflow",
))? as usize;
if self.values.len() != self.shadow.len() || restore > self.values.len() {
return Err(InternalError::OperandStack(
"exception restore height exceeds operand stack",
)
.into());
}
self.values.truncate(restore);
self.shadow.truncate(restore);
if rec.payload_args {
for arg in inner.exn(exn)?.args.clone() {
self.push(arg);
}
}
if rec.payload_ref {
self.push(Val::ExnRef(Some(exn)));
}
self.frames
.last_mut()
.ok_or(InternalError::FrameStack("missing handler frame"))?
.ip = rec.landing_ip;
Ok(())
}
fn pop_unwound_frame(&mut self, base: u32) -> Result<()> {
if self.values.len() != self.shadow.len() || base as usize > self.values.len() {
return Err(InternalError::OperandStack(
"frame base exceeds operand stack during unwind",
)
.into());
}
self.values.truncate(base as usize);
self.shadow.truncate(base as usize);
self.frames.pop();
Ok(())
}
pub(super) fn raise_host_exception(
&mut self,
inner: &mut StoreInner,
exn: Rooted<ExnRef>,
stop_depth: usize,
) -> Result<()> {
if self.frames.len() <= stop_depth {
return Err(PendingException { exn }.into());
}
let fault_ip = self
.frames
.last()
.ok_or(InternalError::FrameStack("missing host call frame"))?
.ip
.checked_sub(1)
.ok_or(InternalError::FrameStack(
"host call return address underflow",
))?;
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,
) -> Result<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
.get(idx as usize)
.ok_or(InternalError::Compiler("exception handler tag is missing"))?
.index
== thrown.index
}
};
if matches {
return Ok(Some(*rec));
}
}
}
}
Ok(None)
}