From baec5bbfffb38083b4180240a0319d20c9afc2a7 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 11 May 2026 19:50:07 -0400 Subject: Fix GC compaction for compare-by-identity sets [Bug #22064] Compare-by-identity sets use the address for hashing, so we must pin it so the object does not move in GC compaction. Objects in a compare-by-identity set is not currently pinned, causing the set to be broken if the object is moved. For example: set = Set.new.compare_by_identity o = Object.new set.add(o) puts set.include?(o) GC.verify_compaction_references(expand_heap: true, toward: :empty) puts set.include?(o) It should output true twice, but it outputs true and false. --- set.c | 17 ++++++++++++++++- test/ruby/test_set.rb | 19 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/set.c b/set.c index 4d2bc997fa..1a5d0e26fa 100644 --- a/set.c +++ b/set.c @@ -123,6 +123,14 @@ struct set_object { set_table table; }; +static int +mark_and_pin_key(st_data_t key, st_data_t data) +{ + rb_gc_mark((VALUE)key); + + return ST_CONTINUE; +} + static int mark_key(st_data_t key, st_data_t data) { @@ -135,7 +143,14 @@ static void set_mark(void *ptr) { struct set_object *sobj = ptr; - if (sobj->table.entries) set_table_foreach(&sobj->table, mark_key, 0); + if (sobj->table.entries) { + if (sobj->table.type == &identhash) { + set_table_foreach(&sobj->table, mark_and_pin_key, 0); + } + else { + set_table_foreach(&sobj->table, mark_key, 0); + } + } } static void diff --git a/test/ruby/test_set.rb b/test/ruby/test_set.rb index 46d649ee73..427dd4b6b0 100644 --- a/test/ruby/test_set.rb +++ b/test/ruby/test_set.rb @@ -902,6 +902,25 @@ class TC_Set < Test::Unit::TestCase assert_equal(array.uniq.sort, set.sort) end + def test_compare_by_identity_compact + omit "compaction is not supported on this platform" unless GC.respond_to?(:compact) + + # [Bug #22064] + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + set = Set.new.compare_by_identity + + o = Object.new + set.add(o) + + assert_include(set, o) + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + assert_include(set, o) + end; + end + def test_reset [Set, Class.new(Set)].each { |klass| a = [1, 2] -- cgit v1.2.3