DelSkayn / rquickjs

High level bindings to the quickjs javascript engine
MIT License
434 stars 59 forks source link

Manually thrown exceptions doesn't contain stack traces #200

Open richarddd opened 10 months ago

richarddd commented 10 months ago

Maybe i'm doing something funky here but it seems like stacks are missing from exceptions that are thrown manually. The only work around that i've found for now is to do ctx.eval::<(), _>("throw new TypeError(1)").unwrap_err(); which seams kind of hacky.

Does NOT contain stack

let error = ctx.throw(String::from("error").into_js(&ctx).unwrap());
let error = Err::<(), _>(error).catch(&ctx).unwrap_err();
if let CaughtError::Exception(error) = error {
    if let Some(stack) = error.stack() {
        println!("stack 1: {}", stack);
    }
}
let error = Exception::throw_type(&ctx, "error");
let error = Err::<(), _>(error).catch(&ctx).unwrap_err();
if let CaughtError::Exception(error) = error {
    if let Some(stack) = error.stack() {
        println!("stack 2: {}", stack);
    }
}
let error = Exception::from_message(ctx.clone(), "error").unwrap();
let error = error.throw();
let error = Err::<(), _>(error).catch(&ctx).unwrap_err();
if let CaughtError::Exception(error) = error {
    if let Some(stack) = error.stack() {
        println!("stack 3: {}", stack);
    }
}

Contains stack

let error: Error = ctx.eval::<(), _>("throw new TypeError(1)").unwrap_err();
if let CaughtError::Exception(ex) = Err::<(), _>(error).catch(&ctx).unwrap_err()
{
    if let Some(stack) = ex.stack() {
        println!("stack 4: {}", stack);
    }
}
DelSkayn commented 10 months ago

This seems to be a limitation of Quickjs.

The JS_ThrowError function has the following code:

add_backtrace = !rt->in_out_of_memory &&
    (!sf || (JS_GetFunctionBytecode(sf->cur_func) == NULL));

Resulting in backtraces only being generated when the error is generated from javascript.

richarddd commented 10 months ago

That makes sense. I wonder what’s the rationale around that decision? 🤔 Anything we want to patch or leave as is? To add a bit of context I’m trying to provide more user friendly error messages for when errors occurs in spawn. What I do now is a wrapper around ctx.spawn that captures the current stack via a throw in eval and then uses that for errors that occur in the spawned future. This seems like a kind of dirty workaround.

richarddd commented 10 months ago

I have also found in the quickjs source that using the Error constructor produces a stack on that object:

let type_error_ctor: Constructor = ctx.globals().get("TypeError")?;
let type_error: Object = type_error_ctor.construct(())?;
let stack: Option<String> = type_error.get(PredefinedAtom::Stack).ok();