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

openSUSE Build Service is sponsored by