Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Beautiful errors for Rust #2197

Merged
merged 17 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified canister_templates/stable.wasm
bdemann marked this conversation as resolved.
Show resolved Hide resolved
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@ pub fn record_benchmark(function_name: &str, instructions: u64) -> Result<(), Bo
quickjs_with_ctx(|ctx| {
let timestamp = ic_cdk::api::time();

let method_names: rquickjs::Object =
ctx.clone().globals().get("_azleCanisterMethodNames")?;

let method_name: String = method_names.get(function_name)?;
let method_names: rquickjs::Object = ctx
.clone()
.globals()
.get("_azleCanisterMethodNames")
.map_err(|e| format!("Failed to get globalThis._azleCanisterMethodNames: {e}"))?;

let method_name: String = method_names.get(function_name).map_err(|e| {
format!("Failed to get globalThis._azleCanisterMethodNames[{function_name}]: {e}")
})?;

BENCHMARKS_REF_CELL.with(|benchmarks_ref_cell| {
let mut benchmarks = benchmarks_ref_cell.borrow_mut();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,66 +1,69 @@
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,
};

// TODO we might not need any of these panic hooks
type CCharPtr = *mut std::os::raw::c_char;

// Heavily inspired by https://stackoverflow.com/a/47676844
#[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 = match panic_info.payload().downcast_ref::<&str>() {
Some(s) => *s,
None => "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);
}));

let runtime = rquickjs::Runtime::new().unwrap();
let context = rquickjs::Context::full(&runtime).unwrap();
pub fn get_candid_and_method_meta_pointer() -> CCharPtr {
match initialize_and_get_candid() {
Ok(c_char_ptr) => c_char_ptr,
Err(error) => {
ic_cdk::trap(&format!("Azle CandidAndMethodMetaError: {error}"));
bdemann marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

fn initialize_and_get_candid() -> Result<CCharPtr, Box<dyn Error>> {
let runtime = rquickjs::Runtime::new()?;
bdemann marked this conversation as resolved.
Show resolved Hide resolved
let context = rquickjs::Context::full(&runtime)?;
bdemann marked this conversation as resolved.
Show resolved Hide resolved

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

quickjs_with_ctx(|ctx| {
quickjs_with_ctx(|ctx| -> Result<CCharPtr, Box<dyn Error>> {
ctx.clone()
.globals()
.set("_azleNodeWasmEnvironment", true)
.unwrap();

ic::register(ctx.clone());
.set("_azleNodeWasmEnvironment", true)?;

ctx.clone()
.globals()
.set("exports", rquickjs::Object::new(ctx.clone()).unwrap())
.unwrap();
.set("exports", rquickjs::Object::new(ctx.clone())?)?;

ctx.clone()
.globals()
.set("_azleExperimental", false)
.unwrap();
ctx.clone().globals().set("_azleExperimental", false)?;

ic::register(ctx.clone())?;
lastmjs marked this conversation as resolved.
Show resolved Hide resolved

let js = get_js_code();

// TODO is there a better name for this main module?
// TODO this returns a promise...make sure we handle it appropriately
rquickjs::Module::evaluate(ctx.clone(), MODULE_NAME, js).unwrap();
let promise =
rquickjs::Module::evaluate(ctx.clone(), MODULE_NAME, std::str::from_utf8(&js)?)?;
bdemann marked this conversation as resolved.
Show resolved Hide resolved

handle_promise_error(ctx.clone(), promise)?;

let get_candid_and_method_meta: rquickjs::Function = ctx
bdemann marked this conversation as resolved.
Show resolved Hide resolved
.clone()
.globals()
.get("_azleGetCandidAndMethodMeta")
.map_err(|e| format!("Failed to get globalThis._azleGetCandidAndMethodMeta: {e}"))?;

let get_candid_and_method_meta: rquickjs::Function =
ctx.globals().get("_azleGetCandidAndMethodMeta").unwrap();
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 = get_candid_and_method_meta.call(()).unwrap();
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).unwrap();
let c_string = std::ffi::CString::new(candid_and_method_meta)?;
bdemann marked this conversation as resolved.
Show resolved Hide resolved
let c_char_ptr = c_string.into_raw();

c_string.into_raw()
Ok(c_char_ptr)
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use std::error::Error;

use rquickjs::function::IntoArgs;

use crate::quickjs_with_ctx::run_event_loop;

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())?,
};

// TODO we run the event loop here and also in handle_promise_error, is that a problem?
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>();
trap_on_last_exception(ctx.clone())?;
}
_ => {}
};

Ok(())
}
Original file line number Diff line number Diff line change
@@ -1,35 +1,57 @@
use crate::{benchmarking::record_benchmark, quickjs_with_ctx, WASM_DATA_REF_CELL};
use crate::{
benchmarking::record_benchmark, error::quickjs_call_with_error_handling, quickjs_with_ctx,
WASM_DATA_REF_CELL,
};

#[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 = if pass_arg_data == 1 { true } else { false };
pub extern "C" fn execute_method_js(function_index: i32, pass_arg_data_raw: i32) {
let function_name = function_index.to_string();
let pass_arg_data = pass_arg_data_raw == 1;

let result = execute_method_js_with_result(function_name, pass_arg_data);

if let Err(e) = result {
ic_cdk::trap(&format!("Azle CanisterMethodError: {}", e));
}
}

fn execute_method_js_with_result(
function_name: String,
pass_arg_data: bool,
) -> Result<(), Box<dyn std::error::Error>> {
quickjs_with_ctx(|ctx| {
let callbacks: rquickjs::Object = ctx.clone().globals().get("_azleCallbacks").unwrap();
let callbacks: rquickjs::Object = ctx
.clone()
.globals()
.get("_azleCallbacks")
.map_err(|e| format!("Failed to get globalThis._azleCallbacks: {e}"))?;

let method_callback: rquickjs::Function = callbacks.get(function_name).unwrap();
let method_callback: rquickjs::Function = callbacks.get(&function_name).map_err(|e| {
format!("Failed to get globalThis._azleCallbacks[{function_name}]: {e}")
})?;

let candid_args = if pass_arg_data {
ic_cdk::api::call::arg_data_raw()
} else {
vec![]
};

method_callback
.call::<_, rquickjs::Undefined>((candid_args,))
.unwrap();
});

if WASM_DATA_REF_CELL.with(|wasm_data_ref_cell| {
wasm_data_ref_cell
.borrow()
.as_ref()
.unwrap()
.record_benchmarks
}) {
quickjs_call_with_error_handling(ctx.clone(), method_callback, (candid_args,))?;

Ok(())
})?;

let record_benchmarks = WASM_DATA_REF_CELL
.with(|wasm_data_ref_cell| wasm_data_ref_cell.borrow().clone())
.as_ref()
.ok_or("could not convert wasm_data_ref_cell to ref")?
.record_benchmarks;

if record_benchmarks {
let instructions = ic_cdk::api::performance_counter(1);
record_benchmark(&function_name, instructions);
record_benchmark(&function_name, instructions)?;
}

Ok(())
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use rquickjs::{Ctx, Function};
use ic_cdk::api::call::accept_message;
use rquickjs::{Ctx, Function, Result};

pub fn get_function(context: Ctx) -> Function {
Function::new(context, || {
ic_cdk::api::call::accept_message();
})
.unwrap()
pub fn get_function(ctx: Ctx) -> Result<Function> {
Function::new(ctx, || accept_message())
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use rquickjs::{Ctx, Function, TypedArray};
use ic_cdk::api::call::arg_data_raw;
use rquickjs::{Ctx, Function, Result};

pub fn get_function(context: Ctx) -> Function {
Function::new(context.clone(), move || {
TypedArray::<u8>::new(context.clone(), ic_cdk::api::call::arg_data_raw())
})
.unwrap()
pub fn get_function(ctx: Ctx) -> Result<Function> {
Function::new(ctx, || arg_data_raw())
}
Loading
Loading