File 3801-erts-Make-ets-update_element-counter-always-reject-b.patch of Package erlang

From 23af43665a8bb71b535879b2b4d3363d9a493f2a Mon Sep 17 00:00:00 2001
From: Jan Uhlig <juhlig@hnc-agency.org>
Date: Mon, 2 Feb 2026 13:30:17 +0100
Subject: [PATCH] erts: Make ets:update_element/counter always reject bad
 default args

Co-authored-by: Maria Scott <maria-12648430@hnc-agency.org>
Co-authored-by: Sverker Eriksson <sverker@erlang.org>
---
 erts/emulator/beam/erl_db.c      | 24 ++++++++++++------------
 erts/emulator/beam/erl_db_hash.c |  5 +----
 erts/emulator/beam/erl_db_tree.c |  4 +---
 lib/stdlib/src/ets.erl           | 10 ++++++----
 lib/stdlib/test/ets_SUITE.erl    | 21 ++++++++++++++-------
 5 files changed, 34 insertions(+), 30 deletions(-)

diff --git a/erts/emulator/beam/erl_db.c b/erts/emulator/beam/erl_db.c
index f083353c88..3f88ae60b9 100644
--- a/erts/emulator/beam/erl_db.c
+++ b/erts/emulator/beam/erl_db.c
@@ -1391,13 +1391,8 @@ static BIF_RETTYPE do_update_element(Process *p, DbTable *tb,
     }
 
     if (!tb->common.meth->db_lookup_dbterm(p, tb, key, default_obj, &handle)) {
-        if (is_value(default_obj)) {
-            p->fvalue = EXI_DEFAULT;
-            cret = DB_ERROR_UNSPEC;
-        }
-        else {
-            cret = DB_ERROR_BADKEY;
-        }
+        ASSERT(is_non_value(default_obj));
+        cret = DB_ERROR_BADKEY;
 	goto bail_out;
     }
 
@@ -1485,9 +1480,11 @@ BIF_RETTYPE ets_update_element_4(BIF_ALIST_4)
 
     DB_BIF_GET_TABLE(tb, DB_WRITE, LCK_WRITE_REC, BIF_ets_update_element_4);
 
-    if (is_not_tuple(BIF_ARG_4)) {
+    if (is_not_tuple(BIF_ARG_4)
+	|| arityval(*tuple_val(BIF_ARG_4)) < tb->common.keypos) {
         db_unlock(tb, LCK_WRITE_REC);
-        BIF_ERROR(BIF_P, BADARG);
+        BIF_P->fvalue = EXI_DEFAULT;
+        BIF_ERROR(BIF_P, BADARG | EXF_HAS_EXT_INFO);
     }
 
     return do_update_element(BIF_P, tb, BIF_ARG_2, BIF_ARG_3, BIF_ARG_4);
@@ -1533,7 +1530,8 @@ do_update_counter(Process *p, DbTable* tb,
     }
 
     if (!tb->common.meth->db_lookup_dbterm(p, tb, arg2, arg4, &handle)) {
-        p->fvalue = is_value(arg4) ? EXI_DEFAULT : EXI_BAD_KEY;
+        ASSERT(is_non_value(arg4));
+        p->fvalue = EXI_BAD_KEY;
 	cret = DB_ERROR_BADPARAM;
 	goto bail_out; /* key not found */
     }
@@ -1720,9 +1718,11 @@ BIF_RETTYPE ets_update_counter_4(BIF_ALIST_4)
 
     DB_BIF_GET_TABLE(tb, DB_WRITE, LCK_WRITE_REC, BIF_ets_update_counter_4);
 
-    if (is_not_tuple(BIF_ARG_4)) {
+    if (is_not_tuple(BIF_ARG_4)
+	|| arityval(*tuple_val(BIF_ARG_4)) < tb->common.keypos) {
         db_unlock(tb, LCK_WRITE_REC);
-        BIF_ERROR(BIF_P, BADARG);
+        BIF_P->fvalue = EXI_DEFAULT;
+        BIF_ERROR(BIF_P, BADARG | EXF_HAS_EXT_INFO);
     }
 
     return do_update_counter(BIF_P, tb, BIF_ARG_2, BIF_ARG_3, BIF_ARG_4);
diff --git a/erts/emulator/beam/erl_db_hash.c b/erts/emulator/beam/erl_db_hash.c
index 9ae1389c60..4b8e3c552e 100644
--- a/erts/emulator/beam/erl_db_hash.c
+++ b/erts/emulator/beam/erl_db_hash.c
@@ -3909,10 +3909,7 @@ db_lookup_dbterm_hash(Process *p, DbTable *tbl, Eterm key, Eterm obj,
         int arity = arityval(*objp);
         Eterm *htop, *hend;
 
-        if (arity < tb->common.keypos) {
-            WUNLOCK_HASH_LCK_CTR(lck_ctr);
-            return 0;
-        }
+        ASSERT(arity >= tb->common.keypos);
         htop = HAlloc(p, arity + 1);
         hend = htop + arity + 1;
         sys_memcpy(htop, objp, sizeof(Eterm) * (arity + 1));
diff --git a/erts/emulator/beam/erl_db_tree.c b/erts/emulator/beam/erl_db_tree.c
index 53ea998bf8..0569d0d1e7 100644
--- a/erts/emulator/beam/erl_db_tree.c
+++ b/erts/emulator/beam/erl_db_tree.c
@@ -3505,9 +3505,7 @@ bool db_lookup_dbterm_tree_common(Process *p, DbTable *tbl, TreeDbTerm **root,
             int arity = arityval(*objp);
             Eterm *htop, *hend;
 
-            if (arity < tbl->common.keypos) {
-                return 0;
-            }
+            ASSERT(arity >= tbl->common.keypos);
             htop = HAlloc(p, arity + 1);
             hend = htop + arity + 1;
             sys_memcpy(htop, objp, sizeof(Eterm) * (arity + 1));
diff --git a/lib/stdlib/src/ets.erl b/lib/stdlib/src/ets.erl
index cee104f278..471356e24e 100644
--- a/lib/stdlib/src/ets.erl
+++ b/lib/stdlib/src/ets.erl
@@ -1728,14 +1728,15 @@ If a default object `Default` is specified, it is used as the object to be
 updated if the key is missing from the table. The value in place of the key is
 ignored and replaced by the proper key value. The return value is as if the
 default object had not been used, that is, a single updated element or a list of
-them.
+them. An invalid default object will fail the function even if the key exists in
+the table.
 
 The function fails with reason `badarg` in the following situations:
 
 - The table type is not `set` or `ordered_set`.
 - No object with the correct key exists and no default object was supplied.
 - The object has the wrong arity.
-- The default object arity is smaller than `<keypos>`.
+- The default object is not a tuple with an arity of at least `<keypos>` of the table.
 - Any field from the default object that is updated is not an integer.
 - The element to update is not an integer.
 - The element to update is also the key.
@@ -1790,14 +1791,15 @@ of an object in a `set` table, or _compare equal_ to the key of an object in an
 
 If a default object `Default` is specified, it is used as the object to be
 updated if the key is missing from the table. The value in place of the key is
-ignored and replaced by the proper key value.
+ignored and replaced by the proper key value. An invalid default object will
+fail the function even if the key exists in the table.
 
 The function fails with reason `badarg` in the following situations:
 
 - The table type is not `set` or `ordered_set`.
 - `Pos` < 1.
 - `Pos` > object arity.
-- The default object arity is smaller than `<keypos>`.
+- The default object is not a tuple with an arity of at least `<keypos>` of the table.
 - The element to update is also the key.
 """.
 -doc(#{since => <<"OTP 27.0">>}).
diff --git a/lib/stdlib/test/ets_SUITE.erl b/lib/stdlib/test/ets_SUITE.erl
index 6babc15f60..7f5389ca52 100644
--- a/lib/stdlib/test/ets_SUITE.erl
+++ b/lib/stdlib/test/ets_SUITE.erl
@@ -2712,8 +2712,12 @@ update_element_default_opts(Opts) ->
                                  error:badarg -> badarg
                              end,
 
-                    %% Ignore bad default object if key exist (for backward bug-compat)
-                    true = ets:update_element(Tab, Key, {3, d}, BadDefault),
+                    %% OTP 29: Reject bad default object even if key may exists
+                    badarg = try
+                                 ets:update_element(Tab, Key, {3, d}, BadDefault)
+                             catch
+                                 error:badarg -> badarg
+                             end,
 
 		    ets:delete(Tab)
                 end
@@ -3101,9 +3105,12 @@ update_counter_with_default_bad_default_do(Opts) ->
 	 end,
     0 = ets:info(T, size),
 
-    %% Ignore bad default object if key exist (for backward bug-compat)
+    %% OTP 29: Reject bad default object even if key exists
     true = ets:insert(T, {0,0,key}),
-    1 = ets:update_counter(T, key, {2, 1}, {0, 0}),
+    badarg = try ets:update_counter(T, key, {2, 1}, {0, 0})
+             catch
+                 error:badarg -> badarg
+             end,
     1 = ets:info(T, size),
     ok.
 
@@ -9684,7 +9691,7 @@ error_info(_Config) ->
          {update_counter, ['$Tab', key, 2, {key,not_integer}]},
          {update_counter, ['$Tab', key, 3, {key,whatever}]},
 
-         {update_counter, ['$Tab', no_key, 1, default]},
+         {update_counter, ['$Tab', no_key, 1, default], [{error_term,default}]},
          {update_counter, ['$Tab', no_key, bad_increment, {tag,0}]},
          {update_counter, ['$Tab', no_key, {1, bad_increment}, {tag,0}]},
          {update_counter, ['$Tab', no_key, {1, 42}, {tag,0}], [{error_term,keypos}]},
@@ -9708,12 +9715,12 @@ error_info(_Config) ->
 	 {update_element, ['$Tab', no_key, {0, new}, {no_key, old}], [{error_term, position}]},
 	 {update_element, ['$Tab', no_key, {1, new}, {no_key, old}], [{error_term, keypos}]},
 	 {update_element, ['$Tab', no_key, {4, new}, {no_key, old}], [{error_term, position}]},
-	 {update_element, ['$Tab', no_key, {4, new}, not_tuple]},
+	 {update_element, ['$Tab', no_key, {4, new}, not_tuple], [{error_term, default}]},
 	 {update_element, [BagTab, no_key, {1, bagged}, {no_key, old}], []},
 	 {update_element, [OneKeyTab, no_key, {0, new}, {no_key, old}], [{error_term, position}]},
 	 {update_element, [OneKeyTab, no_key, {1, new}, {no_key, old}], [{error_term, keypos}]},
 	 {update_element, [OneKeyTab, no_key, {4, new}, {no_key, old}], [{error_term, position}]},
-	 {update_element, [OneKeyTab, no_key, {4, new}, not_tuple]},
+	 {update_element, [OneKeyTab, no_key, {4, new}, not_tuple], [{error_term, default}]},
 
          {whereis, [{bad,name}], [no_table]}
         ],
-- 
2.51.0

openSUSE Build Service is sponsored by