1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
// Copyright 2021-2023 Protocol Labs
// SPDX-License-Identifier: Apache-2.0, MIT
//! This module contains code used to convert errors to and from wasmtime traps.
use anyhow::anyhow;
use fvm_shared::error::ExitCode;
use wasmtime::Trap;
use crate::call_manager::NO_DATA_BLOCK_ID;
use crate::kernel::{BlockId, ExecutionError};
/// Represents an actor "abort". Returning an [`Abort`] from a syscall will cause the currently
/// running actor to exit and may cause part or all of the actor call stack to unwind.
#[derive(Debug, thiserror::Error)]
pub enum Abort {
/// The actor explicitly aborted with the given exit code, panicked, or ran out of memory.
#[error("exit with code {0} ({2})")]
Exit(ExitCode, String, BlockId),
/// The actor ran out of gas. This will unwind the actor call stack until we reach an actor
/// invocation with a gas limit _less_ than that of the caller's gas limit, or until we reach
/// the top-level call.
#[error("out of gas")]
OutOfGas,
/// The system failed with a fatal error indicating a bug in the FVM. This will abort the entire
/// top-level message and record a
/// [`SYS_ASSERTION_FAILED`][fvm_shared::ExitCode::SYS_ASSERTION_FAILED] exit code on-chain.
#[error("fatal error: {0}")]
Fatal(anyhow::Error),
}
impl Abort {
/// Convert an execution error into an "abort". We can't directly convert because we need an
/// exit code, not a syscall error number.
pub fn from_error(code: ExitCode, e: ExecutionError) -> Self {
match e {
ExecutionError::Syscall(e) => Abort::Exit(
code,
format!(
"actor aborted with an invalid message: {} (code={:?})",
e.0, e.1
),
0,
),
ExecutionError::OutOfGas => Abort::OutOfGas,
ExecutionError::Fatal(err) => Abort::Fatal(err),
}
}
/// Just like from_error, but escalating syscall errors as fatal.
pub fn from_error_as_fatal(e: ExecutionError) -> Self {
match e {
ExecutionError::OutOfGas => Abort::OutOfGas,
ExecutionError::Fatal(e) => Abort::Fatal(e),
ExecutionError::Syscall(e) => Abort::Fatal(anyhow!("unexpected syscall error: {}", e)),
}
}
}
/// Unwraps a trap error from an actor into an "abort".
impl From<anyhow::Error> for Abort {
fn from(e: anyhow::Error) -> Self {
if let Some(trap) = e.downcast_ref::<Trap>() {
return match trap {
| Trap::MemoryOutOfBounds
| Trap::TableOutOfBounds
| Trap::IndirectCallToNull
| Trap::BadSignature
| Trap::IntegerOverflow
| Trap::IntegerDivisionByZero
| Trap::BadConversionToInteger
| Trap::UnreachableCodeReached
// Should require the atomic feature to be enabled, but we might as well just
// handle this.
| Trap::HeapMisaligned
| Trap::AtomicWaitNonSharedMemory
// I think this is fatal? But I'm not sure.
| Trap::StackOverflow => Abort::Exit(
ExitCode::SYS_ILLEGAL_INSTRUCTION,
trap.to_string(),
NO_DATA_BLOCK_ID,
),
_ => Abort::Fatal(anyhow!("unexpected wasmtime trap: {}", trap)),
};
};
match e.downcast::<Abort>() {
Ok(abort) => abort,
Err(e) => Abort::Fatal(e),
}
}
}