Skip to content

Commit

Permalink
putting a lot of error infrastructure into place in the Rust stable c…
Browse files Browse the repository at this point in the history
…rate
  • Loading branch information
lastmjs committed Oct 24, 2024
1 parent f07de25 commit 76b5cc2
Show file tree
Hide file tree
Showing 19 changed files with 179 additions and 152 deletions.
Binary file modified canister_templates/stable.wasm
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export async function execute(
len
);
const message = new TextDecoder('utf8').decode(memory);

console.info(message);
},
global_timer_set: (): void => {},
Expand Down Expand Up @@ -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) {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,70 +1,74 @@
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::<String>() {
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<dyn Error>> {
fn initialize_and_get_candid() -> Result<CCharPtr, Box<dyn Error>> {
let runtime = rquickjs::Runtime::new()?;
let context = rquickjs::Context::full(&runtime)?;

CONTEXT_REF_CELL.with(|context_ref_cell| {
*context_ref_cell.borrow_mut() = Some(context);
});

quickjs_with_ctx(|ctx| -> Result<*mut std::os::raw::c_char, Box<dyn Error>> {
quickjs_with_ctx(|ctx| -> Result<CCharPtr, Box<dyn Error>> {
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)
})
}
Original file line number Diff line number Diff line change
@@ -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<rquickjs::Value<'a>, Box<dyn std::error::Error>> {
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<T>(ctx: rquickjs::Ctx) -> Result<T, Box<dyn std::error::Error>> {
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<dyn Error>> {
run_event_loop(ctx.clone());

match promise.state() {
rquickjs::promise::PromiseState::Rejected => {
promise.result::<rquickjs::Value>(); // TODO is this strictly necessary?
trap_on_last_exception(ctx.clone())?;
}
_ => {}
};

Ok(())
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
use crate::quickjs_with_ctx;
use std::error::Error;
use crate::{error::handle_promise_error, quickjs_with_ctx};

#[no_mangle]
#[allow(unused)]
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<dyn Error>> {
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()
} else {
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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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));
}
}

Expand Down Expand Up @@ -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)?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<DefaultMemoryImpl>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<F, R>(callback: F) -> Result<R, Box<dyn Error>>
where
F: FnOnce(Ctx) -> Result<R, Box<dyn Error>>,
Expand All @@ -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);

Expand All @@ -25,6 +27,6 @@ where
})
}

fn run_event_loop(ctx: Ctx) {
pub fn run_event_loop(ctx: Ctx) {
while ctx.execute_pending_job() {}
}
Loading

0 comments on commit 76b5cc2

Please sign in to comment.