Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions src/libAtomVM/bif.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
8 changes: 4 additions & 4 deletions src/libAtomVM/nifs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
}

Expand All @@ -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();
}

Expand Down
2 changes: 2 additions & 0 deletions src/libAtomVM/nifs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
34 changes: 26 additions & 8 deletions src/libAtomVM/opcodesswitch.h
Original file line number Diff line number Diff line change
Expand Up @@ -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); \
} \
Expand All @@ -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); \
} \
Expand All @@ -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); \
Expand All @@ -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"); \
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
Comment on lines +3753 to +3757
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As described in the comment, the exception class may not be in x_regs[0] at this point, and then the error/exception is effectively lost. To avoid that, we set the exception class to error, but it may have been exit or throw as well. We can't get the exception class from the stacktrace, as it's already built, so I'm not sure where to get it from. Generally, keeping the exception class as part of the raw stacktrace feels a bit hacky to me, but I'm not sure where to keep it instead @bettio @pguyot

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]);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I don't understand why we create a stacktrace, while it's already created and we use it to retrieve the exception class and value 🤔

}
goto handle_error;
#endif

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
34 changes: 34 additions & 0 deletions src/libAtomVM/stacktrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/libAtomVM/stacktrace.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 1 addition & 7 deletions src/platforms/generic_unix/lib/platform_nifs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/platforms/rp2/src/lib/platform_nifs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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(); \
}

Expand Down
8 changes: 8 additions & 0 deletions tests/erlang_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}
)
6 changes: 6 additions & 0 deletions tests/erlang_tests/reraise_raiser.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-module(reraise_raiser).

-export([raise_error/0]).

raise_error() ->
error("foo").
11 changes: 11 additions & 0 deletions tests/erlang_tests/reraise_reraiser.erl
Original file line number Diff line number Diff line change
@@ -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.
19 changes: 19 additions & 0 deletions tests/erlang_tests/test_reraise.erl
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 2 additions & 0 deletions tests/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
Loading