Skip to main content

tycho_vm/
error.rs

1use tycho_types::error::Error;
2
3use crate::stack::StackValueType;
4
5/// Result of VM-related stuff.
6pub type VmResult<T> = Result<T, Box<VmError>>;
7
8/// VM execution error.
9#[derive(Debug, thiserror::Error)]
10pub enum VmError {
11    #[error("stack underflow at depth {0}")]
12    StackUnderflow(usize),
13    #[error("too many arguments copied into a closure continuation: {0}")]
14    TooManyArguments(usize),
15    #[error("expected integer in range {min}..={max}, found {actual}")]
16    IntegerOutOfRange {
17        min: isize,
18        max: isize,
19        actual: String,
20    },
21    #[error("control register index out of range: {0}")]
22    ControlRegisterOutOfRange(usize),
23    #[error("control register redefined")]
24    ControlRegisterRedefined,
25    #[error("integer overflow")]
26    IntegerOverflow,
27    #[error("invalid opcode")]
28    InvalidOpcode,
29    #[error(
30        "expected type {}, found {}",
31        StackValueType::display_raw(*expected),
32        StackValueType::display_raw(*actual),
33    )]
34    InvalidType { expected: u8, actual: u8 },
35    #[error("out of gas")]
36    OutOfGas,
37    #[error(transparent)]
38    CellError(#[from] Error),
39    #[error("dict error")]
40    DictError,
41    #[error("unknown error. {0}")]
42    Unknown(String),
43}
44
45impl VmError {
46    pub fn is_out_of_gas(&self) -> bool {
47        matches!(self, Self::OutOfGas | Self::CellError(Error::Cancelled))
48    }
49
50    pub fn as_exception(&self) -> VmException {
51        match self {
52            Self::StackUnderflow(_) => VmException::StackUnderflow,
53            Self::TooManyArguments(_) => VmException::StackOverflow,
54            Self::IntegerOutOfRange { .. } => VmException::RangeCheck,
55            Self::ControlRegisterOutOfRange(_) => VmException::RangeCheck,
56            Self::ControlRegisterRedefined => VmException::TypeCheck,
57            Self::IntegerOverflow => VmException::IntOverflow,
58            Self::InvalidOpcode => VmException::InvalidOpcode,
59            Self::InvalidType { .. } => VmException::TypeCheck,
60            Self::OutOfGas => VmException::OutOfGas,
61            Self::Unknown(_) => VmException::Unknown,
62            Self::CellError(e) => match e {
63                Error::CellUnderflow => VmException::CellUnderflow,
64                Error::CellOverflow => VmException::CellOverflow,
65                Error::UnexpectedExoticCell | Error::UnexpectedOrdinaryCell => {
66                    VmException::VirtError
67                }
68                Error::Cancelled => VmException::OutOfGas, // ?
69                Error::IntOverflow => VmException::IntOverflow,
70                Error::InvalidCell => VmException::CellOverflow,
71                _ => VmException::Fatal, // ?
72            },
73            Self::DictError => VmException::DictError,
74        }
75    }
76}
77
78impl From<Error> for Box<VmError> {
79    #[inline]
80    fn from(e: Error) -> Self {
81        Box::new(VmError::CellError(e))
82    }
83}
84
85/// Opcode dump result.
86#[cfg(feature = "dump")]
87pub type DumpResult = Result<(), DumpError>;
88
89/// Opcode dump error.
90#[cfg(feature = "dump")]
91#[derive(thiserror::Error, Debug)]
92pub enum DumpError {
93    #[error(transparent)]
94    InvalidCode(#[from] tycho_types::error::Error),
95    #[error("invalid opcode")]
96    InvalidOpcode,
97    #[error("unexpected cell")]
98    CellMismatch,
99    #[error(transparent)]
100    WriterError(#[from] std::fmt::Error),
101}
102
103/// A code for an execution error.
104#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
105#[repr(u8)]
106pub enum VmException {
107    Ok = 0,
108    Alternative = 1,
109    StackUnderflow = 2,
110    StackOverflow = 3,
111    IntOverflow = 4,
112    RangeCheck = 5,
113    InvalidOpcode = 6,
114    TypeCheck = 7,
115    CellOverflow = 8,
116    CellUnderflow = 9,
117    DictError = 10,
118    Unknown = 11,
119    Fatal = 12,
120    OutOfGas = 13,
121    VirtError = 14,
122}
123
124impl VmException {
125    pub const fn as_exit_code(&self) -> i32 {
126        !(*self as i32)
127    }
128}
129
130impl std::fmt::Display for VmException {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        f.write_str(match self {
133            Self::Ok => "normal termination",
134            Self::Alternative => "alternative termination",
135            Self::StackUnderflow => "stack underflow",
136            Self::StackOverflow => "stack overflow",
137            Self::IntOverflow => "integer overflow",
138            Self::RangeCheck => "integer out of range",
139            Self::InvalidOpcode => "invalid opcode",
140            Self::TypeCheck => "type check error",
141            Self::CellOverflow => "cell overflow",
142            Self::CellUnderflow => "cell underflow",
143            Self::DictError => "dictionary error",
144            Self::Unknown => "unknown error",
145            Self::Fatal => "fatal error",
146            Self::OutOfGas => "out of gas",
147            Self::VirtError => "virtualization error",
148        })
149    }
150}