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 16 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
@@ -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,68 @@
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, 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)?;

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 = 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
Loading