diff --git a/canister_templates/stable.wasm b/canister_templates/stable.wasm index e268860f02..c69d1c3c7c 100644 Binary files a/canister_templates/stable.wasm and b/canister_templates/stable.wasm differ diff --git a/src/build/stable/commands/compile/candid_and_method_meta/execute.ts b/src/build/stable/commands/compile/candid_and_method_meta/execute.ts index 52e6c302d7..27e74ea804 100644 --- a/src/build/stable/commands/compile/candid_and_method_meta/execute.ts +++ b/src/build/stable/commands/compile/candid_and_method_meta/execute.ts @@ -30,6 +30,7 @@ export async function execute( len ); const message = new TextDecoder('utf8').decode(memory); + console.info(message); }, global_timer_set: (): void => {}, @@ -63,7 +64,18 @@ export async function execute( stable64_size: (): void => {}, stable64_write: (): void => {}, time: (): bigint => 0n, - trap: (): void => {} + trap: (ptr: number, len: number): void => { + const memory = new Uint8Array( + (wasmInstance.exports.memory as any).buffer, + ptr, + len + ); + const message = new TextDecoder('utf8').decode(memory); + + console.error(message); + + process.exit(1); + } } // env: { // azle_log(ptr: number, len: number) { diff --git a/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/azle_error.rs b/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/azle_error.rs deleted file mode 100644 index 4e8257f119..0000000000 --- a/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/azle_error.rs +++ /dev/null @@ -1,44 +0,0 @@ -#[derive(Debug)] -pub enum AzleError { - Init(Box), - PostUpgrade(Box), - MethodExecution(Box), - QuickJSContextNotInitialized, - QuickJSCallbackExecutionFailed(Box), - WasmDataVecToString(Box), - WasmDataStringToStruct(Box), -} - -impl std::fmt::Display for AzleError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - AzleError::QuickJSContextNotInitialized => { - write!(f, "QuickJS context not initialized") - } - AzleError::QuickJSCallbackExecutionFailed(error) => { - write!(f, "QuickJS callback execution failed: {}", error) - } - AzleError::MethodExecution(error) => { - write!(f, "Azle MethodExecutionError: {}", error) - } - AzleError::Init(error) => write!(f, "Azle InitError: {}", error), - AzleError::PostUpgrade(error) => write!(f, "Azle PostUpgradeError: {}", error), - AzleError::WasmDataVecToString(error) => { - write!( - f, - "WasmData conversion failed while converting Vec to String: {}", - error - ) - } - AzleError::WasmDataStringToStruct(error) => { - write!( - f, - "WasmData conversion failed while converting String to WasmData Struct: {}", - error - ) - } - } - } -} - -impl std::error::Error for AzleError {} diff --git a/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/candid.rs b/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/candid.rs index 68795f0f8c..a8a9960180 100644 --- a/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/candid.rs +++ b/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/candid.rs @@ -1,39 +1,25 @@ +use std::error::Error; + use crate::{ - ic, quickjs_with_ctx, wasm_binary_manipulation::get_js_code, CONTEXT_REF_CELL, MODULE_NAME, + error::{handle_promise_error, quickjs_call_with_error_handling}, + ic, quickjs_with_ctx, + wasm_binary_manipulation::get_js_code, + CONTEXT_REF_CELL, MODULE_NAME, }; -use std::error::Error; -#[no_mangle] -pub fn get_candid_and_method_meta_pointer() -> *mut std::os::raw::c_char { - std::panic::set_hook(Box::new(|panic_info| { - let msg = if let Some(s) = panic_info.payload().downcast_ref::<&str>() { - *s - } else if let Some(s) = panic_info.payload().downcast_ref::() { - s.as_str() - } else { - "Unknown panic message" - }; - - let location = if let Some(location) = panic_info.location() { - format!(" at {}:{}", location.file(), location.line()) - } else { - " (unknown location)".to_string() - }; - - let message = &format!("Panic occurred: {}{}", msg, location); - - ic_cdk::println!("{}", message); - })); +type CCharPtr = *mut std::os::raw::c_char; +#[no_mangle] +pub fn get_candid_and_method_meta_pointer() -> CCharPtr { match initialize_and_get_candid() { - Ok(c_string) => c_string, - Err(e) => { - ic_cdk::trap(&format!("Error during candid initialization: {}", e)); + Ok(c_char_ptr) => c_char_ptr, + Err(error) => { + ic_cdk::trap(&format!("Azle CandidAndMethodMetaError: {}", error)); } } } -fn initialize_and_get_candid() -> Result<*mut std::os::raw::c_char, Box> { +fn initialize_and_get_candid() -> Result> { let runtime = rquickjs::Runtime::new()?; let context = rquickjs::Context::full(&runtime)?; @@ -41,30 +27,48 @@ fn initialize_and_get_candid() -> Result<*mut std::os::raw::c_char, Box Result<*mut std::os::raw::c_char, Box> { + quickjs_with_ctx(|ctx| -> Result> { ctx.clone() .globals() .set("_azleNodeWasmEnvironment", true)?; - ic::register(ctx.clone())?; - ctx.clone() .globals() .set("exports", rquickjs::Object::new(ctx.clone())?)?; ctx.clone().globals().set("_azleExperimental", false)?; - let js = get_js_code()?; + ic::register(ctx.clone())?; - rquickjs::Module::evaluate(ctx.clone(), MODULE_NAME, std::str::from_utf8(&js)?)?; + let js = get_js_code(); - let get_candid_and_method_meta: rquickjs::Function = - ctx.globals().get("_azleGetCandidAndMethodMeta")?; + let promise = + rquickjs::Module::evaluate(ctx.clone(), MODULE_NAME, std::str::from_utf8(&js)?)?; - let candid_and_method_meta: String = get_candid_and_method_meta.call(())?; + handle_promise_error(ctx.clone(), promise)?; + + let get_candid_and_method_meta: rquickjs::Function = ctx + .clone() + .globals() + .get("_azleGetCandidAndMethodMeta") + .map_err(|e| { + format!( + "Failed to get globalThis._azleGetCandidAndMethodMeta: {}", + e + ) + })?; + + let candid_and_method_meta_js_value = + quickjs_call_with_error_handling(ctx.clone(), get_candid_and_method_meta, ())?; + + let candid_and_method_meta: String = candid_and_method_meta_js_value + .as_string() + .ok_or("Failed to convert candidAndMethodMeta JS value to string")? + .to_string()?; let c_string = std::ffi::CString::new(candid_and_method_meta)?; + let c_char_ptr = c_string.into_raw(); - Ok(c_string.into_raw()) + Ok(c_char_ptr) }) } diff --git a/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/error.rs b/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/error.rs new file mode 100644 index 0000000000..ba00f6fbb1 --- /dev/null +++ b/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/error.rs @@ -0,0 +1,56 @@ +use rquickjs::function::IntoArgs; + +use crate::quickjs_with_ctx::run_event_loop; + +use std::error::Error; + +pub fn quickjs_call_with_error_handling<'a>( + ctx: rquickjs::Ctx<'a>, + function: rquickjs::Function<'a>, + args: impl IntoArgs<'a>, +) -> Result, Box> { + let result: rquickjs::Value = match function.call(args) { + Ok(value) => value, + Err(_) => trap_on_last_exception(ctx.clone())?, + }; + + run_event_loop(ctx.clone()); + + if result.is_promise() { + let promise: rquickjs::Promise = result + .clone() + .into_promise() + .ok_or("Failed to convert function call return JS value to promise")?; + handle_promise_error(ctx.clone(), promise)?; + } + + Ok(result) +} + +fn trap_on_last_exception(ctx: rquickjs::Ctx) -> Result> { + let exception: rquickjs::Exception = ctx + .clone() + .catch() + .as_exception() + .ok_or("No exception found")? + .clone(); + + ic_cdk::trap(&exception.to_string()); +} + +pub fn handle_promise_error( + ctx: rquickjs::Ctx, + promise: rquickjs::Promise, +) -> Result<(), Box> { + run_event_loop(ctx.clone()); + + match promise.state() { + rquickjs::promise::PromiseState::Rejected => { + promise.result::(); // TODO is this strictly necessary? + trap_on_last_exception(ctx.clone())?; + } + _ => {} + }; + + Ok(()) +} diff --git a/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/execute_method_js.rs b/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/execute_method_js.rs index b77716badc..207986073f 100644 --- a/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/execute_method_js.rs +++ b/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/execute_method_js.rs @@ -1,5 +1,4 @@ -use crate::quickjs_with_ctx; -use std::error::Error; +use crate::{error::handle_promise_error, quickjs_with_ctx}; #[no_mangle] #[allow(unused)] @@ -7,10 +6,16 @@ pub extern "C" fn execute_method_js(function_index: i32, pass_arg_data: i32) { let function_name = function_index.to_string(); let pass_arg_data = pass_arg_data == 1; - if let Err(error) = quickjs_with_ctx(|ctx| -> Result<(), Box> { - let callbacks: rquickjs::Object = ctx.clone().globals().get("_azleCallbacks")?; + let quickjs_result = quickjs_with_ctx(|ctx| { + let callbacks: rquickjs::Object = ctx + .clone() + .globals() + .get("_azleCallbacks") + .map_err(|e| format!("Failed to get _azleCallbacks global: {}", e))?; - let method_callback: rquickjs::Function = callbacks.get(&function_name)?; + let method_callback: rquickjs::Function = callbacks + .get(&function_name) + .map_err(|e| format!("Failed to get method callback from _azleCallbacks: {}", e))?; let candid_args = if pass_arg_data { ic_cdk::api::call::arg_data_raw() @@ -18,10 +23,16 @@ pub extern "C" fn execute_method_js(function_index: i32, pass_arg_data: i32) { vec![] }; - method_callback.call::<_, rquickjs::Undefined>((candid_args,))?; + let promise: rquickjs::Promise = method_callback + .call((candid_args,)) + .map_err(|e| format!("Failed to execute method callback: {}", e))?; + + handle_promise_error(ctx.clone(), promise)?; Ok(()) - }) { - ic_cdk::trap(&format!("Azle MethodExecutionError: {}", error)); + }); + + if let Err(e) = quickjs_result { + ic_cdk::trap(&format!("Azle CanisterMethodError: {}", e)); } } diff --git a/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/init_and_post_upgrade.rs b/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/init_and_post_upgrade.rs index dfa1cf8d5a..39642a4133 100644 --- a/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/init_and_post_upgrade.rs +++ b/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/init_and_post_upgrade.rs @@ -4,7 +4,7 @@ use crate::{ execute_method_js::execute_method_js, ic, quickjs_with_ctx, wasm_binary_manipulation::{get_js_code, get_wasm_data}, - AzleError, CONTEXT_REF_CELL, MEMORY_MANAGER_REF_CELL, MODULE_NAME, + CONTEXT_REF_CELL, MEMORY_MANAGER_REF_CELL, MODULE_NAME, }; #[inline(never)] @@ -16,9 +16,7 @@ pub extern "C" fn init(function_index: i32, pass_arg_data: i32) { format!("prevent init and post_upgrade optimization"); if let Err(e) = initialize(true, function_index, pass_arg_data) { - let azle_error = AzleError::Init(e); - - ic_cdk::trap(&azle_error.to_string()); + ic_cdk::trap(&format!("Azle InitError: {}", e)); } } @@ -70,7 +68,7 @@ fn initialize( ic_wasi_polyfill::init_with_memory(&[], &env_vars, polyfill_memory); - let js = get_js_code()?; + let js = get_js_code(); initialize_js( std::str::from_utf8(&js)?, diff --git a/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/lib.rs b/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/lib.rs index d624c2295f..490c1a9449 100644 --- a/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/lib.rs +++ b/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/lib.rs @@ -5,9 +5,9 @@ use ic_stable_structures::{ DefaultMemoryImpl, }; -mod azle_error; mod candid; mod chunk; +mod error; mod execute_method_js; mod guards; mod ic; @@ -16,10 +16,11 @@ mod quickjs_with_ctx; mod stable_b_tree_map; mod wasm_binary_manipulation; -pub use azle_error::AzleError; pub use quickjs_with_ctx::quickjs_with_ctx; -const MODULE_NAME: &str = "main"; +// TODO dynamically get the canister name +// TODO send it in through the Wasm meta data +const MODULE_NAME: &str = ".azle/[canister_name]/main.js"; #[allow(unused)] type Memory = VirtualMemory; diff --git a/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/quickjs_with_ctx.rs b/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/quickjs_with_ctx.rs index f5bdcd6cb5..c23cff4266 100644 --- a/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/quickjs_with_ctx.rs +++ b/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/quickjs_with_ctx.rs @@ -2,8 +2,10 @@ use std::error::Error; use rquickjs::Ctx; -use crate::{AzleError, CONTEXT_REF_CELL}; +use crate::CONTEXT_REF_CELL; +// TODO should we get rid of the result when calling the callback? +// TODO I am not sure we need to do that pub fn quickjs_with_ctx(callback: F) -> Result> where F: FnOnce(Ctx) -> Result>, @@ -12,11 +14,11 @@ where let context_ref = context_ref_cell.borrow(); let context = context_ref .as_ref() - .ok_or(AzleError::QuickJSContextNotInitialized)?; + .ok_or("QuickJS context not initialized")?; context.with(|ctx| { - let result = - callback(ctx.clone()).map_err(|e| AzleError::QuickJSCallbackExecutionFailed(e))?; + let result = callback(ctx.clone()) + .map_err(|e| format!("QuickJS callback execution failed: {}", e))?; run_event_loop(ctx); @@ -25,6 +27,6 @@ where }) } -fn run_event_loop(ctx: Ctx) { +pub fn run_event_loop(ctx: Ctx) { while ctx.execute_pending_job() {} } diff --git a/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/wasm_binary_manipulation.rs b/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/wasm_binary_manipulation.rs index cb3149f4bb..19f88591ae 100644 --- a/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/wasm_binary_manipulation.rs +++ b/src/build/stable/commands/compile/wasm_binary/rust/stable_canister_template/src/wasm_binary_manipulation.rs @@ -2,8 +2,6 @@ use std::error::Error; use serde::{Deserialize, Serialize}; -use crate::AzleError; - #[derive(Debug, Serialize, Deserialize)] pub struct WasmData { #[serde(rename = "envVars")] @@ -25,14 +23,14 @@ extern "C" fn js_passive_data_size() -> usize { } // TODO waiting on license inspired from https://github.com/adambratschikaye/wasm-inject-data/blob/main/src/static_wasm.rs -pub fn get_js_code() -> Result, Box> { +pub fn get_js_code() -> Vec { let size = js_passive_data_size(); let mut js_vec = vec![243; size]; let js_vec_location = js_vec.as_mut_ptr() as i32; init_js_passive_data(js_vec_location); - Ok(js_vec) + js_vec } #[inline(never)] @@ -58,10 +56,18 @@ pub fn get_wasm_data() -> Result> { init_wasm_data_passive_data(wasm_data_vec_location); - let wasm_data_str = std::str::from_utf8(&wasm_data_vec) - .map_err(|e| AzleError::WasmDataVecToString(e.into()))?; - let wasm_data: WasmData = serde_json::from_str(wasm_data_str) - .map_err(|e| AzleError::WasmDataStringToStruct(e.into()))?; + let wasm_data_str = std::str::from_utf8(&wasm_data_vec).map_err(|e| { + format!( + "WasmData conversion failed while converting Vec to String: {}", + e + ) + })?; + let wasm_data: WasmData = serde_json::from_str(wasm_data_str).map_err(|e| { + format!( + "WasmData conversion failed while converting String to WasmData struct: {}", + e + ) + })?; Ok(wasm_data) } diff --git a/src/lib/stable/canister_methods/heartbeat.ts b/src/lib/stable/canister_methods/heartbeat.ts index 42c9c8bda9..ef65efb635 100644 --- a/src/lib/stable/canister_methods/heartbeat.ts +++ b/src/lib/stable/canister_methods/heartbeat.ts @@ -11,8 +11,8 @@ export function heartbeat( index }; - globalThis._azleCallbacks[index.toString()] = (): void => { - executeAndReplyWithCandidSerde( + globalThis._azleCallbacks[index.toString()] = async (): Promise => { + await executeAndReplyWithCandidSerde( 'heartbeat', [], originalMethod.bind(globalThis._azleCanisterClassInstance), diff --git a/src/lib/stable/canister_methods/init.ts b/src/lib/stable/canister_methods/init.ts index 3195e61a4a..7781a6e077 100644 --- a/src/lib/stable/canister_methods/init.ts +++ b/src/lib/stable/canister_methods/init.ts @@ -20,10 +20,10 @@ export function init( IDL.Func(paramIdlTypes, [], ['init']) ); - globalThis._azleCallbacks[index.toString()] = ( + globalThis._azleCallbacks[index.toString()] = async ( ...args: any[] - ): void => { - executeAndReplyWithCandidSerde( + ): Promise => { + await executeAndReplyWithCandidSerde( 'init', args, originalMethod.bind(globalThis._azleCanisterClassInstance), diff --git a/src/lib/stable/canister_methods/inspect_message.ts b/src/lib/stable/canister_methods/inspect_message.ts index d55b2cf171..b2aea5ec22 100644 --- a/src/lib/stable/canister_methods/inspect_message.ts +++ b/src/lib/stable/canister_methods/inspect_message.ts @@ -12,8 +12,8 @@ export function inspectMessage( index }; - globalThis._azleCallbacks[index.toString()] = (): void => { - executeAndReplyWithCandidSerde( + globalThis._azleCallbacks[index.toString()] = async (): Promise => { + await executeAndReplyWithCandidSerde( 'inspectMessage', [], originalMethod.bind(globalThis._azleCanisterClassInstance), diff --git a/src/lib/stable/canister_methods/post_upgrade.ts b/src/lib/stable/canister_methods/post_upgrade.ts index 88aead96e0..fa45a02b3d 100644 --- a/src/lib/stable/canister_methods/post_upgrade.ts +++ b/src/lib/stable/canister_methods/post_upgrade.ts @@ -20,10 +20,10 @@ export function postUpgrade( IDL.Func(paramIdlTypes, [], ['post_upgrade']) ); - globalThis._azleCallbacks[index.toString()] = ( + globalThis._azleCallbacks[index.toString()] = async ( ...args: any[] - ): void => { - executeAndReplyWithCandidSerde( + ): Promise => { + await executeAndReplyWithCandidSerde( 'postUpgrade', args, originalMethod.bind(globalThis._azleCanisterClassInstance), diff --git a/src/lib/stable/canister_methods/pre_upgrade.ts b/src/lib/stable/canister_methods/pre_upgrade.ts index ad016b912c..61093b1ddb 100644 --- a/src/lib/stable/canister_methods/pre_upgrade.ts +++ b/src/lib/stable/canister_methods/pre_upgrade.ts @@ -11,8 +11,8 @@ export function preUpgrade( index }; - globalThis._azleCallbacks[index.toString()] = (): void => { - executeAndReplyWithCandidSerde( + globalThis._azleCallbacks[index.toString()] = async (): Promise => { + await executeAndReplyWithCandidSerde( 'preUpgrade', [], originalMethod.bind(globalThis._azleCanisterClassInstance), diff --git a/src/lib/stable/canister_methods/query.ts b/src/lib/stable/canister_methods/query.ts index 94891a049b..9c8c23f766 100644 --- a/src/lib/stable/canister_methods/query.ts +++ b/src/lib/stable/canister_methods/query.ts @@ -30,10 +30,10 @@ export function query( ['query'] ); - globalThis._azleCallbacks[index.toString()] = ( + globalThis._azleCallbacks[index.toString()] = async ( ...args: any[] - ): void => { - executeAndReplyWithCandidSerde( + ): Promise => { + await executeAndReplyWithCandidSerde( 'query', args, originalMethod.bind(globalThis._azleCanisterClassInstance), diff --git a/src/lib/stable/canister_methods/update.ts b/src/lib/stable/canister_methods/update.ts index 6cc73cc57a..3f9458b8d6 100644 --- a/src/lib/stable/canister_methods/update.ts +++ b/src/lib/stable/canister_methods/update.ts @@ -23,10 +23,10 @@ export function update( returnIdlType === undefined ? [] : [returnIdlType] ); - globalThis._azleCallbacks[index.toString()] = ( + globalThis._azleCallbacks[index.toString()] = async ( ...args: any[] - ): void => { - executeAndReplyWithCandidSerde( + ): Promise => { + await executeAndReplyWithCandidSerde( 'update', args, originalMethod.bind(globalThis._azleCanisterClassInstance), diff --git a/src/lib/stable/error.ts b/src/lib/stable/error.ts deleted file mode 100644 index af5f48c9da..0000000000 --- a/src/lib/stable/error.ts +++ /dev/null @@ -1,10 +0,0 @@ -// import { trap } from './ic_apis/trap'; // TODO why does this break bitcoin_psbt examples? https://github.com/demergent-labs/azle/issues/2003 -import { trap } from '.'; - -export function handleUncaughtError(rawError: any): never { - const error = rawError instanceof Error ? rawError : new Error(rawError); - - const azleError = `Uncaught ${error.name}: ${error.message}${error.stack}`; - - trap(azleError); -} diff --git a/src/lib/stable/execute_with_candid_serde.ts b/src/lib/stable/execute_with_candid_serde.ts index 8f1f6631c7..5fd0d5fe31 100644 --- a/src/lib/stable/execute_with_candid_serde.ts +++ b/src/lib/stable/execute_with_candid_serde.ts @@ -1,7 +1,6 @@ import { IDL, JsonValue } from '@dfinity/candid'; -import { handleUncaughtError } from './error'; -import { reply, trap } from './ic_apis'; +import { reply } from './ic_apis'; type CanisterMethodMode = | 'query' @@ -20,13 +19,9 @@ export async function executeAndReplyWithCandidSerde( returnIdlType: IDL.Type | undefined, manual: boolean ): Promise { - try { - const decodedArgs = decodeArgs(mode, args, paramIdlTypes); - const unencodedResult = await getUnencodedResult(decodedArgs, callback); - encodeResultAndReply(mode, manual, unencodedResult, returnIdlType); - } catch (error: any) { - trap(error.toString()); - } + const decodedArgs = decodeArgs(mode, args, paramIdlTypes); + const unencodedResult = await getUnencodedResult(decodedArgs, callback); + encodeResultAndReply(mode, manual, unencodedResult, returnIdlType); } function decodeArgs( @@ -50,11 +45,7 @@ async function getUnencodedResult( args: JsonValue[], callback: (...args: any) => any ): Promise { - try { - return await callback(...args); - } catch (error) { - handleUncaughtError(error); - } + return await callback(...args); } function encodeResultAndReply(