File 8531-erts-Check-process-registers-during-CLA-check.patch of Package erlang
From 757e922f6a89d7d590d132f72f82e469b61c6ce5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?John=20H=C3=B6gberg?= <john@erlang.org>
Date: Mon, 14 Apr 2025 20:57:17 +0200
Subject: [PATCH] erts: Check process registers during CLA check
---
erts/emulator/beam/beam_bif_load.c | 19 ++++++++-
erts/emulator/test/signal_SUITE.erl | 62 +++++++++++++++++++++++++----
2 files changed, 73 insertions(+), 8 deletions(-)
diff --git a/erts/emulator/beam/beam_bif_load.c b/erts/emulator/beam/beam_bif_load.c
index 853ae05d3f..b7309f6b6e 100644
--- a/erts/emulator/beam/beam_bif_load.c
+++ b/erts/emulator/beam/beam_bif_load.c
@@ -1092,7 +1092,24 @@ erts_check_copy_literals_gc_need(Process *c_p, int *redsp,
goto done;
}
}
-
+
+ /* Check if there are any *direct* references to literals in the process'
+ * registers.
+ *
+ * These are not guaranteed to be kept up to date, but as we can only land
+ * here during signal handling we KNOW that these are either up to date, or
+ * they are not actually live (effective arity is 0 in a `receive`). Should
+ * any of these registers contain garbage, we merely risk scheduling a
+ * pointless garbage collection as `any_heap_ref_ptrs` doesn't follow
+ * pointers, it just range-checks them. */
+ scanned += c_p->arity;
+ if (any_heap_ref_ptrs(&c_p->arg_reg[0],
+ &c_p->arg_reg[c_p->arity],
+ literals,
+ lit_bsize)) {
+ goto done;
+ }
+
res = 0; /* no need for gc */
done: {
diff --git a/erts/emulator/test/signal_SUITE.erl b/erts/emulator/test/signal_SUITE.erl
index 0c5b27a0b1..925a6e9fdf 100644
--- a/erts/emulator/test/signal_SUITE.erl
+++ b/erts/emulator/test/signal_SUITE.erl
@@ -54,11 +54,14 @@
copy_literal_area_signal_recv/1,
copy_literal_area_signal_exit/1,
copy_literal_area_signal_recv_exit/1,
+ copy_literal_area_signal_registers/1,
simultaneous_signals_basic/1,
simultaneous_signals_recv/1,
simultaneous_signals_exit/1,
simultaneous_signals_recv_exit/1]).
+-export([check_literal_conversion/1]).
+
init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) ->
[{testcase, Func}|Config].
@@ -99,6 +102,7 @@ groups() ->
copy_literal_area_signal_recv,
copy_literal_area_signal_exit,
copy_literal_area_signal_recv_exit,
+ copy_literal_area_signal_registers,
simultaneous_signals_basic,
simultaneous_signals_recv,
simultaneous_signals_exit,
@@ -827,6 +831,48 @@ copy_literal_area_signal_exit(Config) when is_list(Config) ->
copy_literal_area_signal_recv_exit(Config) when is_list(Config) ->
copy_literal_area_signal_test(true, true).
+%% Tests the case where the literal is only present in the process' saved
+%% registers. This is easy to provoke with hibernation, but can also occur
+%% if a process happens to be scheduled out on e.g. a function call with a
+%% literal argument just as it's being purged.
+copy_literal_area_signal_registers(Config) when is_list(Config) ->
+ persistent_term:put({?MODULE, ?FUNCTION_NAME}, [make_ref()]),
+ LiteralArgs = persistent_term:get({?MODULE, ?FUNCTION_NAME}),
+ true = is_list(LiteralArgs),
+ 0 = erts_debug:size_shared(LiteralArgs), %% Should be a literal...
+
+ Self = self(),
+
+ {Pid, Monitor} =
+ spawn_monitor(fun() ->
+ Self ! {sync, LiteralArgs},
+ erlang:hibernate(?MODULE,
+ check_literal_conversion,
+ LiteralArgs)
+ end),
+
+ receive
+ {sync, LiteralArgs} ->
+ receive after 500 ->
+ {current_function,{erlang,hibernate,3}} =
+ process_info(Pid, current_function)
+ end
+ end,
+
+ persistent_term:erase({?MODULE, ?FUNCTION_NAME}),
+ receive after 1 -> ok end,
+
+ literal_area_collector_test:check_idle(),
+
+ false = (0 =:= erts_debug:size_shared(LiteralArgs)),
+ Pid ! check_literal_conversion,
+
+ receive
+ {'DOWN', Monitor, process, Pid, R} ->
+ normal = R,
+ ok
+ end.
+
copy_literal_area_signal_test(RecvPair, Exit) ->
persistent_term:put({?MODULE, ?FUNCTION_NAME}, make_ref()),
Literal = persistent_term:get({?MODULE, ?FUNCTION_NAME}),
@@ -840,12 +886,7 @@ copy_literal_area_signal_test(RecvPair, Exit) ->
true ->
ok
end,
- receive check_literal_conversion -> ok end,
- receive
- Literal ->
- %% Should not be a literal anymore...
- false = (0 == erts_debug:size_shared(Literal))
- end
+ check_literal_conversion(Literal)
end,
PMs = lists:map(fun (_) ->
spawn_opt(ProcF, [link, monitor])
@@ -896,6 +937,15 @@ copy_literal_area_signal_test(RecvPair, Exit) ->
end, PMs),
ok.
+%% Exported for optional use with hibernate/3
+check_literal_conversion(Literal) ->
+ receive
+ check_literal_conversion ->
+ %% Should not be a literal anymore...
+ false = (0 == erts_debug:size_shared(Literal)),
+ ok
+ end.
+
simultaneous_signals_basic(Config) when is_list(Config) ->
simultaneous_signals_test(false, false).
--
2.43.0