Fix erlang:raise/3 assert with built stacktrace#2252
Fix erlang:raise/3 assert with built stacktrace#2252bettio wants to merge 2 commits intoatomvm:release-0.7from
Conversation
07263bb to
7e62129
Compare
7e62129 to
86ff1db
Compare
86ff1db to
949690a
Compare
|
https://ampcode.com/threads/T-019d4d69-3ac6-718e-8d7c-1ad32f287c7b PR Review: Fix erlang:raise/3 assert with built stacktraceCommit: SummaryWhen The fix:
Verdict: ✅ Approve with suggestionsThe core fix is correct and well-reasoned for the interpreter path. GC root handling is safe, Issues Found🔴 Critical: JIT
|
949690a to
b76f2c3
Compare
I think I fixed all of them. |
|
looks good, this is suggested for the codeql complaining, no strong preference on my part.. CodeQL Fix: Use-after-GC false positive in stacktrace.cCodeQL flagged term stacktrace_create_raw_mfa(Context *ctx, Module *mod, int current_offset, term module_atom, term function_atom, int arity)
{
- term exception_class = context_exception_class(ctx);
-
if (term_is_list(ctx->exception_stacktrace)) {
// Already a built stacktrace (possibly empty) from erlang:raise/3
...
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);
+ term_put_tuple_element(stack_info, 5, context_exception_class(ctx));
return stack_info;
}
...
ctx->x[live_x_regs - 1] = ctx->exception_reason;
- // NOLINT(term-use-after-gc) exception_class is always an atom
if (UNLIKELY(memory_ensure_free_with_roots(ctx, requested_size, live_x_regs, ctx->x, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
...
...
term_put_tuple_element(stack_info, 4, raw_stacktrace);
- term_put_tuple_element(stack_info, 5, exception_class);
+ term_put_tuple_element(stack_info, 5, context_exception_class(ctx));
return stack_info;
}No behavioral change — |
src/libAtomVM/jit.c
Outdated
| ctx->exception_stacktrace | ||
| = stacktrace_create_raw(ctx, jit_state->module, offset); | ||
| if (offset || term_is_invalid_term(ctx->exception_stacktrace) | ||
| || term_is_nonempty_list(ctx->exception_stacktrace)) { |
There was a problem hiding this comment.
| || term_is_nonempty_list(ctx->exception_stacktrace)) { | |
| || term_is_list(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; |
There was a problem hiding this comment.
We don't log here but below we do:
fprintf(stderr, "WARNING: Unable to allocate heap space for raw stacktrace\n");
Arguably, this OOM is less likely.
src/libAtomVM/opcodesswitch.h
Outdated
| // OP_RAISE can later process. | ||
| term ex_class = x_regs[0]; | ||
| if (UNLIKELY(ex_class != ERROR_ATOM && ex_class != LOWERCASE_EXIT_ATOM && ex_class != THROW_ATOM)) { | ||
| x_regs[0] = BADARG_ATOM; |
There was a problem hiding this comment.
I think we have a bug here. We should raise. We should exercise this with a test.
|
|
||
| term frame_info; | ||
|
|
||
| // on OTP <= 22 module_atom is set to erlang, when calling a function such as erlang:throw |
There was a problem hiding this comment.
Forgot to remove this with the OTP26+ change
src/libAtomVM/stacktrace.c
Outdated
When erlang:raise/3 is called with a built stacktrace (list of
{M,F,A,Loc} tuples) and the re-raised exception passes through a
try/catch whose clauses do not match, the compiler-generated catch-all
(OP_RAISE) calls stacktrace_exception_class, which asserts on a
6-tuple. The built list was stored as-is, never wrapped.
Wrap the built stacktrace into a raw 6-tuple in stacktrace_create_raw_mfa
so OP_RAISE and stacktrace_build can process it. Route OP_RAW_RAISE
through HANDLE_ERROR() so it also hits the wrapping path.
Use term_invalid_term() instead of term_nil() as the
exception_stacktrace sentinel so that an empty stacktrace [] passed to
erlang:raise/3 is correctly detected and preserved, matching OTP
behavior.
Signed-off-by: Davide Bettio <davide@uninstall.it>
b76f2c3 to
0cb6d10
Compare
|
Everything should be fixed now. |
erlang:raise/3 with an invalid exception class (not error, exit, or throw) must raise error:badarg. The NIF path returned the atom badarg as a normal value, the opcode path set x[0] and fell through, and the JIT path skipped validation entirely. Fix all three paths (nif_erlang_raise, OP_RAW_RAISE, jit_raw_raise) to properly raise error:badarg matching OTP behavior. Signed-off-by: Davide Bettio <davide@uninstall.it>
0cb6d10 to
1c34917
Compare
stacktrace_exception_classwhenerlang:raise/3is called with a built stacktrace and the re-raised exception hits a non-matching catch clause (triggering the compiler-generatedOP_RAISE)stacktrace_create_raw_mfaOP_RAW_RAISEthroughHANDLE_ERROR()so both the NIF path and theraw_raiseopcode path go through the wrapping logicstacktrace_buildfor pre-built stacktracesFixes #2185
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