Skip to main content

aranya_policy_vm/
error.rs

1extern crate alloc;
2
3use alloc::{borrow::ToOwned as _, string::String};
4use core::{convert::Infallible, fmt};
5
6use aranya_policy_ast::Identifier;
7use aranya_policy_module::{CodeMap, Label, ValueConversionError};
8use buggy::Bug;
9
10use crate::io::MachineIOError;
11
12/// Possible machine errors.
13// TODO(chip): These should be elaborated with additional data, and/or
14// more fine grained types.
15#[derive(Debug, PartialEq, Eq, thiserror::Error)]
16pub enum MachineErrorType {
17    /// Stack underflow - an operation tried to consume a value from an
18    /// empty stack.
19    #[error("stack underflow")]
20    StackUnderflow,
21    /// Stack overflow - an operation tried to push a value onto a full
22    /// stack.
23    #[error("stack overflow")]
24    StackOverflow,
25    /// Name already defined - an attempt was made to define a name
26    /// that was already defined. Parameter is the name.
27    #[error("name `{0}` already defined")]
28    AlreadyDefined(Identifier),
29    /// Name not defined - an attempt was made to access a name that
30    /// has not been defined. Parameter is the name.
31    #[error("name `{0}` not defined")]
32    NotDefined(String),
33    /// Invalid type - An operation was given a value of the wrong
34    /// type. E.g. addition with strings.
35    #[error("expected type {want}, but got {got}: {msg}")]
36    InvalidType {
37        /// Expected type name
38        want: String,
39        /// Received type name
40        got: String,
41        /// Extra information
42        msg: String,
43    },
44    /// Invalid struct member - An attempt to access a member not
45    /// present in a struct. Parameter is the key name.
46    #[error("invalid struct member `{0}`")]
47    InvalidStructMember(Identifier),
48    /// Invalid fact - An attempt was made to use a fact in a way
49    /// that does not match the Fact schema.
50    #[error("invalid fact: {0}")]
51    InvalidFact(Identifier),
52    /// Invalid schema - An attempt to publish a Command struct or emit
53    /// an Effect that does not match its definition.
54    #[error("invalid schema: {0}")]
55    InvalidSchema(Identifier),
56    /// Unresolved target - A branching instruction attempted to jump
57    /// to a target whose address has not yet been resolved.
58    #[error("unresolved branch/jump target: {0}")]
59    UnresolvedTarget(Label),
60    /// Invalid address - An attempt to execute an instruction went
61    /// beyond instruction bounds, or an action/command lookup did not
62    /// find an address for the given name.
63    #[error("invalid address: {0}")]
64    InvalidAddress(Identifier),
65    /// Bad state - Some internal state is invalid and execution cannot
66    /// continue.
67    #[error("bad state: {0}")]
68    BadState(&'static str),
69    /// IntegerOverflow occurs when an instruction wraps an integer above
70    /// the max value or below the min value.
71    #[error("integer wrap")]
72    IntegerOverflow,
73    /// Invalid instruction - An instruction was used in the wrong
74    /// context, or some information encoded into an instruction is
75    /// invalid. E.g. a Swap(0)
76    #[error("invalid instruction")]
77    InvalidInstruction,
78    /// An instruction has done something wrong with the call stack, like
79    /// `Return`ed without a `Call`.
80    #[error("call stack")]
81    CallStack,
82    /// IO Error - Some machine I/O operation caused an error
83    #[error("IO: {0}")]
84    IO(MachineIOError),
85    /// FFI module name not found.
86    #[error("FFI module not defined: {0}")]
87    FfiModuleNotDefined(usize),
88    /// FFI module was found, but the procedure index is invalid.
89    #[error("FFI proc {0} not defined in module {1}")]
90    FfiProcedureNotDefined(Identifier, usize),
91    /// Context mismatch
92    #[error("Attempted call with invalid context")]
93    ContextMismatch,
94    /// An implementation bug
95    #[error("bug: {0}")]
96    Bug(Bug),
97    /// Unknown - every other possible problem
98    #[error("unknown error: {0}")]
99    Unknown(String),
100}
101
102impl MachineErrorType {
103    /// Constructs an `InvalidType` error
104    pub fn invalid_type(
105        want: impl Into<String>,
106        got: impl Into<String>,
107        msg: impl Into<String>,
108    ) -> Self {
109        Self::InvalidType {
110            want: want.into(),
111            got: got.into(),
112            msg: msg.into(),
113        }
114    }
115}
116
117impl From<Infallible> for MachineErrorType {
118    fn from(err: Infallible) -> Self {
119        match err {}
120    }
121}
122
123impl From<ValueConversionError> for MachineErrorType {
124    fn from(value: ValueConversionError) -> Self {
125        match value {
126            ValueConversionError::InvalidType { want, got, msg } => {
127                Self::InvalidType { want, got, msg }
128            }
129            ValueConversionError::InvalidStructMember(s) => Self::InvalidStructMember(s),
130            ValueConversionError::OutOfRange => Self::InvalidType {
131                want: "Int".to_owned(),
132                got: "Int".to_owned(),
133                msg: "out of range".to_owned(),
134            },
135            ValueConversionError::BadState => Self::BadState("value conversion error"),
136        }
137    }
138}
139
140/// The source location and text of an error
141#[derive(Debug, PartialEq)]
142struct MachineErrorSource {
143    /// Line and column of where the error is
144    linecol: (usize, usize),
145    /// The text of the error
146    text: String,
147}
148
149/// An error returned by [`Machine`][crate::machine::Machine].
150#[derive(Debug, PartialEq, thiserror::Error)]
151pub struct MachineError {
152    /// The type of the error
153    #[source]
154    pub err_type: MachineErrorType,
155    /// The source code information, if it exists
156    source: Option<MachineErrorSource>,
157}
158
159impl MachineError {
160    /// Creates a `MachineError`.
161    pub fn new(err_type: MachineErrorType) -> Self {
162        Self {
163            err_type,
164            source: None,
165        }
166    }
167
168    pub(crate) fn from_position(
169        err_type: MachineErrorType,
170        pc: usize,
171        codemap: Option<&CodeMap>,
172    ) -> Self {
173        Self {
174            err_type,
175            source: None,
176        }
177        .with_position(pc, codemap)
178    }
179
180    pub(crate) fn with_position(mut self, pc: usize, codemap: Option<&CodeMap>) -> Self {
181        if self.source.is_none()
182            && let Some(codemap) = codemap
183        {
184            self.source = codemap
185                .span_from_instruction(pc)
186                .ok()
187                .map(|span| MachineErrorSource {
188                    linecol: span.start_linecol(),
189                    text: span.as_str().to_owned(),
190                });
191        }
192        self
193    }
194}
195
196impl fmt::Display for MachineError {
197    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198        match &self.source {
199            Some(source) => write!(
200                f,
201                "{} at line {} col {}:\n\t{}",
202                self.err_type, source.linecol.0, source.linecol.1, source.text
203            ),
204            None => write!(f, "{}", self.err_type),
205        }
206    }
207}
208
209impl From<MachineErrorType> for MachineError {
210    fn from(value: MachineErrorType) -> Self {
211        Self::new(value)
212    }
213}
214
215impl From<Infallible> for MachineError {
216    fn from(err: Infallible) -> Self {
217        match err {}
218    }
219}
220
221impl From<Bug> for MachineError {
222    fn from(bug: Bug) -> Self {
223        Self::new(MachineErrorType::Bug(bug))
224    }
225}