File 0916-ets-Treat-keys-containing-maps-as-unbound.patch of Package erlang
From eb7e92f0b42e9f6a6617962b00f9015e22cadadb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?John=20H=C3=B6gberg?= <john@erlang.org>
Date: Tue, 16 Apr 2024 15:27:39 +0200
Subject: [PATCH] ets: Treat keys containing maps as unbound
Fixes #8385
---
erts/emulator/beam/erl_db_hash.c | 2 +-
erts/emulator/beam/erl_db_tree.c | 2 +-
erts/emulator/beam/erl_db_util.c | 29 ++++++++++-------------------
erts/emulator/beam/erl_db_util.h | 2 +-
lib/stdlib/test/ets_SUITE.erl | 16 +++++++++++++++-
5 files changed, 28 insertions(+), 23 deletions(-)
diff --git a/erts/emulator/beam/erl_db_hash.c b/erts/emulator/beam/erl_db_hash.c
index 6eb7bde1b4..a5e6167785 100644
--- a/erts/emulator/beam/erl_db_hash.c
+++ b/erts/emulator/beam/erl_db_hash.c
@@ -2943,7 +2943,7 @@ static int analyze_pattern(DbTableHash *tb, Eterm pattern,
} else {
key = db_getkey(tb->common.keypos, tpl);
if (is_value(key)) {
- if (!db_has_variable(key)) { /* Bound key */
+ if (db_is_fully_bound(key)) {
int ix, search_slot;
HashDbTerm** bp;
erts_rwmtx_t* lck;
diff --git a/erts/emulator/beam/erl_db_tree.c b/erts/emulator/beam/erl_db_tree.c
index a439b974a7..8329764cf7 100644
--- a/erts/emulator/beam/erl_db_tree.c
+++ b/erts/emulator/beam/erl_db_tree.c
@@ -3749,7 +3749,7 @@ static enum ms_key_boundness key_boundness(DbTableCommon *tb,
key = db_getkey(tb->keypos, pattern);
if (is_non_value(key))
return MS_KEY_IMPOSSIBLE; /* can't possibly match anything */
- if (!db_has_variable(key)) { /* Bound key */
+ if (db_is_fully_bound(key)) {
*keyp = key;
return MS_KEY_BOUND;
} else if (key != am_Underscore &&
diff --git a/erts/emulator/beam/erl_db_util.c b/erts/emulator/beam/erl_db_util.c
index 8bace68d0f..7488c5c893 100644
--- a/erts/emulator/beam/erl_db_util.c
+++ b/erts/emulator/beam/erl_db_util.c
@@ -3514,11 +3514,8 @@ int db_has_map(Eterm node) {
return 0;
}
-/* check if obj is (or contains) a variable */
-/* return 1 if obj contains a variable or underscore */
-/* return 0 if obj is fully ground */
-
-int db_has_variable(Eterm node) {
+/* Check if obj is fully bound (contains no variables, underscores, or maps) */
+int db_is_fully_bound(Eterm node) {
DECLARE_ESTACK(s);
ESTACK_PUSH(s,node);
@@ -3539,30 +3536,24 @@ int db_has_variable(Eterm node) {
while(arity--) {
ESTACK_PUSH(s,*(++tuple));
}
- } else if (is_flatmap(node)) {
- Eterm *values = flatmap_get_values(flatmap_val(node));
- Uint size = flatmap_get_size(flatmap_val(node));
- ESTACK_PUSH(s, ((flatmap_t *) flatmap_val(node))->keys);
- while (size--) {
- ESTACK_PUSH(s, *(values++));
- }
- } else if (is_map(node)) { /* other map-nodes or map-heads */
- Eterm *ptr = hashmap_val(node);
- int i = hashmap_bitcount(MAP_HEADER_VAL(*ptr));
- ptr += MAP_HEADER_ARITY(*ptr);
- while(i--) { ESTACK_PUSH(s, *++ptr); }
+ } else if (is_map(node)) {
+ /* Like in Erlang code, "literal" maps in a pattern match any
+ * map that has the given elements, so they must be considered
+ * variable. */
+ DESTROY_ESTACK(s);
+ return 0;
}
break;
case TAG_PRIMARY_IMMED1:
if (node == am_Underscore || db_is_variable(node) >= 0) {
DESTROY_ESTACK(s);
- return 1;
+ return 0;
}
break;
}
}
DESTROY_ESTACK(s);
- return 0;
+ return 1;
}
/*
diff --git a/erts/emulator/beam/erl_db_util.h b/erts/emulator/beam/erl_db_util.h
index 6ed1e15104..eb129aadcd 100644
--- a/erts/emulator/beam/erl_db_util.h
+++ b/erts/emulator/beam/erl_db_util.h
@@ -450,7 +450,7 @@ void* db_store_term_comp(DbTableCommon *tb, /*May be NULL*/
Eterm db_copy_element_from_ets(DbTableCommon* tb, Process* p, DbTerm* obj,
Uint pos, Eterm** hpp, Uint extra);
int db_has_map(Eterm obj);
-int db_has_variable(Eterm obj);
+int db_is_fully_bound(Eterm obj);
int db_is_variable(Eterm obj);
void db_do_update_element(DbUpdateHandle* handle,
Sint position,
diff --git a/lib/stdlib/test/ets_SUITE.erl b/lib/stdlib/test/ets_SUITE.erl
index 588096301d..cd021e78af 100644
--- a/lib/stdlib/test/ets_SUITE.erl
+++ b/lib/stdlib/test/ets_SUITE.erl
@@ -102,6 +102,7 @@
-export([whereis_table/1]).
-export([ms_excessive_nesting/1]).
-export([error_info/1]).
+-export([bound_maps/1]).
-export([init_per_testcase/2, end_per_testcase/2]).
%% Convenience for manual testing
@@ -180,7 +181,9 @@ all() ->
test_delete_table_while_size_snapshot,
test_decentralized_counters_setting,
ms_excessive_nesting,
- error_info].
+ error_info,
+ bound_maps
+ ].
groups() ->
@@ -9061,6 +9064,17 @@ ets_apply(F, Args, Opts) ->
ets_format_args(Args) ->
lists:join(", ", [io_lib:format("~P", [A,10]) || A <- Args]).
+bound_maps(_Config) ->
+ T = ets:new('__bound_maps__', [ordered_set, public]),
+ Ref = make_ref(),
+ Attrs = [#{}, #{key => value}],
+ [ets:insert_new(T, {{Attr, Ref}, original}) || Attr <- Attrs],
+ Attr = #{},
+ Key = {Attr, Ref},
+ MS = [{{Key, '$1'},[],[{{{element, 1, '$_'}, {const, new}}}]}],
+ 2 = ets:select_replace(T, MS),
+ ok.
+
%%%
%%% Common utility functions.
%%%
--
2.35.3