Skip to content

Commit 6eb4a23

Browse files
authored
Merge pull request #4972 from stacks-network/wasm-todo-addressing
TODO's addressing
2 parents 61e4e45 + c6c526c commit 6eb4a23

File tree

2 files changed

+179
-25
lines changed

2 files changed

+179
-25
lines changed

clarity/src/vm/clarity_wasm.rs

Lines changed: 177 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -568,19 +568,7 @@ pub fn call_function<'a, 'b, 'c>(
568568

569569
// Call the function
570570
func.call(&mut store, &wasm_args, &mut results)
571-
.map_err(|e| {
572-
// TODO: If the root cause is a clarity error, we should be able to return that,
573-
// but it is not cloneable, so we can't return it directly.
574-
// If the root cause is a trap from our Wasm code, then we need to translate
575-
// it into a Clarity error.
576-
// See issue stacks-network/clarity-wasm#104
577-
// if let Some(vm_error) = e.root_cause().downcast_ref::<crate::vm::errors::Error>() {
578-
// vm_error.clone()
579-
// } else {
580-
// Error::Wasm(WasmError::Runtime(e))
581-
// }
582-
Error::Wasm(WasmError::Runtime(e))
583-
})?;
571+
.map_err(|e| error_mapping::resolve_error(e, instance, &mut store))?;
584572

585573
// If the function returns a value, translate it into a Clarity `Value`
586574
wasm_to_clarity_value(&return_type, 0, &results, memory, &mut &mut store, epoch)
@@ -721,7 +709,7 @@ pub fn is_in_memory_type(ty: &TypeSignature) -> bool {
721709

722710
fn clar2wasm_ty(ty: &TypeSignature) -> Vec<ValType> {
723711
match ty {
724-
TypeSignature::NoType => vec![ValType::I32], // TODO: can this just be empty?
712+
TypeSignature::NoType => vec![ValType::I32], // TODO: clarity-wasm issue #445. Can this just be empty?
725713
TypeSignature::IntType => vec![ValType::I64, ValType::I64],
726714
TypeSignature::UIntType => vec![ValType::I64, ValType::I64],
727715
TypeSignature::ResponseType(inner_types) => {
@@ -1029,8 +1017,10 @@ fn read_from_wasm(
10291017
_ => Err(Error::Wasm(WasmError::InvalidIndicator(indicator))),
10301018
}
10311019
}
1032-
TypeSignature::NoType => todo!("type not yet implemented: {:?}", ty),
1033-
TypeSignature::ListUnionType(_subtypes) => todo!("type not yet implemented: {:?}", ty),
1020+
TypeSignature::NoType => Err(Error::Wasm(WasmError::InvalidNoTypeInValue)),
1021+
TypeSignature::ListUnionType(_subtypes) => {
1022+
Err(Error::Wasm(WasmError::InvalidListUnionTypeInValue))
1023+
}
10341024
}
10351025
}
10361026

@@ -1857,7 +1847,7 @@ fn wasm_to_clarity_value(
18571847
Ok((Some(tuple.into()), index - value_index))
18581848
}
18591849
TypeSignature::ListUnionType(_lu) => {
1860-
todo!("Wasm value type not implemented: {:?}", type_sig)
1850+
Err(Error::Wasm(WasmError::InvalidListUnionTypeInValue))
18611851
}
18621852
}
18631853
}
@@ -1938,7 +1928,7 @@ fn link_define_variable_fn(linker: &mut Linker<ClarityWasmContext>) -> Result<()
19381928
name_length: i32,
19391929
mut value_offset: i32,
19401930
mut value_length: i32| {
1941-
// TODO: Include this cost
1931+
// TODO: clarity-wasm issue #344 Include this cost
19421932
// runtime_cost(ClarityCostFunction::CreateVar, global_context, value_type.size())?;
19431933

19441934
// Get the memory from the caller
@@ -2497,7 +2487,7 @@ fn link_get_variable_fn(linker: &mut Linker<ClarityWasmContext>) -> Result<(), E
24972487
Err(_e) => data_types.value_type.size()? as u64,
24982488
};
24992489

2500-
// TODO: Include this cost
2490+
// TODO: clarity-wasm issue #344 Include this cost
25012491
// runtime_cost(ClarityCostFunction::FetchVar, env, result_size)?;
25022492

25032493
let value = result.map(|data| data.value)?;
@@ -2564,7 +2554,7 @@ fn link_set_variable_fn(linker: &mut Linker<ClarityWasmContext>) -> Result<(), E
25642554
)))?
25652555
.clone();
25662556

2567-
// TODO: Include this cost
2557+
// TODO: clarity-wasm issue #344 Include this cost
25682558
// runtime_cost(
25692559
// ClarityCostFunction::SetVar,
25702560
// env,
@@ -2585,7 +2575,7 @@ fn link_set_variable_fn(linker: &mut Linker<ClarityWasmContext>) -> Result<(), E
25852575
epoch,
25862576
)?;
25872577

2588-
// TODO: Include this cost
2578+
// TODO: clarity-wasm issue #344 Include this cost
25892579
// env.add_memory(value.get_memory_use())?;
25902580

25912581
// Store the variable in the global context
@@ -5939,10 +5929,7 @@ fn link_load_constant_fn(linker: &mut Linker<ClarityWasmContext>) -> Result<(),
59395929
.contract_context()
59405930
.variables
59415931
.get(&ClarityName::from(const_name.as_str()))
5942-
// TODO: create a new WasmError to handle the constant not found issue
5943-
.ok_or(Error::Wasm(WasmError::WasmGeneratorError(
5944-
"Constant not found on MARF".to_string(),
5945-
)))?
5932+
.ok_or(CheckErrors::UndefinedVariable(const_name.to_string()))?
59465933
.clone();
59475934

59485935
// Constant value type
@@ -7456,3 +7443,168 @@ mod tests {
74567443
assert_eq!(read, expected);
74577444
}
74587445
}
7446+
7447+
mod error_mapping {
7448+
use wasmtime::{AsContextMut, Instance, Trap};
7449+
7450+
use crate::vm::errors::{CheckErrors, Error, RuntimeErrorType, ShortReturnType, WasmError};
7451+
use crate::vm::types::ResponseData;
7452+
use crate::vm::Value;
7453+
7454+
const LOG2_ERROR_MESSAGE: &str = "log2 must be passed a positive integer";
7455+
const SQRTI_ERROR_MESSAGE: &str = "sqrti must be passed a positive integer";
7456+
const POW_ERROR_MESSAGE: &str = "Power argument to (pow ...) must be a u32 integer";
7457+
7458+
pub enum ErrorMap {
7459+
NotClarityError = -1,
7460+
ArithmeticOverflow = 0,
7461+
ArithmeticUnderflow = 1,
7462+
DivisionByZero = 2,
7463+
ArithmeticLog2Error = 3,
7464+
ArithmeticSqrtiError = 4,
7465+
UnwrapFailure = 5,
7466+
Panic = 6,
7467+
ShortReturnAssertionFailure = 7,
7468+
ArithmeticPowError = 8,
7469+
NotMapped = 99,
7470+
}
7471+
7472+
impl From<i32> for ErrorMap {
7473+
fn from(error_code: i32) -> Self {
7474+
match error_code {
7475+
-1 => ErrorMap::NotClarityError,
7476+
0 => ErrorMap::ArithmeticOverflow,
7477+
1 => ErrorMap::ArithmeticUnderflow,
7478+
2 => ErrorMap::DivisionByZero,
7479+
3 => ErrorMap::ArithmeticLog2Error,
7480+
4 => ErrorMap::ArithmeticSqrtiError,
7481+
5 => ErrorMap::UnwrapFailure,
7482+
6 => ErrorMap::Panic,
7483+
7 => ErrorMap::ShortReturnAssertionFailure,
7484+
8 => ErrorMap::ArithmeticPowError,
7485+
_ => ErrorMap::NotMapped,
7486+
}
7487+
}
7488+
}
7489+
7490+
pub fn resolve_error(
7491+
e: wasmtime::Error,
7492+
instance: Instance,
7493+
mut store: impl AsContextMut,
7494+
) -> Error {
7495+
if let Some(vm_error) = e.root_cause().downcast_ref::<Error>() {
7496+
// SAFETY:
7497+
//
7498+
// This unsafe operation returns the value of a location pointed by `*mut T`.
7499+
//
7500+
// The purpose of this code is to take the ownership of the `vm_error` value
7501+
// since clarity::vm::errors::Error is not a Clonable type.
7502+
//
7503+
// Converting a `&T` (vm_error) to a `*mut T` doesn't cause any issues here
7504+
// because the reference is not borrowed elsewhere.
7505+
//
7506+
// The replaced `T` value is deallocated after the operation. Therefore, the chosen `T`
7507+
// is a dummy value, solely to satisfy the signature of the replace function
7508+
// and not cause harm when it is deallocated.
7509+
//
7510+
// Specifically, Error::Wasm(WasmError::ModuleNotFound) was selected as the placeholder value.
7511+
return unsafe {
7512+
core::ptr::replace(
7513+
(vm_error as *const Error) as *mut Error,
7514+
Error::Wasm(WasmError::ModuleNotFound),
7515+
)
7516+
};
7517+
}
7518+
7519+
if let Some(vm_error) = e.root_cause().downcast_ref::<CheckErrors>() {
7520+
// SAFETY:
7521+
//
7522+
// This unsafe operation returns the value of a location pointed by `*mut T`.
7523+
//
7524+
// The purpose of this code is to take the ownership of the `vm_error` value
7525+
// since clarity::vm::errors::Error is not a Clonable type.
7526+
//
7527+
// Converting a `&T` (vm_error) to a `*mut T` doesn't cause any issues here
7528+
// because the reference is not borrowed elsewhere.
7529+
//
7530+
// The replaced `T` value is deallocated after the operation. Therefore, the chosen `T`
7531+
// is a dummy value, solely to satisfy the signature of the replace function
7532+
// and not cause harm when it is deallocated.
7533+
//
7534+
// Specifically, CheckErrors::ExpectedName was selected as the placeholder value.
7535+
return unsafe {
7536+
let err = core::ptr::replace(
7537+
(vm_error as *const CheckErrors) as *mut CheckErrors,
7538+
CheckErrors::ExpectedName,
7539+
);
7540+
7541+
<CheckErrors as std::convert::Into<Error>>::into(err)
7542+
};
7543+
}
7544+
7545+
// Check if the error is caused by
7546+
// an unreachable Wasm trap.
7547+
//
7548+
// In this case, runtime errors are handled
7549+
// by being mapped to the corresponding ClarityWasm Errors.
7550+
if let Some(Trap::UnreachableCodeReached) = e.root_cause().downcast_ref::<Trap>() {
7551+
return from_runtime_error_code(instance, &mut store, e);
7552+
}
7553+
7554+
// All other errors are treated as general runtime errors.
7555+
Error::Wasm(WasmError::Runtime(e))
7556+
}
7557+
7558+
fn from_runtime_error_code(
7559+
instance: Instance,
7560+
mut store: impl AsContextMut,
7561+
e: wasmtime::Error,
7562+
) -> Error {
7563+
let global = "runtime-error-code";
7564+
let runtime_error_code = instance
7565+
.get_global(&mut store, global)
7566+
.and_then(|glob| glob.get(&mut store).i32())
7567+
.unwrap_or_else(|| panic!("Could not find {global} global with i32 value"));
7568+
7569+
match ErrorMap::from(runtime_error_code) {
7570+
ErrorMap::NotClarityError => Error::Wasm(WasmError::Runtime(e)),
7571+
ErrorMap::ArithmeticOverflow => {
7572+
Error::Runtime(RuntimeErrorType::ArithmeticOverflow, Some(Vec::new()))
7573+
}
7574+
ErrorMap::ArithmeticUnderflow => {
7575+
Error::Runtime(RuntimeErrorType::ArithmeticUnderflow, Some(Vec::new()))
7576+
}
7577+
ErrorMap::DivisionByZero => {
7578+
Error::Runtime(RuntimeErrorType::DivisionByZero, Some(Vec::new()))
7579+
}
7580+
ErrorMap::ArithmeticLog2Error => Error::Runtime(
7581+
RuntimeErrorType::Arithmetic(LOG2_ERROR_MESSAGE.into()),
7582+
Some(Vec::new()),
7583+
),
7584+
ErrorMap::ArithmeticSqrtiError => Error::Runtime(
7585+
RuntimeErrorType::Arithmetic(SQRTI_ERROR_MESSAGE.into()),
7586+
Some(Vec::new()),
7587+
),
7588+
ErrorMap::UnwrapFailure => {
7589+
Error::Runtime(RuntimeErrorType::UnwrapFailure, Some(Vec::new()))
7590+
}
7591+
ErrorMap::Panic => {
7592+
panic!("An error has been detected in the code")
7593+
}
7594+
// TODO: UInt(42) value below is just a placeholder.
7595+
// It should be replaced by the current "thrown-value" when clarity-wasm issue #385 is resolved.
7596+
// Tests that reach this code are currently ignored.
7597+
ErrorMap::ShortReturnAssertionFailure => Error::ShortReturn(
7598+
ShortReturnType::AssertionFailed(Value::Response(ResponseData {
7599+
committed: false,
7600+
data: Box::new(Value::UInt(42)),
7601+
})),
7602+
),
7603+
ErrorMap::ArithmeticPowError => Error::Runtime(
7604+
RuntimeErrorType::Arithmetic(POW_ERROR_MESSAGE.into()),
7605+
Some(Vec::new()),
7606+
),
7607+
_ => panic!("Runtime error code {} not supported", runtime_error_code),
7608+
}
7609+
}
7610+
}

clarity/src/vm/errors.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ pub enum WasmError {
139139
UnableToWriteMemory(wasmtime::Error),
140140
ValueTypeMismatch,
141141
InvalidNoTypeInValue,
142+
InvalidListUnionTypeInValue,
142143
InvalidFunctionKind(i32),
143144
DefineFunctionCalledInRunMode,
144145
ExpectedReturnValue,
@@ -173,6 +174,7 @@ impl fmt::Display for WasmError {
173174
WasmError::UnableToWriteMemory(e) => write!(f, "Unable to write memory: {e}"),
174175
WasmError::ValueTypeMismatch => write!(f, "Value type mismatch"),
175176
WasmError::InvalidNoTypeInValue => write!(f, "Invalid no type in value"),
177+
WasmError::InvalidListUnionTypeInValue => write!(f, "Invalid list union type in value"),
176178
WasmError::InvalidFunctionKind(kind) => write!(f, "Invalid function kind: {kind}"),
177179
WasmError::DefineFunctionCalledInRunMode => {
178180
write!(f, "Define function called in run mode")

0 commit comments

Comments
 (0)