generated from stacks-network/.github
-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #379 from stacks-network/feat/runtime-error-handling
runtime error handling
- Loading branch information
Showing
28 changed files
with
784 additions
and
366 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
use clarity::vm::errors::{CheckErrors, Error, RuntimeErrorType, ShortReturnType, WasmError}; | ||
use clarity::vm::types::ResponseData; | ||
use clarity::vm::Value; | ||
use wasmtime::{AsContextMut, Instance, Trap}; | ||
|
||
const LOG2_ERROR_MESSAGE: &str = "log2 must be passed a positive integer"; | ||
const SQRTI_ERROR_MESSAGE: &str = "sqrti must be passed a positive integer"; | ||
const POW_ERROR_MESSAGE: &str = "Power argument to (pow ...) must be a u32 integer"; | ||
|
||
pub enum ErrorMap { | ||
NotClarityError = -1, | ||
ArithmeticOverflow = 0, | ||
ArithmeticUnderflow = 1, | ||
DivisionByZero = 2, | ||
ArithmeticLog2Error = 3, | ||
ArithmeticSqrtiError = 4, | ||
UnwrapFailure = 5, | ||
Panic = 6, | ||
ShortReturnAssertionFailure = 7, | ||
ArithmeticPowError = 8, | ||
NotMapped = 99, | ||
} | ||
|
||
impl From<i32> for ErrorMap { | ||
fn from(error_code: i32) -> Self { | ||
match error_code { | ||
-1 => ErrorMap::NotClarityError, | ||
0 => ErrorMap::ArithmeticOverflow, | ||
1 => ErrorMap::ArithmeticUnderflow, | ||
2 => ErrorMap::DivisionByZero, | ||
3 => ErrorMap::ArithmeticLog2Error, | ||
4 => ErrorMap::ArithmeticSqrtiError, | ||
5 => ErrorMap::UnwrapFailure, | ||
6 => ErrorMap::Panic, | ||
7 => ErrorMap::ShortReturnAssertionFailure, | ||
8 => ErrorMap::ArithmeticPowError, | ||
_ => ErrorMap::NotMapped, | ||
} | ||
} | ||
} | ||
|
||
pub(crate) fn resolve_error( | ||
e: wasmtime::Error, | ||
instance: Instance, | ||
mut store: impl AsContextMut, | ||
) -> Error { | ||
if let Some(vm_error) = e.root_cause().downcast_ref::<Error>() { | ||
// SAFETY: | ||
// | ||
// This unsafe operation returns the value of a location pointed by `*mut T`. | ||
// | ||
// The purpose of this code is to take the ownership of the `vm_error` value | ||
// since clarity::vm::errors::Error is not a Clonable type. | ||
// | ||
// Converting a `&T` (vm_error) to a `*mut T` doesn't cause any issues here | ||
// because the reference is not borrowed elsewhere. | ||
// | ||
// The replaced `T` value is deallocated after the operation. Therefore, the chosen `T` | ||
// is a dummy value, solely to satisfy the signature of the replace function | ||
// and not cause harm when it is deallocated. | ||
// | ||
// Specifically, Error::Wasm(WasmError::ModuleNotFound) was selected as the placeholder value. | ||
return unsafe { | ||
core::ptr::replace( | ||
(vm_error as *const Error) as *mut Error, | ||
Error::Wasm(WasmError::ModuleNotFound), | ||
) | ||
}; | ||
} | ||
|
||
if let Some(vm_error) = e.root_cause().downcast_ref::<CheckErrors>() { | ||
// SAFETY: | ||
// | ||
// This unsafe operation returns the value of a location pointed by `*mut T`. | ||
// | ||
// The purpose of this code is to take the ownership of the `vm_error` value | ||
// since clarity::vm::errors::Error is not a Clonable type. | ||
// | ||
// Converting a `&T` (vm_error) to a `*mut T` doesn't cause any issues here | ||
// because the reference is not borrowed elsewhere. | ||
// | ||
// The replaced `T` value is deallocated after the operation. Therefore, the chosen `T` | ||
// is a dummy value, solely to satisfy the signature of the replace function | ||
// and not cause harm when it is deallocated. | ||
// | ||
// Specifically, CheckErrors::ExpectedName was selected as the placeholder value. | ||
return unsafe { | ||
let err = core::ptr::replace( | ||
(vm_error as *const CheckErrors) as *mut CheckErrors, | ||
CheckErrors::ExpectedName, | ||
); | ||
|
||
<CheckErrors as std::convert::Into<Error>>::into(err) | ||
}; | ||
} | ||
|
||
// Check if the error is caused by | ||
// an unreachable Wasm trap. | ||
// | ||
// In this case, runtime errors are handled | ||
// by being mapped to the corresponding ClarityWasm Errors. | ||
if let Some(Trap::UnreachableCodeReached) = e.root_cause().downcast_ref::<Trap>() { | ||
return from_runtime_error_code(instance, &mut store, e); | ||
} | ||
|
||
// All other errors are treated as general runtime errors. | ||
Error::Wasm(WasmError::Runtime(e)) | ||
} | ||
|
||
fn from_runtime_error_code( | ||
instance: Instance, | ||
mut store: impl AsContextMut, | ||
e: wasmtime::Error, | ||
) -> Error { | ||
let global = "runtime-error-code"; | ||
let runtime_error_code = instance | ||
.get_global(&mut store, global) | ||
.and_then(|glob| glob.get(&mut store).i32()) | ||
.unwrap_or_else(|| panic!("Could not find {global} global with i32 value")); | ||
|
||
match ErrorMap::from(runtime_error_code) { | ||
ErrorMap::NotClarityError => Error::Wasm(WasmError::Runtime(e)), | ||
ErrorMap::ArithmeticOverflow => { | ||
Error::Runtime(RuntimeErrorType::ArithmeticOverflow, Some(Vec::new())) | ||
} | ||
ErrorMap::ArithmeticUnderflow => { | ||
Error::Runtime(RuntimeErrorType::ArithmeticUnderflow, Some(Vec::new())) | ||
} | ||
ErrorMap::DivisionByZero => { | ||
Error::Runtime(RuntimeErrorType::DivisionByZero, Some(Vec::new())) | ||
} | ||
ErrorMap::ArithmeticLog2Error => Error::Runtime( | ||
RuntimeErrorType::Arithmetic(LOG2_ERROR_MESSAGE.into()), | ||
Some(Vec::new()), | ||
), | ||
ErrorMap::ArithmeticSqrtiError => Error::Runtime( | ||
RuntimeErrorType::Arithmetic(SQRTI_ERROR_MESSAGE.into()), | ||
Some(Vec::new()), | ||
), | ||
ErrorMap::UnwrapFailure => { | ||
Error::Runtime(RuntimeErrorType::UnwrapFailure, Some(Vec::new())) | ||
} | ||
ErrorMap::Panic => { | ||
panic!("An error has been detected in the code") | ||
} | ||
// TODO: UInt(42) value below is just a placeholder. | ||
// It should be replaced by the current "thrown-value" when issue #385 is resolved. | ||
// Tests that reach this code are currently ignored. | ||
ErrorMap::ShortReturnAssertionFailure => Error::ShortReturn( | ||
ShortReturnType::AssertionFailed(Value::Response(ResponseData { | ||
committed: false, | ||
data: Box::new(Value::UInt(42)), | ||
})), | ||
), | ||
ErrorMap::ArithmeticPowError => Error::Runtime( | ||
RuntimeErrorType::Arithmetic(POW_ERROR_MESSAGE.into()), | ||
Some(Vec::new()), | ||
), | ||
_ => panic!("Runtime error code {} not supported", runtime_error_code), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.