Skip to content

Commit e9f3fe2

Browse files
committed
Merge pull request atomvm#1959 from bettio/error-handling-rework
Error handling rework This PR fixes 2 main issues and limitations in exception handling. - re-rase now works (see also atomvm#1951) - when a function head doesn't match, arguments are now visible in stacktrace - when a bad argument is given to a NIF/BIF, arguments are visible in stacktrace too, this has been implemented starting from `erlang:integer_to_binary` This is a huge leap forward in terms of developer experience when debugging errors. These changes are made under both the "Apache 2.0" and the "GNU Lesser General Public License 2.1 or later" license terms (dual license). SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
2 parents b57b94f + ecf5329 commit e9f3fe2

24 files changed

+825
-212
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9090
instead `badarg`.
9191
- Resources are now references instead of empty binaries.
9292
- Badarg error return from calling crypto:crypto_one_time with invalid arguments now matches OTP24+.
93+
- When function head doesn't match, function arguments are now in stacktrace
94+
- Function arguments are added to stacktrace also for some NIFs, when one of the arguments is badarg
9395

9496
### Fixed
9597

@@ -106,6 +108,7 @@ instead `badarg`.
106108
- Fix supervisor crash if a `one_for_one` child fails to restart.
107109
- Fix collision in references created with `make_ref/0` on 32 bits platforms.
108110
- Fixed a bug in `OP_BS_CREATE_BIN`
111+
- Fix re-raise behavior by implementing `erlang:raise/3` 3rd argument support
109112

110113
## [0.6.7] - Unreleased
111114

libs/jit/src/jit.erl

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -175,28 +175,37 @@ first_pass(
175175
% 2
176176
first_pass(<<?OP_FUNC_INFO, Rest0/binary>>, MMod, MSt0, #state{tail_cache = TC} = State0) ->
177177
?ASSERT_ALL_NATIVE_FREE(MSt0),
178-
{_ModuleAtom, Rest1} = decode_atom(Rest0),
179-
{_FunctionName, Rest2} = decode_atom(Rest1),
180-
{_Arity, Rest3} = decode_literal(Rest2),
181-
?TRACE("OP_FUNC_INFO ~p, ~p, ~p\n", [_ModuleAtom, _FunctionName, _Arity]),
182-
% Implement function clause at the previous label.
178+
{_ModuleAtomIndex, Rest1} = decode_atom(Rest0),
179+
{FunctionAtomIndex, Rest2} = decode_atom(Rest1),
180+
{Arity, Rest3} = decode_literal(Rest2),
181+
?TRACE("OP_FUNC_INFO ~p, ~p, ~p\n", [_ModuleAtomIndex, FunctionAtomIndex, Arity]),
183182
Offset = MMod:offset(MSt0),
184183
{MSt1, OffsetReg} = MMod:move_to_native_register(MSt0, Offset),
185-
TailCacheKey = {call_primitive_last, ?PRIM_RAISE_ERROR, [OffsetReg, ?FUNCTION_CLAUSE_ATOM]},
186-
State1 =
184+
{MSt2, FunctionAtomIndexReg} = MMod:move_to_native_register(MSt1, FunctionAtomIndex),
185+
{MSt3, ArityReg} = MMod:move_to_native_register(MSt2, Arity),
186+
TailCacheKey =
187+
{call_primitive_last, ?PRIM_RAISE_ERROR_MFA, [OffsetReg, FunctionAtomIndexReg, ArityReg]},
188+
{MSt4, State1} =
187189
case lists:keyfind(TailCacheKey, 1, TC) of
188190
false ->
189-
MSt3 = MMod:call_primitive_last(MSt1, ?PRIM_RAISE_ERROR, [
190-
ctx, jit_state, {free, OffsetReg}, ?FUNCTION_CLAUSE_ATOM
191+
CacheOffset = MMod:offset(MSt3),
192+
MSt4a = MMod:call_primitive_last(MSt3, ?PRIM_RAISE_ERROR_MFA, [
193+
ctx,
194+
jit_state,
195+
{free, OffsetReg},
196+
{free, FunctionAtomIndexReg},
197+
{free, ArityReg}
191198
]),
192-
State0#state{tail_cache = [{TailCacheKey, Offset} | TC]};
199+
{MSt4a, State0#state{tail_cache = [{TailCacheKey, CacheOffset} | TC]}};
193200
{TailCacheKey, CacheOffset} ->
194-
MSt2 = MMod:jump_to_offset(MSt1, CacheOffset),
195-
MSt3 = MMod:free_native_registers(MSt2, [OffsetReg]),
196-
State0
201+
MSt4a = MMod:jump_to_offset(MSt3, CacheOffset),
202+
MSt4b = MMod:free_native_registers(MSt4a, [
203+
OffsetReg, FunctionAtomIndexReg, ArityReg
204+
]),
205+
{MSt4b, State0}
197206
end,
198-
?ASSERT_ALL_NATIVE_FREE(MSt3),
199-
first_pass(Rest3, MMod, MSt3, State1);
207+
?ASSERT_ALL_NATIVE_FREE(MSt4),
208+
first_pass(Rest3, MMod, MSt4, State1);
200209
% 3
201210
first_pass(
202211
<<?OP_INT_CALL_END>>, MMod, MSt0, #state{labels_count = LabelsCount} = State
@@ -276,7 +285,7 @@ first_pass(<<?OP_CALL_EXT, Rest0/binary>>, MMod, MSt0, State0) ->
276285
?TRACE("OP_CALL_EXT ~p, ~p\n", [Arity, Index]),
277286
MSt1 = MMod:decrement_reductions_and_maybe_schedule_next(MSt0),
278287
MSt2 = MMod:call_primitive_with_cp(MSt1, ?PRIM_CALL_EXT, [
279-
ctx, jit_state, offset, Arity, Index, -1
288+
ctx, jit_state, offset, Arity, Index, ?CALL_EXT_NO_DEALLOC_MFA
280289
]),
281290
?ASSERT_ALL_NATIVE_FREE(MSt2),
282291
first_pass(Rest2, MMod, MSt2, State0);
@@ -1034,7 +1043,9 @@ first_pass(<<?OP_CALL_EXT_ONLY, Rest0/binary>>, MMod, MSt0, State0) ->
10341043
{Index, Rest2} = decode_literal(Rest1),
10351044
?TRACE("OP_CALL_EXT_ONLY ~p, ~p\n", [Arity, Index]),
10361045
MSt1 = MMod:decrement_reductions_and_maybe_schedule_next(MSt0),
1037-
MSt2 = MMod:call_primitive_last(MSt1, ?PRIM_CALL_EXT, [ctx, jit_state, offset, Arity, Index, -1]),
1046+
MSt2 = MMod:call_primitive_last(MSt1, ?PRIM_CALL_EXT, [
1047+
ctx, jit_state, offset, Arity, Index, ?CALL_EXT_NO_DEALLOC
1048+
]),
10381049
?ASSERT_ALL_NATIVE_FREE(MSt2),
10391050
first_pass(Rest2, MMod, MSt2, State0);
10401051
% 96
@@ -1154,7 +1165,7 @@ first_pass(<<?OP_RAISE, Rest0/binary>>, MMod, MSt0, State0) ->
11541165
{MSt2, ExcValue, Rest2} = decode_compact_term(Rest1, MMod, MSt1, State0),
11551166
?TRACE("OP_RAISE ~p, ~p\n", [Stacktrace, ExcValue]),
11561167
MSt3 = MMod:call_primitive_last(MSt2, ?PRIM_RAISE, [
1157-
ctx, jit_state, offset, Stacktrace, ExcValue
1168+
ctx, jit_state, Stacktrace, ExcValue
11581169
]),
11591170
?ASSERT_ALL_NATIVE_FREE(MSt3),
11601171
first_pass(Rest2, MMod, MSt3, State0);
@@ -2020,13 +2031,13 @@ first_pass(<<?OP_RAW_RAISE, Rest0/binary>>, MMod, MSt0, State0) ->
20202031
?ASSERT_ALL_NATIVE_FREE(MSt0),
20212032
{MSt1, ExClassReg} = MMod:move_to_native_register(MSt0, {x_reg, 0}),
20222033
MSt2 = MMod:if_block(MSt1, {ExClassReg, '==', ?ERROR_ATOM}, fun(BSt0) ->
2023-
MMod:call_primitive_last(BSt0, ?PRIM_HANDLE_ERROR, [ctx, jit_state, offset])
2034+
MMod:call_primitive_last(BSt0, ?PRIM_RAW_RAISE, [ctx, jit_state])
20242035
end),
20252036
MSt3 = MMod:if_block(MSt2, {ExClassReg, '==', ?LOWERCASE_EXIT_ATOM}, fun(BSt0) ->
2026-
MMod:call_primitive_last(BSt0, ?PRIM_HANDLE_ERROR, [ctx, jit_state, offset])
2037+
MMod:call_primitive_last(BSt0, ?PRIM_RAW_RAISE, [ctx, jit_state])
20272038
end),
20282039
MSt4 = MMod:if_block(MSt3, {{free, ExClassReg}, '==', ?THROW_ATOM}, fun(BSt0) ->
2029-
MMod:call_primitive_last(BSt0, ?PRIM_HANDLE_ERROR, [ctx, jit_state, offset])
2040+
MMod:call_primitive_last(BSt0, ?PRIM_RAW_RAISE, [ctx, jit_state])
20302041
end),
20312042
MSt5 = MMod:move_to_vm_register(MSt4, ?BADARG_ATOM, {x_reg, 0}),
20322043
?ASSERT_ALL_NATIVE_FREE(MSt5),

libs/jit/src/primitives.hrl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,20 @@
9595
-define(PRIM_TERM_REUSE_BINARY, 72).
9696
-define(PRIM_ALLOC_BIG_INTEGER_FRAGMENT, 73).
9797
-define(PRIM_BITSTRING_INSERT_FLOAT, 74).
98+
-define(PRIM_RAW_RAISE, 75).
99+
-define(PRIM_RAISE_ERROR_MFA, 76).
98100

99101
% Parameters to ?PRIM_MEMORY_ENSURE_FREE_WITH_ROOTS
100102
% -define(MEMORY_NO_SHRINK, 0).
101103
-define(MEMORY_CAN_SHRINK, 1).
102104
% -define(MEMORY_FORCE_SHRINK, 2).
103105
% -define(MEMORY_NO_GC, 3).
104106

107+
% n_words parameter values for ?PRIM_CALL_EXT
108+
% n_words >= 0 means CALL_EXT_LAST (deallocate n_words from stack)
109+
-define(CALL_EXT_NO_DEALLOC, -1).
110+
-define(CALL_EXT_NO_DEALLOC_MFA, -2).
111+
105112
% term_integer_sign_t sign parameter for PRIM_ALLOC_BIG_INTEGER_FRAGMENT
106113
-define(TERM_POSITIVE_INTEGER, 0).
107114
-define(TERM_NEGATIVE_INTEGER, 4).

src/libAtomVM/bif.c

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,18 @@
4343
#include "bifs_hash.h"
4444
#pragma GCC diagnostic pop
4545

46-
#define RAISE_ERROR(error_type_atom) \
47-
do { \
48-
ctx->x[0] = ERROR_ATOM; \
49-
ctx->x[1] = (error_type_atom); \
50-
return term_invalid_term(); \
46+
#define RAISE_ERROR(error_type_atom) \
47+
do { \
48+
context_set_exception_class(ctx, ERROR_ATOM); \
49+
ctx->exception_reason = (error_type_atom); \
50+
return term_invalid_term(); \
5151
} while (0);
5252

53-
#define RAISE_ERROR_BIF(fail_label, error_type_atom) \
54-
if (fail_label == 0) { \
55-
ctx->x[0] = ERROR_ATOM; \
56-
ctx->x[1] = (error_type_atom); \
57-
} \
53+
#define RAISE_ERROR_BIF(fail_label, error_type_atom) \
54+
if (fail_label == 0) { \
55+
context_set_exception_class(ctx, ERROR_ATOM); \
56+
ctx->exception_reason = (error_type_atom); \
57+
} \
5858
return term_invalid_term();
5959

6060
#define VALIDATE_VALUE_BIF(fail_label, value, verify_function) \

src/libAtomVM/context.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ Context *context_new(GlobalContext *glb)
123123
ctx->bs = term_invalid_term();
124124
ctx->bs_offset = 0;
125125

126+
context_set_exception_class(ctx, term_nil());
127+
ctx->exception_reason = term_nil();
128+
ctx->exception_stacktrace = term_nil();
129+
126130
ctx->exit_reason = NORMAL_ATOM;
127131

128132
globalcontext_init_process(glb, ctx);

src/libAtomVM/context.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ struct Context
158158
term group_leader;
159159

160160
term exit_reason;
161+
162+
uintptr_t exception_class;
163+
term exception_reason;
164+
term exception_stacktrace;
161165
};
162166

163167
#ifndef TYPEDEF_CONTEXT
@@ -620,6 +624,32 @@ int context_get_catch_label(Context *ctx, Module **mod);
620624
*/
621625
void context_dump(Context *ctx);
622626

627+
enum
628+
{
629+
EXCEPTION_USE_LIVE_REGS_FLAG = 1
630+
};
631+
632+
static inline term context_exception_class(const Context *ctx)
633+
{
634+
return (ctx->exception_class & ~TERM_IMMED2_TAG_MASK) | TERM_IMMED2_ATOM;
635+
}
636+
637+
static inline void context_set_exception_class(Context *ctx, term exception_class)
638+
{
639+
ctx->exception_class = exception_class & ~TERM_IMMED2_TAG_MASK;
640+
}
641+
642+
static inline void context_set_exception_class_use_live_flag(Context *ctx, term exception_class)
643+
{
644+
ctx->exception_class
645+
= (exception_class & ~TERM_IMMED2_TAG_MASK) | EXCEPTION_USE_LIVE_REGS_FLAG;
646+
}
647+
648+
static inline bool context_exception_class_has_live_regs_flag(const Context *ctx)
649+
{
650+
return (ctx->exception_class & EXCEPTION_USE_LIVE_REGS_FLAG) != 0;
651+
}
652+
623653
#ifdef __cplusplus
624654
}
625655
#endif

0 commit comments

Comments
 (0)