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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ strict format validation
- Fixed locale-dependent decimal separator in `erlang:float_to_binary` and `erlang:float_to_list`
- Fixed `erlang:binary_to_float/1` and `erlang:list_to_float/1` returning `inf` for overflow instead
of raising `badarg`
- Fixed `erlang:raise/3` with a built stacktrace causing an assert when the re-raised exception
passes through a non-matching catch clause

## [0.7.0-alpha.0] - 2026-03-20

Expand Down
7 changes: 6 additions & 1 deletion src/libAtomVM/opcodesswitch.h
Original file line number Diff line number Diff line change
Expand Up @@ -5255,7 +5255,12 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
context_set_exception_class(ctx, x_regs[0]);
ctx->exception_reason = x_regs[1];
ctx->exception_stacktrace = x_regs[2];
goto handle_error;
// Use HANDLE_ERROR() instead of goto handle_error so that
// stacktrace_create_raw_mfa is called. When x_regs[2] holds
// a built stacktrace (list of {M,F,A,Loc} tuples from
// erlang:raise/3), stacktrace_create_raw_mfa wraps it into
// a raw 6-tuple that OP_RAISE can process.
HANDLE_ERROR();
}
break;
}
Expand Down
34 changes: 31 additions & 3 deletions src/libAtomVM/stacktrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,28 @@ term stacktrace_create_raw_mfa(Context *ctx, Module *mod, int current_offset, te
term exception_class = context_exception_class(ctx);

if (term_is_nonempty_list(ctx->exception_stacktrace)) {
// there is already a built stacktrace, nothing to do here
// (this happens when re-raising with raise/3
return ctx->exception_stacktrace;
// Already a built stacktrace (list of {M,F,A,Loc} tuples) from
// erlang:raise/3 NIF (via RAISE_WITH_STACKTRACE) or OP_RAW_RAISE.
// Wrap it in a raw 6-tuple so OP_RAISE can extract the exception class
// and stacktrace_build can return the list as-is.
ctx->x[0] = ctx->exception_stacktrace;
ctx->x[1] = ctx->exception_reason;
if (UNLIKELY(memory_ensure_free_with_roots(ctx, TUPLE_SIZE(6), 2, ctx->x, MEMORY_CAN_SHRINK)
!= MEMORY_GC_OK)) {
return OUT_OF_MEMORY_ATOM;
}
ctx->exception_stacktrace = ctx->x[0];
ctx->exception_reason = ctx->x[1];
term built_stacktrace = ctx->exception_stacktrace;
term stack_info = term_alloc_tuple(6, &ctx->heap);
term_put_tuple_element(stack_info, 0, term_from_int(0));
term_put_tuple_element(stack_info, 1, term_from_int(0));
term_put_tuple_element(stack_info, 2, term_from_int(0));
term_put_tuple_element(stack_info, 3, term_from_int(0));
term_put_tuple_element(stack_info, 4, built_stacktrace);
// NOLINT(term-use-after-gc) exception_class is always an atom
term_put_tuple_element(stack_info, 5, exception_class);

Check failure

Code scanning / CodeQL

Use of term variable after garbage collection Error

Term variable 'exception_class' may hold a stale value after GC call
here
. Defined
here
.
return stack_info;
}

// Check if EXCEPTION_USE_LIVE_REGS_FLAG is set
Expand Down Expand Up @@ -383,6 +402,15 @@ term stacktrace_build(Context *ctx, term *stack_info, uint32_t live)
int filename_lens = term_to_int(term_get_tuple_element(*stack_info, 2));
int num_mods = term_to_int(term_get_tuple_element(*stack_info, 3));

// Pre-built stacktrace from erlang:raise/3: element 4 already holds
// the built list, num_frames == 0. Return the list directly.
if (num_frames == 0) {
term raw_stacktrace = term_get_tuple_element(*stack_info, 4);
if (term_is_nonempty_list(raw_stacktrace)) {
return raw_stacktrace;
}
}

struct ModulePathPair *module_paths = malloc(num_mods * sizeof(struct ModulePathPair));
if (IS_NULL_PTR(module_paths)) {
fprintf(stderr, "Unable to allocate space for module paths. Returning raw stacktrace.\n");
Expand Down
2 changes: 2 additions & 0 deletions tests/erlang_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,7 @@ compile_erlang(test_lists_keyfind)
compile_erlang(test_reraise)
compile_erlang(reraise_reraiser)
compile_erlang(reraise_raiser)
compile_erlang(test_raise_built_stacktrace)

compile_erlang(stacktrace_function_args)
compile_erlang(test_multi_value_comprehension)
Expand Down Expand Up @@ -1209,6 +1210,7 @@ set(erlang_test_beams
test_reraise.beam
reraise_reraiser.beam
reraise_raiser.beam
test_raise_built_stacktrace.beam

stacktrace_function_args.beam

Expand Down
70 changes: 70 additions & 0 deletions tests/erlang_tests/test_raise_built_stacktrace.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
%
% This file is part of AtomVM.
%
% Copyright 2026 Davide Bettio <davide@uninstall.it>
%
% Licensed under the Apache License, Version 2.0 (the "License");
% you may not use this file except in compliance with the License.
% You may obtain a copy of the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS,
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
% See the License for the specific language governing permissions and
% limitations under the License.
%
% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
%

-module(test_raise_built_stacktrace).

-export([start/0, id/1]).

start() ->
ok = test_raise_direct(),
ok = test_raise_dynamic(),
0.

%% Tests the raw_raise opcode path (erlang:raise/3 compiled to raw_raise).
test_raise_direct() ->
try
try
do_raise_direct()
catch
throw:_ -> should_not_happen
end
catch
error:badarg -> ok
end.

%% Tests the NIF path (dynamic apply bypasses raw_raise opcode).
test_raise_dynamic() ->
try
try
do_raise_dynamic()
catch
throw:_ -> should_not_happen
end
catch
error:badarg -> ok
end.

do_raise_direct() ->
try
erlang:error(badarg)
catch
error:badarg:Stacktrace ->
erlang:raise(error, badarg, Stacktrace)
end.

do_raise_dynamic() ->
try
erlang:error(badarg)
catch
error:badarg:Stacktrace ->
apply(?MODULE:id(erlang), ?MODULE:id(raise), [error, badarg, Stacktrace])
end.

id(X) -> X.
1 change: 1 addition & 0 deletions tests/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,7 @@ struct Test tests[] = {
TEST_CASE(test_lists_keyfind),

TEST_CASE_COND(test_reraise, 0, SKIP_STACKTRACES),
TEST_CASE_COND(test_raise_built_stacktrace, 0, SKIP_STACKTRACES),
TEST_CASE_COND(stacktrace_function_args, 0, SKIP_STACKTRACES),

TEST_CASE(test_inline_arith),
Expand Down
Loading