From 967e49d89f73da6ffd3ee13fc903664cf0107a3a Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 16:42:21 +0300 Subject: [PATCH] builtin: fix map memory management (fixes #24835) --- vlib/builtin/map.c.v | 1 + vlib/builtin/map.v | 25 ++++++++++++++ vlib/builtin/map_delete_reclaim_test.v | 46 ++++++++++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 vlib/builtin/map_delete_reclaim_test.v diff --git a/vlib/builtin/map.c.v b/vlib/builtin/map.c.v index b65e61284..9d3543cd1 100644 --- a/vlib/builtin/map.c.v +++ b/vlib/builtin/map.c.v @@ -131,6 +131,7 @@ fn (mut d DenseArray) zeros_to_end() { d.deletes = 0 // TODO: reallocate instead as more deletes are likely free(d.all_deleted) + d.all_deleted = nil } d.len = count old_cap := d.cap diff --git a/vlib/builtin/map.v b/vlib/builtin/map.v index 55704b869..92df65f83 100644 --- a/vlib/builtin/map.v +++ b/vlib/builtin/map.v @@ -119,6 +119,26 @@ fn (d &DenseArray) has_index(i int) bool { return d.deletes == 0 || unsafe { d.all_deleted[i] } == 0 } +@[inline] +fn (mut d DenseArray) trim_deleted_tail() { + if d.deletes == 0 { + return + } + for d.len > 0 && unsafe { d.all_deleted[d.len - 1] } != 0 { + unsafe { + d.all_deleted[d.len - 1] = 0 + } + d.deletes-- + d.len-- + } + if d.deletes == 0 { + unsafe { + free(d.all_deleted) + d.all_deleted = nil + } + } +} + // Make space to append an element and return index // The growth-factor is roughly 1.125 `(x + (x >> 3))` @[inline] @@ -672,6 +692,11 @@ fn (m &map) exists(key voidptr) bool { @[inline] fn (mut d DenseArray) delete(i int) { + if i == d.len - 1 { + d.len-- + d.trim_deleted_tail() + return + } if d.deletes == 0 { d.all_deleted = vcalloc(d.cap) // sets to 0 } diff --git a/vlib/builtin/map_delete_reclaim_test.v b/vlib/builtin/map_delete_reclaim_test.v new file mode 100644 index 000000000..5e35d07e4 --- /dev/null +++ b/vlib/builtin/map_delete_reclaim_test.v @@ -0,0 +1,46 @@ +struct DenseArrayLayoutForTest { + key_bytes int + value_bytes int +mut: + cap int + len int + deletes u32 + all_deleted &u8 = unsafe { nil } + keys &u8 = unsafe { nil } + values &u8 = unsafe { nil } +} + +struct MapLayoutForTest { + key_bytes int + value_bytes int +mut: + even_index u32 + cached_hashbits u8 + shift u8 + key_values DenseArrayLayoutForTest + metas &u32 = unsafe { nil } + extra_metas u32 + has_string_keys bool + hash_fn voidptr + key_eq_fn voidptr + clone_fn voidptr + free_fn voidptr +pub mut: + len int +} + +fn test_map_delete_reclaims_dense_array_tail() { + mut m := map[string]string{} + raw := unsafe { &MapLayoutForTest(&m) } + initial_cap := raw.key_values.cap + for i in 0 .. 100 { + key := i.str() + m[key] = key + m.delete(key) + assert m.len == 0 + assert raw.key_values.len == 0 + assert raw.key_values.deletes == 0 + assert raw.key_values.cap == initial_cap + assert raw.key_values.all_deleted == unsafe { nil } + } +} -- 2.39.5