wain_exec/
trap.rs

1use crate::value::Value;
2use std::fmt;
3use wain_ast::{Import, ValType};
4
5#[cfg_attr(test, derive(Debug))]
6pub enum TrapReason {
7    UnknownImport {
8        mod_name: String,
9        name: String,
10        kind: &'static str,
11    },
12    OutOfLimit {
13        max: usize,
14        idx: usize,
15        kind: &'static str,
16    },
17    DataSegmentOutOfBuffer {
18        segment_end: usize,
19        buffer_size: usize,
20    },
21    ElemSegmentLargerThanTable {
22        segment_end: usize,
23        table_size: usize,
24    },
25    ReachUnreachable,
26    IdxOutOfTable {
27        idx: usize,
28        table_size: usize,
29    },
30    UninitializedElem(usize),
31    FuncSignatureMismatch {
32        import: Option<(String, String)>,
33        expected_params: Vec<ValType>,
34        expected_results: Vec<ValType>,
35        actual_params: Vec<ValType>,
36        actual_results: Vec<ValType>,
37    },
38    // 10. https://webassembly.github.io/spec/core/exec/instructions.html#and
39    LoadMemoryOutOfRange {
40        max: usize,
41        addr: usize,
42        operation: &'static str,
43        ty: &'static str,
44    },
45    ImportFuncCallFail {
46        mod_name: String,
47        name: String,
48        msg: String,
49    },
50    WrongInvokeTarget {
51        name: String,
52        actual: Option<&'static str>,
53    },
54    InvokeInvalidArgs {
55        name: String,
56        args: Vec<Value>,
57        arg_types: Vec<ValType>,
58    },
59    RemZeroDivisor,
60    DivByZeroOrOverflow,
61    ValueOutOfRange {
62        src_val: Value,
63        dest_type: &'static str,
64    },
65}
66
67#[cfg_attr(test, derive(Debug))]
68pub struct Trap {
69    pub reason: TrapReason,
70    pub offset: usize,
71}
72
73impl Trap {
74    pub(crate) fn unknown_import(import: &Import<'_>, kind: &'static str, offset: usize) -> Box<Self> {
75        Self::new(
76            TrapReason::UnknownImport {
77                mod_name: import.mod_name.0.to_string(),
78                name: import.name.0.to_string(),
79                kind,
80            },
81            offset,
82        )
83    }
84
85    pub(crate) fn out_of_range<N: Into<Value>>(num: N, type_name: &'static str, offset: usize) -> Box<Self> {
86        Self::new(
87            TrapReason::ValueOutOfRange {
88                src_val: num.into(),
89                dest_type: type_name,
90            },
91            offset,
92        )
93    }
94
95    pub(crate) fn new(reason: TrapReason, offset: usize) -> Box<Trap> {
96        Box::new(Trap { reason, offset })
97    }
98}
99
100struct JoinWritable<'a, D: fmt::Display>(&'a [D], &'static str);
101
102impl<'a, D: fmt::Display> fmt::Display for JoinWritable<'a, D> {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        if let Some(d) = self.0.first() {
105            d.fmt(f)?;
106        }
107        for d in self.0.iter().skip(1) {
108            write!(f, "{}{}", self.1, d)?;
109        }
110        Ok(())
111    }
112}
113
114impl fmt::Display for Trap {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        use TrapReason::*;
117        match &self.reason {
118            UnknownImport { mod_name, name, kind } => write!(
119                f,
120                "unknown module '{}' or unknown {} value '{}' imported from the module",
121                mod_name, kind, name,
122            )?,
123            OutOfLimit { max, idx, kind } => {
124                write!(f, "specified {} index 0x{:x} is out of limit 0x{:x}", kind, idx, max,)?
125            }
126            DataSegmentOutOfBuffer {
127                segment_end,
128                buffer_size,
129            } => write!(
130                f,
131                "'data' segment ends at address 0x{:x} but memory buffer size is 0x{:x}",
132                segment_end, buffer_size,
133            )?,
134            ElemSegmentLargerThanTable {
135                segment_end,
136                table_size,
137            } => write!(
138                f,
139                "'elem' segment ends at index {} but table length is {}",
140                segment_end, table_size,
141            )?,
142            ReachUnreachable => f.write_str("reached unreachable code")?,
143            IdxOutOfTable { idx, table_size } => write!(
144                f,
145                "cannot refer function because index {} is out of table size {}",
146                idx, table_size
147            )?,
148            UninitializedElem(idx) => write!(f, "element at index {} in table is uninitialized", idx,)?,
149            FuncSignatureMismatch {
150                import,
151                expected_params,
152                expected_results,
153                actual_params,
154                actual_results,
155            } => {
156                if let Some((mod_name, name)) = import {
157                    write!(
158                        f,
159                        "function signature mismatch in imported function '{}' of module '{}'. ",
160                        name, mod_name
161                    )?;
162                } else {
163                    f.write_str("cannot invoke function due to mismatch of function signature. ")?;
164                }
165                write!(
166                    f,
167                    "expected '[{}] -> [{}]' but got '[{}] -> [{}]'",
168                    JoinWritable(expected_params, " "),
169                    JoinWritable(expected_results, " "),
170                    JoinWritable(actual_params, " "),
171                    JoinWritable(actual_results, " "),
172                )?
173            }
174            LoadMemoryOutOfRange {
175                max,
176                addr,
177                operation,
178                ty,
179            } => write!(
180                f,
181                "cannot {} {} value at 0x{:x} due to out of range of memory. memory size is 0x{:x}",
182                operation, ty, addr, max,
183            )?,
184            ImportFuncCallFail { mod_name, name, msg } => write!(
185                f,
186                "calling imported function '{}' in module '{}': {}",
187                name, mod_name, msg,
188            )?,
189            WrongInvokeTarget { name, actual: None } => write!(f, "cannot invoke unknown function '{}'", name)?,
190            WrongInvokeTarget {
191                name,
192                actual: Some(actual),
193            } => write!(
194                f,
195                "cannot invoke '{name}': '{name}' is {actual}",
196                name = name,
197                actual = actual,
198            )?,
199            InvokeInvalidArgs { name, args, arg_types } => write!(
200                f,
201                "cannot invoke function '{}' since given values [{}] does not match to parameter types [{}]",
202                name,
203                JoinWritable(args, ", "),
204                JoinWritable(arg_types, " "),
205            )?,
206            RemZeroDivisor => f.write_str("attempt to calculate reminder with zero divisor")?,
207            DivByZeroOrOverflow => f.write_str("integer overflow or attempt to divide integer by zero")?,
208            ValueOutOfRange { src_val, dest_type } => write!(
209                f,
210                "source value '{}' cannot represent destination type '{}'",
211                src_val, dest_type
212            )?,
213        }
214        write!(f, ": execution was trapped at byte offset 0x{:x}", self.offset)
215    }
216}
217
218pub type Result<T> = ::std::result::Result<T, Box<Trap>>;