From bd19f689b9522cda02bc17c80dd8ecd3d805088a Mon Sep 17 00:00:00 2001 From: Laria Carolin Chabowski Date: Sat, 28 Jan 2023 21:41:48 +0100 Subject: [PATCH] Add backtraces to errors --- src/apfl.h | 19 +++--- src/context.c | 93 ++++++++++++++++++++-------- src/context.h | 3 +- src/eval.c | 104 +++++++++++++++++++++++++++++--- src/functional-test-runner.c | 5 +- src/main.c | 38 ++---------- webpage/playground/playground.c | 36 +---------- 7 files changed, 184 insertions(+), 114 deletions(-) diff --git a/src/apfl.h b/src/apfl.h index ad4d541..6886581 100644 --- a/src/apfl.h +++ b/src/apfl.h @@ -619,12 +619,9 @@ typedef size_t apfl_slotidx; enum apfl_result { APFL_RESULT_OK, // Evaluation succeeded, value is on the stack - - // TODO: No further details are yet provided on (fatal) error. Not quite - // sure what to return / how errors/exceptions should work. Maybe a - // value + a backtrace? - APFL_RESULT_ERR, - APFL_RESULT_ERR_ALLOC, + APFL_RESULT_ERR, // Evaluation failed, an error is on the stack + APFL_RESULT_ERRERR, // Evaluation failed and an error occurred during error handling, no error on the stack. + APFL_RESULT_ERR_ALLOC, // Evaluation failed due to memory allocation error, no error on the stack. }; struct apfl_config { @@ -636,7 +633,7 @@ apfl_ctx apfl_ctx_new(struct apfl_config); void apfl_ctx_destroy(apfl_ctx); -typedef void (*apfl_panic_callback)(apfl_ctx, bool with_error_on_stack, void *); +typedef void (*apfl_panic_callback)(apfl_ctx, void *, enum apfl_result); void apfl_ctx_set_panic_callback(apfl_ctx, apfl_panic_callback, void *); @@ -645,8 +642,12 @@ typedef struct apfl_iterative_runner_data *apfl_iterative_runner; apfl_iterative_runner apfl_iterative_runner_new(apfl_ctx, struct apfl_source_reader); bool apfl_iterative_runner_next(apfl_iterative_runner); enum apfl_result apfl_iterative_runner_get_result(apfl_iterative_runner); -bool apfl_iterative_runner_has_error_on_stack(apfl_iterative_runner); bool apfl_iterative_runner_stopped_because_of_error(apfl_iterative_runner); +bool apfl_iterative_runner_run_repl( + apfl_iterative_runner, + struct apfl_format_writer w_out, + struct apfl_format_writer w_err +); void apfl_iterative_runner_destroy(apfl_iterative_runner); typedef void (*apfl_cfunc)(apfl_ctx); @@ -722,7 +723,7 @@ void apfl_push_userdata(apfl_ctx, void *); void *apfl_get_userdata(apfl_ctx, apfl_stackidx); void apfl_call(apfl_ctx, apfl_stackidx func, apfl_stackidx args); -enum apfl_result apfl_call_protected(apfl_ctx, apfl_stackidx func, apfl_stackidx args, bool *with_error_on_stack); +enum apfl_result apfl_call_protected(apfl_ctx, apfl_stackidx func, apfl_stackidx args); enum apfl_call_stack_entry_type { APFL_CSE_FUNCTION, diff --git a/src/context.c b/src/context.c index dc84668..c25268b 100644 --- a/src/context.c +++ b/src/context.c @@ -19,22 +19,33 @@ static bool current_stack_move_to_top(apfl_ctx, apfl_stackidx); static struct apfl_string *new_copied_string(struct gc *, struct apfl_string_view); APFL_NORETURN static void -panic(apfl_ctx ctx, bool with_error_on_stack, enum apfl_result result) +panic(apfl_ctx ctx, enum apfl_result result) { (void)ctx; (void)result; - (void)with_error_on_stack; fprintf(stderr, "panic!\n"); // TODO: more details abort(); } +struct protected_errcallback_data { + void *opaque_outer; + void (*errcallback)(apfl_ctx, void *); +}; + +void +protected_errcallback(apfl_ctx ctx, void *opaque) +{ + struct protected_errcallback_data *data = opaque; + data->errcallback(ctx, data->opaque_outer); +} + enum apfl_result apfl_do_protected( apfl_ctx ctx, void (*callback)(apfl_ctx, void *), void *opaque, - bool *with_error_on_stack + void (*errcallback)(apfl_ctx, void *) ) { struct error_handler *prev_handler = ctx->error_handler; @@ -52,17 +63,45 @@ apfl_do_protected( result = (enum apfl_result)(rv - RESULT_OFF_FOR_LONGJMP); assert(result != APFL_RESULT_OK); - struct apfl_value err; - if ((*with_error_on_stack = handler.with_error_on_stack)) { - if (!apfl_stack_pop(ctx, &err, -1)) { - *with_error_on_stack = false; - // TODO: Indicate error during error handling + bool with_error_on_stack = APFL_RESULT_ERR; + + if (with_error_on_stack && errcallback != NULL) { + struct protected_errcallback_data data = { + .opaque_outer = opaque, + .errcallback = errcallback, + }; + switch (apfl_do_protected(ctx, protected_errcallback, &data, NULL)) { + case APFL_RESULT_OK: + break; + case APFL_RESULT_ERR: + result = APFL_RESULT_ERRERR; + with_error_on_stack = false; + break; + case APFL_RESULT_ERRERR: + result = APFL_RESULT_ERRERR; + with_error_on_stack = false; + break; + case APFL_RESULT_ERR_ALLOC: + result = APFL_RESULT_ERR_ALLOC; + with_error_on_stack = false; + break; } } - apfl_gc_tmproots_restore(&ctx->gc, tmproots); - assert(callstack_len <= ctx->call_stack.cap); + struct apfl_value err; + if (with_error_on_stack && !apfl_stack_pop(ctx, &err, -1)) { + result = APFL_RESULT_ERRERR; + with_error_on_stack = false; + } + apfl_gc_tmproots_restore(&ctx->gc, tmproots); + tmproots = apfl_gc_tmproots_begin(&ctx->gc); + if (with_error_on_stack && !apfl_value_add_as_tmproot(&ctx->gc, err)) { + result = APFL_RESULT_ERRERR; + with_error_on_stack = false; + } + + assert(callstack_len <= ctx->call_stack.cap); for (size_t i = callstack_len; i < ctx->call_stack.len; i++) { apfl_call_stack_entry_deinit(ctx->gc.allocator, &ctx->call_stack.items[i]); } @@ -79,12 +118,12 @@ apfl_do_protected( ) ); - if (*with_error_on_stack) { - if (!apfl_stack_push(ctx, err)) { - *with_error_on_stack = false; - // TODO: Indicate error during error handling - } + if (with_error_on_stack && !apfl_stack_push(ctx, err)) { + result = APFL_RESULT_ERRERR; + with_error_on_stack = false; } + + apfl_gc_tmproots_restore(&ctx->gc, tmproots); } ctx->error_handler = prev_handler; @@ -105,49 +144,53 @@ call_protected_cb(apfl_ctx ctx, void *opaque) } enum apfl_result -apfl_call_protected(apfl_ctx ctx, apfl_stackidx func, apfl_stackidx args, bool *with_error_on_stack) +apfl_call_protected(apfl_ctx ctx, apfl_stackidx func, apfl_stackidx args) { struct call_protected_data data = { .func = func, .args = args, }; - return apfl_do_protected(ctx, call_protected_cb, &data, with_error_on_stack); + return apfl_do_protected(ctx, call_protected_cb, &data, NULL); } APFL_NORETURN static void -raise_error(apfl_ctx ctx, bool with_error_on_stack, enum apfl_result type) +raise_error(apfl_ctx ctx, enum apfl_result type) { assert(type != APFL_RESULT_OK); if (ctx->error_handler != NULL) { - ctx->error_handler->with_error_on_stack = with_error_on_stack; longjmp(ctx->error_handler->jump, (int)type + RESULT_OFF_FOR_LONGJMP); } if (ctx->panic_callback != NULL) { - ctx->panic_callback(ctx, with_error_on_stack, ctx->panic_callback_data); + ctx->panic_callback(ctx, ctx->panic_callback_data, type); } - panic(ctx, with_error_on_stack, type); + panic(ctx, type); } APFL_NORETURN void apfl_raise_error(apfl_ctx ctx, apfl_stackidx idx) { - bool ok = current_stack_move_to_top(ctx, idx); - raise_error(ctx, ok, APFL_RESULT_ERR); + if (!current_stack_move_to_top(ctx, idx)) { + raise_error(ctx, APFL_RESULT_ERRERR); + } + raise_error(ctx, APFL_RESULT_ERR); } APFL_NORETURN void apfl_raise_const_error(apfl_ctx ctx, const char *message) { - raise_error(ctx, try_push_const_string(ctx, message), APFL_RESULT_ERR); + if (!try_push_const_string(ctx, message)) { + raise_error(ctx, APFL_RESULT_ERRERR); + } + raise_error(ctx, APFL_RESULT_ERR); } APFL_NORETURN void apfl_raise_alloc_error(apfl_ctx ctx) { - raise_error(ctx, false, APFL_RESULT_ERR_ALLOC); + raise_error(ctx, APFL_RESULT_ERR_ALLOC); } APFL_NORETURN void diff --git a/src/context.h b/src/context.h index eed519a..a1c24f7 100644 --- a/src/context.h +++ b/src/context.h @@ -129,7 +129,6 @@ struct call_stack { struct error_handler { jmp_buf jump; - bool with_error_on_stack; }; struct iterative_runners_list { @@ -202,7 +201,7 @@ enum apfl_result apfl_do_protected( apfl_ctx ctx, void (*callback)(apfl_ctx, void *), void *opaque, - bool *with_error_on_stack + void (*errcallback)(apfl_ctx, void *) ); bool apfl_ctx_register_iterative_runner(apfl_ctx, apfl_iterative_runner); diff --git a/src/eval.c b/src/eval.c index d3d0628..5e1cb32 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1353,7 +1353,6 @@ struct apfl_iterative_runner_data { apfl_tokenizer_ptr tokenizer; apfl_parser_ptr parser; enum apfl_result result; - bool with_error_on_stack; enum interative_runner_state state; struct scope *scope; }; @@ -1494,6 +1493,54 @@ iterative_runner_next_protected(apfl_ctx ctx, void *opaque) assert(false); } +#define DECORATE_TRY_FMT(ctx, x) do { if (!(x)) { apfl_raise_alloc_error(ctx); } } while (0) + +static void +iterative_runner_decorate_error(apfl_ctx ctx, void *opaque) +{ + (void)opaque; + + struct apfl_string_builder sb = apfl_string_builder_init(ctx->gc.allocator); + struct apfl_format_writer w = apfl_format_string_writer(&sb); + + apfl_tostring(ctx, -1); + + if (!( + apfl_format_put_string(w, apfl_get_string(ctx, -1)) + && apfl_format_put_string(w, "\n\nBacktrace:") + )) { + goto fail; + } + + size_t depth = apfl_call_stack_depth(ctx); + for (size_t i = 0; i < depth; i++) { + if (!( + apfl_format_put_string(w, "\n") + && apfl_format_put_string(w, "#") + && apfl_format_put_int(w, (int)i+1) + && apfl_format_put_string(w, ": ") + && apfl_call_stack_entry_info_format( + w, + apfl_call_stack_inspect(ctx, i) + ) + )) { + goto fail; + } + } + + struct apfl_string string = apfl_string_builder_move_string(&sb); + apfl_string_builder_deinit(&sb); + + if (!apfl_move_string_onto_stack(ctx, string)) { + apfl_raise_alloc_error(ctx); + } + return; + +fail: + apfl_string_builder_deinit(&sb); + apfl_raise_alloc_error(ctx); +} + bool apfl_iterative_runner_next(apfl_iterative_runner runner) { @@ -1503,7 +1550,12 @@ apfl_iterative_runner_next(apfl_iterative_runner runner) apfl_stack_clear(runner->ctx); - runner->result = apfl_do_protected(runner->ctx, iterative_runner_next_protected, runner, &runner->with_error_on_stack); + runner->result = apfl_do_protected( + runner->ctx, + iterative_runner_next_protected, + runner, + iterative_runner_decorate_error + ); return runner->state == IRUNNER_OK; } @@ -1514,18 +1566,54 @@ apfl_iterative_runner_get_result(apfl_iterative_runner runner) return runner->result; } -bool -apfl_iterative_runner_has_error_on_stack(apfl_iterative_runner runner) -{ - return runner->with_error_on_stack; -} - bool apfl_iterative_runner_stopped_because_of_error(apfl_iterative_runner runner) { return runner->state == IRUNNER_ERR; } +bool +apfl_iterative_runner_run_repl( + apfl_iterative_runner runner, + struct apfl_format_writer w_out, + struct apfl_format_writer w_err +) { + apfl_ctx ctx = runner->ctx; + + while (apfl_iterative_runner_next(runner)) { + switch (apfl_iterative_runner_get_result(runner)) { + case APFL_RESULT_OK : + if (apfl_get_type(ctx, -1) == APFL_VALUE_NIL) { + apfl_drop(ctx, -1); + } else { + FMT_TRY(apfl_debug_print_val(ctx, -1, w_out)); + } + break; + case APFL_RESULT_ERR: + FMT_TRY(apfl_format_put_string(w_err, "Error occurred during evaluation:\n")); + if (apfl_get_type(ctx, -1) == APFL_VALUE_STRING) { + FMT_TRY(apfl_format_put_string(w_err, apfl_get_string(ctx, -1))); + } else { + FMT_TRY(apfl_debug_print_val(ctx, -1, w_err)); + } + FMT_TRY(apfl_format_put_char(w_err, '\n')); + break; + case APFL_RESULT_ERRERR: + FMT_TRY(apfl_format_put_string(w_err, "Error occurred during error handling.\n")); + break; + case APFL_RESULT_ERR_ALLOC: + FMT_TRY(apfl_format_put_string(w_err, "Fatal: Could not allocate memory.\n")); + return false; + } + } + + if (apfl_iterative_runner_stopped_because_of_error(runner)) { + return false; + } + + return true; +} + void apfl_iterative_runner_destroy(apfl_iterative_runner runner) { diff --git a/src/functional-test-runner.c b/src/functional-test-runner.c index b418965..b6b8931 100644 --- a/src/functional-test-runner.c +++ b/src/functional-test-runner.c @@ -120,7 +120,7 @@ parse_test(const struct apfl_string str, struct test_parts *parts) static void dump_stack_error(apfl_ctx ctx, apfl_iterative_runner runner) { - if (apfl_iterative_runner_has_error_on_stack(runner)) { + if (apfl_iterative_runner_get_result(runner) == APFL_RESULT_ERR) { assert(apfl_debug_print_val(ctx, -1, apfl_format_file_writer(stderr))); } } @@ -175,6 +175,9 @@ runtest(const char *filename) fprintf(stderr, "Error occurred during evaluation.\n"); dump_stack_error(ctx, runner); return T_ERR; + case APFL_RESULT_ERRERR: + fprintf(stderr, "Error while handling error.\n"); + return T_ERR; case APFL_RESULT_ERR_ALLOC: fprintf(stderr, "Fatal: Could not allocate memory.\n"); return T_FATAL; diff --git a/src/main.c b/src/main.c index fb781e3..9300928 100644 --- a/src/main.c +++ b/src/main.c @@ -138,22 +138,15 @@ exit: return rv; } -static void -dump_stack_error(apfl_ctx ctx, apfl_iterative_runner runner) -{ - if (apfl_iterative_runner_has_error_on_stack(runner)) { - assert(apfl_debug_print_val(ctx, -1, apfl_format_file_writer(stderr))); - } -} - static int repl_eval(void) { - int rv = 0; + struct apfl_format_writer w_out = apfl_format_file_writer(stdout); + struct apfl_format_writer w_err = apfl_format_file_writer(stderr); apfl_ctx ctx = apfl_ctx_new((struct apfl_config) { .allocator = apfl_allocator_default(), - .output_writer = apfl_format_file_writer(stdout), + .output_writer = w_out, }); if (ctx == NULL) { fprintf(stderr, "Failed to init the context\n"); @@ -167,31 +160,8 @@ repl_eval(void) return 1; } - while (apfl_iterative_runner_next(runner)) { - switch (apfl_iterative_runner_get_result(runner)) { - case APFL_RESULT_OK : - if (apfl_get_type(ctx, -1) == APFL_VALUE_NIL) { - apfl_drop(ctx, -1); - } else { - assert(apfl_debug_print_val(ctx, -1, apfl_format_file_writer(stdout))); - } - break; - case APFL_RESULT_ERR: - dump_stack_error(ctx, runner); - fprintf(stderr, "Error occurred during evaluation.\n"); - break; - case APFL_RESULT_ERR_ALLOC: - fprintf(stderr, "Fatal: Could not allocate memory.\n"); - rv = 1; - goto exit; - } - } + int rv = apfl_iterative_runner_run_repl(runner, w_out, w_err) ? 0 : 1; - if (apfl_iterative_runner_stopped_because_of_error(runner)) { - rv = 1; - } - -exit: apfl_iterative_runner_destroy(runner); apfl_ctx_destroy(ctx); return rv; diff --git a/webpage/playground/playground.c b/webpage/playground/playground.c index b13d2aa..793c9f9 100644 --- a/webpage/playground/playground.c +++ b/webpage/playground/playground.c @@ -62,19 +62,9 @@ playground_source_reader_cb(void *context, char *buf, size_t *len, bool need) return true; } -static void -dump_stack_error(apfl_ctx ctx, apfl_iterative_runner runner, struct apfl_format_writer w_err) -{ - if (apfl_iterative_runner_has_error_on_stack(runner)) { - assert(apfl_debug_print_val(ctx, -1, w_err)); - } -} - int main(void) { - int rv = 0; - struct apfl_format_writer w_ok = {.write = web_fmt_write, .opaque = (void *)0}; struct apfl_format_writer w_err = {.write = web_fmt_write, .opaque = (void *)1}; @@ -104,32 +94,8 @@ main(void) return 1; } - while (apfl_iterative_runner_next(runner)) { - switch (apfl_iterative_runner_get_result(runner)) { - case APFL_RESULT_OK : - if (apfl_get_type(ctx, -1) == APFL_VALUE_NIL) { - apfl_drop(ctx, -1); - } else { - assert(apfl_debug_print_val(ctx, -1, w_ok)); - } - break; - case APFL_RESULT_ERR: - dump_stack_error(ctx, runner, w_err); - assert(apfl_format_put_string(w_err, "Error occurred during evaluation.\n")); - break; - case APFL_RESULT_ERR_ALLOC: - assert(apfl_format_put_string(w_err, "Fatal: Could not allocate memory.\n")); - rv = 1; - goto exit; - } - } + int rv = apfl_iterative_runner_run_repl(runner, w_ok, w_err) ? 0 : 1; - if (apfl_iterative_runner_stopped_because_of_error(runner)) { - assert(apfl_format_put_string(w_err, "Runner stopped due to error.\n")); - rv = 1; - } - -exit: free(source_reader_data.str); apfl_iterative_runner_destroy(runner); apfl_ctx_destroy(ctx);