diff --git a/src/libAtomVM/bif.c b/src/libAtomVM/bif.c index fc8c2febe3..0d5b955ac0 100644 --- a/src/libAtomVM/bif.c +++ b/src/libAtomVM/bif.c @@ -45,12 +45,14 @@ #define RAISE_ERROR(error_type_atom) \ ctx->x[0] = ERROR_ATOM; \ ctx->x[1] = (error_type_atom); \ + ctx->x[2] = term_nil(); \ return term_invalid_term(); #define RAISE_ERROR_BIF(fail_label, error_type_atom) \ if (fail_label == 0) { \ ctx->x[0] = ERROR_ATOM; \ ctx->x[1] = (error_type_atom); \ + ctx->x[2] = term_nil(); \ } \ return term_invalid_term(); diff --git a/src/libAtomVM/nifs.c b/src/libAtomVM/nifs.c index 819418dfac..cbd8cc3404 100644 --- a/src/libAtomVM/nifs.c +++ b/src/libAtomVM/nifs.c @@ -69,6 +69,7 @@ #define RAISE(a, b) \ ctx->x[0] = (a); \ ctx->x[1] = (b); \ + ctx->x[2] = term_nil(); \ return term_invalid_term(); #ifndef MAX @@ -2983,9 +2984,7 @@ static term nif_erlang_system_flag(Context *ctx, int argc, term argv[]) int new_value = term_to_int(value); int nb_processors = smp_get_online_processors(); if (UNLIKELY(new_value < 1) || UNLIKELY(new_value > nb_processors)) { - argv[0] = ERROR_ATOM; - argv[1] = BADARG_ATOM; - return term_invalid_term(); + RAISE_ERROR(BADARG_ATOM); } while (!ATOMIC_COMPARE_EXCHANGE_WEAK_INT(&ctx->global->online_schedulers, &old_value, new_value)) {}; return term_from_int32(old_value); @@ -3531,6 +3530,7 @@ static term nif_erlang_throw(Context *ctx, int argc, term argv[]) ctx->x[0] = THROW_ATOM; ctx->x[1] = t; + ctx->x[2] = term_nil(); return term_invalid_term(); } @@ -3544,7 +3544,7 @@ static term nif_erlang_raise(Context *ctx, int argc, term argv[]) } ctx->x[0] = ex_class; ctx->x[1] = argv[1]; - ctx->x[2] = term_nil(); + ctx->x[2] = argv[2]; return term_invalid_term(); } diff --git a/src/libAtomVM/nifs.h b/src/libAtomVM/nifs.h index a569f42c16..e9346be10c 100644 --- a/src/libAtomVM/nifs.h +++ b/src/libAtomVM/nifs.h @@ -38,12 +38,14 @@ extern "C" { if (UNLIKELY(!verify_function((value)))) { \ argv[0] = ERROR_ATOM; \ argv[1] = BADARG_ATOM; \ + argv[2] = term_nil(); \ return term_invalid_term(); \ } #define RAISE_ERROR(error_type_atom) \ ctx->x[0] = ERROR_ATOM; \ ctx->x[1] = (error_type_atom); \ + ctx->x[2] = term_nil(); \ return term_invalid_term(); const struct Nif *nifs_get(const char *mfa); diff --git a/src/libAtomVM/opcodesswitch.h b/src/libAtomVM/opcodesswitch.h index 69d55514ef..8ae80a6a25 100644 --- a/src/libAtomVM/opcodesswitch.h +++ b/src/libAtomVM/opcodesswitch.h @@ -1170,7 +1170,7 @@ static void destroy_extended_registers(Context *ctx, unsigned int live) #define PROCESS_MAYBE_TRAP_RETURN_VALUE(return_value) \ if (term_is_invalid_term(return_value)) { \ if (UNLIKELY(!context_get_flags(ctx, Trap))) { \ - HANDLE_ERROR(); \ + HANDLE_ERROR_MAYBE_STACKTRACE(); \ } else { \ SCHEDULE_WAIT(mod, pc); \ } \ @@ -1180,7 +1180,7 @@ static void destroy_extended_registers(Context *ctx, unsigned int live) if (term_is_invalid_term(return_value)) { \ if (UNLIKELY(!context_get_flags(ctx, Trap))) { \ pc = rest_pc; \ - HANDLE_ERROR(); \ + HANDLE_ERROR_MAYBE_STACKTRACE(); \ } else { \ SCHEDULE_WAIT(mod, pc); \ } \ @@ -1189,7 +1189,7 @@ static void destroy_extended_registers(Context *ctx, unsigned int live) #define PROCESS_MAYBE_TRAP_RETURN_VALUE_LAST(return_value) \ if (term_is_invalid_term(return_value)) { \ if (UNLIKELY(!context_get_flags(ctx, Trap))) { \ - HANDLE_ERROR(); \ + HANDLE_ERROR_MAYBE_STACKTRACE(); \ } else { \ DO_RETURN(); \ SCHEDULE_WAIT(mod, pc); \ @@ -1216,6 +1216,10 @@ static void destroy_extended_registers(Context *ctx, unsigned int live) x_regs[2] = stacktrace_create_raw(ctx, mod, pc - code, x_regs[0]); \ goto handle_error; +#define HANDLE_ERROR_MAYBE_STACKTRACE() \ + x_regs[2] = x_regs[2] == term_nil() ? stacktrace_create_raw(ctx, mod, pc - code, x_regs[0]) : x_regs[2]; \ + goto handle_error; + #define VERIFY_IS_INTEGER(t, opcode_name, label) \ if (UNLIKELY(!term_is_integer(t))) { \ TRACE(opcode_name ": " #t " is not an integer\n"); \ @@ -1411,6 +1415,10 @@ static bool sort_kv_pairs(struct kv_pair *kv, int size, GlobalContext *global) return true; } +static bool is_exception_class(term t) { + return t == ERROR_ATOM || t == LOWERCASE_EXIT_ATOM || t == THROW_ATOM; +} + static term maybe_alloc_boxed_integer_fragment(Context *ctx, avm_int64_t value) { #if BOXED_TERMS_REQUIRED_FOR_INT64 > 1 @@ -3741,9 +3749,19 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) #ifdef IMPL_EXECUTE_LOOP TRACE("raise/2 stacktrace=0x%lx exc_value=0x%lx\n", stacktrace, exc_value); - x_regs[0] = stacktrace_exception_class(stacktrace); - x_regs[1] = exc_value; - x_regs[2] = stacktrace_create_raw(ctx, mod, saved_pc - code, x_regs[0]); + if (stacktrace_is_built(stacktrace)) { + // FIXME: This is a temporary solution as in some niche cases of reraise the x_regs[0] is + // overwritten and it does not represent exception class + if (!is_exception_class(x_regs[0])) { + x_regs[0] = ERROR_ATOM; + } + x_regs[1] = exc_value; + x_regs[2] = stacktrace; + } else { + x_regs[0] = stacktrace_exception_class(stacktrace); + x_regs[1] = exc_value; + x_regs[2] = stacktrace_create_raw(ctx, mod, saved_pc - code, x_regs[0]); + } goto handle_error; #endif @@ -3780,7 +3798,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) break; case ERROR_ATOM_INDEX: { - x_regs[2] = stacktrace_build(ctx, &x_regs[2], 3); + x_regs[2] = stacktrace_ensure_built(ctx, &x_regs[2], 3); // MEMORY_CAN_SHRINK because catch_end is classified as gc in beam_ssa_codegen.erl if (UNLIKELY(memory_ensure_free_with_roots(ctx, TUPLE_SIZE(2) * 2, 2, x_regs + 1, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); @@ -6249,7 +6267,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) #ifdef IMPL_EXECUTE_LOOP - x_regs[0] = stacktrace_build(ctx, &x_regs[0], 1); + x_regs[0] = stacktrace_ensure_built(ctx, &x_regs[0], 1); #endif break; diff --git a/src/libAtomVM/stacktrace.c b/src/libAtomVM/stacktrace.c index 76830d0c81..d93b704381 100644 --- a/src/libAtomVM/stacktrace.c +++ b/src/libAtomVM/stacktrace.c @@ -33,6 +33,20 @@ term stacktrace_create_raw(Context *ctx, Module *mod, int current_offset, term e return exception_class; } +bool stacktrace_is_built(term stack_info_or_stacktrace) +{ + UNUSED(stack_info_or_stacktrace); + return true; +} + +term stacktrace_ensure_built(Context *ctx, term *stack_info_or_stacktrace, uint32_t live) +{ + UNUSED(ctx); + UNUSED(stack_info_or_stacktrace); + UNUSED(live); + return UNDEFINED_ATOM; +} + term stacktrace_build(Context *ctx, term *stack_info, uint32_t live) { UNUSED(ctx); @@ -257,6 +271,26 @@ static term find_path_created(const void *key, struct ModulePathPair *module_pat return term_invalid_term(); } +bool stacktrace_is_built(term stack_info_or_stacktrace) +{ + if (term_is_tuple(stack_info_or_stacktrace)) { + return false; + } else if (term_is_list(stack_info_or_stacktrace)) { + return true; + } else { + fprintf(stderr, "Error: invalid stack_info or stacktrace passed to stacktrace_is_built\n"); + AVM_ABORT(); + } +} + +term stacktrace_ensure_built(Context *ctx, term *stack_info_or_stacktrace, uint32_t live) +{ + if (stacktrace_is_built(*stack_info_or_stacktrace)) { + return *stack_info_or_stacktrace; + } + return stacktrace_build(ctx, stack_info_or_stacktrace, live); +} + term stacktrace_build(Context *ctx, term *stack_info, uint32_t live) { GlobalContext *glb = ctx->global; diff --git a/src/libAtomVM/stacktrace.h b/src/libAtomVM/stacktrace.h index d5d1228cf1..c2925527ab 100644 --- a/src/libAtomVM/stacktrace.h +++ b/src/libAtomVM/stacktrace.h @@ -30,6 +30,8 @@ extern "C" { #endif term stacktrace_create_raw(Context *ctx, Module *mod, int current_offset, term exception_class); +bool stacktrace_is_built(term stack_info_or_stacktrace); +term stacktrace_ensure_built(Context *ctx, term *stack_info_or_stacktrace, uint32_t live); /** * @brief Build a stack trace * @param ctx context diff --git a/src/platforms/generic_unix/lib/platform_nifs.c b/src/platforms/generic_unix/lib/platform_nifs.c index f94c3c44a6..fb74ff27b5 100644 --- a/src/platforms/generic_unix/lib/platform_nifs.c +++ b/src/platforms/generic_unix/lib/platform_nifs.c @@ -36,16 +36,10 @@ //#define ENABLE_TRACE #include "trace.h" -#define VALIDATE_VALUE(value, verify_function) \ - if (UNLIKELY(!verify_function((value)))) { \ - argv[0] = ERROR_ATOM; \ - argv[1] = BADARG_ATOM; \ - return term_invalid_term(); \ - } - #define RAISE_ERROR(error_type_atom) \ ctx->x[0] = ERROR_ATOM; \ ctx->x[1] = (error_type_atom); \ + ctx->x[2] = term_nil(); \ return term_invalid_term(); #if ATOMVM_HAS_MBEDTLS diff --git a/src/platforms/rp2/src/lib/platform_nifs.c b/src/platforms/rp2/src/lib/platform_nifs.c index 5f2ea026bc..b820958faa 100644 --- a/src/platforms/rp2/src/lib/platform_nifs.c +++ b/src/platforms/rp2/src/lib/platform_nifs.c @@ -48,6 +48,7 @@ if (UNLIKELY(!verify_function((value)))) { \ argv[0] = ERROR_ATOM; \ argv[1] = BADARG_ATOM; \ + ctx->x[2] = term_nil(); \ return term_invalid_term(); \ } diff --git a/tests/erlang_tests/CMakeLists.txt b/tests/erlang_tests/CMakeLists.txt index 8aeb8b9fd3..b5f5bd4945 100644 --- a/tests/erlang_tests/CMakeLists.txt +++ b/tests/erlang_tests/CMakeLists.txt @@ -551,6 +551,10 @@ compile_assembler(test_op_bs_start_match_asm) compile_erlang(test_op_bs_create_bin) compile_assembler(test_op_bs_create_bin_asm) +compile_erlang(test_reraise) +compile_erlang(reraise_reraiser) +compile_erlang(reraise_raiser) + if(Erlang_VERSION VERSION_GREATER_EQUAL "23") set(OTP23_OR_GREATER_TESTS test_op_bs_start_match_asm.beam @@ -1073,6 +1077,10 @@ add_custom_target(erlang_test_modules DEPENDS test_op_bs_start_match.beam test_op_bs_create_bin.beam + test_reraise.beam + reraise_reraiser.beam + reraise_raiser.beam + ${OTP23_OR_GREATER_TESTS} ${OTP25_OR_GREATER_TESTS} ) diff --git a/tests/erlang_tests/reraise_raiser.erl b/tests/erlang_tests/reraise_raiser.erl new file mode 100644 index 0000000000..bb0681eec5 --- /dev/null +++ b/tests/erlang_tests/reraise_raiser.erl @@ -0,0 +1,6 @@ +-module(reraise_raiser). + +-export([raise_error/0]). + +raise_error() -> + error("foo"). diff --git a/tests/erlang_tests/reraise_reraiser.erl b/tests/erlang_tests/reraise_reraiser.erl new file mode 100644 index 0000000000..19ff4d9351 --- /dev/null +++ b/tests/erlang_tests/reraise_reraiser.erl @@ -0,0 +1,11 @@ +-module(reraise_reraiser). + +-export([reraise_error/0]). + +reraise_error() -> + try + reraise_raiser:raise_error() + catch + Class:Reason:Stacktrace -> + erlang:error(erlang:raise(Class, Reason, Stacktrace)) + end. diff --git a/tests/erlang_tests/test_reraise.erl b/tests/erlang_tests/test_reraise.erl new file mode 100644 index 0000000000..2196bd008f --- /dev/null +++ b/tests/erlang_tests/test_reraise.erl @@ -0,0 +1,19 @@ +-module(test_reraise). + +-export([start/0]). + +start() -> + try + reraise_reraiser:reraise_error() + catch + Class:Reason:Stacktrace -> + error = Class, + "foo" = Reason, + + [ + {reraise_raiser, raise_error, 0, _Meta1}, + {reraise_reraiser, reraise_error, 0, _Meta2} + | _Rest + ] = Stacktrace + end, + 0. diff --git a/tests/test.c b/tests/test.c index f322316c36..2e257234de 100644 --- a/tests/test.c +++ b/tests/test.c @@ -598,6 +598,8 @@ struct Test tests[] = { TEST_CASE(test_ets), TEST_CASE(test_node), + TEST_CASE(test_reraise), + // TEST CRASHES HERE: TEST_CASE(memlimit), { NULL, 0, false, false }