Julia exceptions in C

Hi everyone,
is there a convenient way of retrieving Julia exceptions in the C interface so that they appear the same way that they do on the Julia REPL? Considering a call to an undefined_function(), on the REPL the output is :

ERROR: UndefVarError: undefined_function not defined.

Following the example on Julia on Exceptions for C embedded Julia code, the result of

printf("%s \n", jl_typeof_str(jl_exception_occurred()));

after a

jl_eval_string("undefined_function()");

simply is:

UndefVarError

which wouldn’t be much useful in more complicated scenarios. Especially considering exception calls following an include, which also need to give informations about errors at specific line numbers, etc…
Is there a way to retrieve the same kind of detail of Julia exceptions in C without having to rewrite them all with the jl_type_error C interface?

For anyone wondering, I found a solution using sprint and showerror:

    jl_eval_string("undefined_function()");

    jl_value_t* exception = jl_exception_occurred();
    jl_value_t* sprint_fun = jl_get_function(jl_main_module, "sprint");
    jl_value_t* showerror_fun = jl_get_function(jl_main_module, "showerror");
    
    JL_GC_PUSH3(&exception, &sprint_fun, &showerror_fun);

    if(exception)
    {
        const char* returned_exception = jl_string_ptr(jl_call2(sprint_fun, showerror_fun, exception));
        printf("ERROR: %s\n", returned_exception);
    }
    
    JL_GC_POP();

In https://github.com/JuliaLang/julia/pull/28886 this construction is used, which looks fairly similar to your solution.

    if (jl_exception_occurred()) {
        const char *p = jl_unbox_voidpointer(jl_eval_string("pointer(sprint(showerror, ccall(:jl_exception_occurred, Any, ())))"));
        fprintf(stderr, "%s%s\n", error_message, p);
        return 0;
    }

Note that this is for a dynamic loading setting, where the GC_PUSH macros aren’t available.

Note that there are also JL_TRY and JL_CATCH macros in C. Here is an example that shows exceptions with the help of these macros in the REPL source code.

This is wrong. Don’t call pointer.

The JL_GC_PUSH3 isn’t doing anything here. You need to do it before calling the next runtime function, i.e. you must use JL_GC_PUSH3 before calling any of the jl_get_function. You should also replace the main module with the base module and you don’t really need to root the two functions.

Thank you! That is exactly what I was looking for.

Thank you for the feedback. Why is base module better than the main module in this case? And what do you mean by “you don’t really need to root the two functions”?

Because those names are from base, not main. It can be reassigned in main.

I mean you don’t need to put those in JL_GC_PUSH. They are global constants and are always rooted.

Thanks. Is

const char *p = jl_string_ptr(jl_eval_string("sprint(showerror, ccall(:jl_exception_occurred, Any, ()))"));

fine?

It’s fine if you make sure you don’t call anything else before finish using that pointer.

Yes, it goes out of scope before any other call into Julia. I’ve fixed this in https://github.com/JuliaLang/julia/pull/28886. I’d be happy to know what else is wrong in the PR.