Skip to content

Commit

Permalink
Merge pull request #2197 from demergent-labs/beautiful_errors
Browse files Browse the repository at this point in the history
Beautiful errors for Rust
  • Loading branch information
lastmjs authored Nov 1, 2024
2 parents b1592ee + 1098a57 commit c6e30aa
Show file tree
Hide file tree
Showing 67 changed files with 1,083 additions and 805 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
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::{cell::RefCell, error::Error};

use candid::CandidType;
use ic_cdk::api::time;
use rquickjs::Object;

use crate::quickjs_with_ctx;

Expand All @@ -17,12 +19,17 @@ thread_local! {

pub fn record_benchmark(function_name: &str, instructions: u64) -> Result<(), Box<dyn Error>> {
quickjs_with_ctx(|ctx| {
let timestamp = ic_cdk::api::time();
let timestamp = time();

let method_names: rquickjs::Object =
ctx.clone().globals().get("_azleCanisterMethodNames")?;
let method_names: 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)?;
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, ffi::CString, os::raw::c_char, str};

use ic_cdk::trap;
use rquickjs::{Context, Function, Module, Object, Runtime};

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::register,
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 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) => {
trap(&format!("Azle CandidAndMethodMetaError: {error}"));
}
}
}

fn initialize_and_get_candid() -> Result<CCharPtr, Box<dyn Error>> {
let runtime = Runtime::new()?;
let context = Context::full(&runtime)?;

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

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

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

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

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

register(ctx.clone())?;

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 = Module::evaluate(ctx.clone(), MODULE_NAME, str::from_utf8(&js)?)?;

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

let get_candid_and_method_meta: Function = ctx
.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 = CString::new(candid_and_method_meta)?;
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
@@ -1,7 +1,9 @@
use ic_cdk::{api::call::call_raw128, id};

#[allow(unused)]
pub async fn chunk() {
let id = ic_cdk::id();
let id = id();
let method = "_azle_chunk";
let args_raw = [68, 73, 68, 76, 0, 0]; // '()' pre encoded
let _ = ic_cdk::api::call::call_raw128(id, method, args_raw, 0).await;
let _ = call_raw128(id, method, args_raw, 0).await;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use std::error::Error;

use ic_cdk::trap;
use rquickjs::{
function::IntoArgs, promise::PromiseState, Ctx, Exception, Function, Promise, Value,
};

use crate::quickjs_with_ctx::run_event_loop;

pub fn quickjs_call_with_error_handling<'a>(
ctx: Ctx<'a>,
function: Function<'a>,
args: impl IntoArgs<'a>,
) -> Result<Value<'a>, Box<dyn Error>> {
let result: 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: 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: Ctx) -> Result<T, Box<dyn Error>> {
let exception: Exception = ctx
.clone()
.catch()
.as_exception()
.ok_or("No exception found")?
.clone();

trap(&exception.to_string());
}

pub fn handle_promise_error(ctx: Ctx, promise: Promise) -> Result<(), Box<dyn Error>> {
run_event_loop(ctx.clone());

match promise.state() {
PromiseState::Rejected => {
promise.result::<Value>();
trap_on_last_exception(ctx.clone())?;
}
_ => {}
};

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

use ic_cdk::{
api::{call::arg_data_raw, performance_counter},
trap,
};
use rquickjs::{Function, Object};

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 {
trap(&format!("Azle CanisterMethodError: {}", e));
}
}

fn execute_method_js_with_result(
function_name: String,
pass_arg_data: bool,
) -> Result<(), Box<dyn Error>> {
quickjs_with_ctx(|ctx| {
let callbacks: rquickjs::Object = ctx.clone().globals().get("_azleCallbacks").unwrap();
let callbacks: 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: 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()
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
}) {
let instructions = ic_cdk::api::performance_counter(1);
record_benchmark(&function_name, instructions);
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 = performance_counter(1);
record_benchmark(&function_name, instructions)?;
}

Ok(())
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use ic_cdk::api::{caller, is_controller};

#[allow(unused)]
pub fn guard_against_non_controllers() -> Result<(), String> {
if ic_cdk::api::is_controller(&ic_cdk::api::caller()) {
if is_controller(&caller()) {
return Ok(());
}
return Err(
Expand Down
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

0 comments on commit c6e30aa

Please sign in to comment.