From 29c480dd6fca993590c82078ba797e2c4e876ac7 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 30 Oct 2024 16:41:55 -0400 Subject: [PATCH] [Bug #20853] Fix Proc#hash to not change after compaction The hash value of a Proc must remain constant after a compaction, otherwise it may not work as the key in a hash table. --- common.mk | 1 + proc.c | 21 +++++++++++++++++++-- test/ruby/test_proc.rb | 18 ++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/common.mk b/common.mk index d449a3e90d..dfe2dc7bdc 100644 --- a/common.mk +++ b/common.mk @@ -13032,6 +13032,7 @@ proc.$(OBJEXT): $(top_srcdir)/internal/compilers.h proc.$(OBJEXT): $(top_srcdir)/internal/error.h proc.$(OBJEXT): $(top_srcdir)/internal/eval.h proc.$(OBJEXT): $(top_srcdir)/internal/gc.h +proc.$(OBJEXT): $(top_srcdir)/internal/hash.h proc.$(OBJEXT): $(top_srcdir)/internal/imemo.h proc.$(OBJEXT): $(top_srcdir)/internal/object.h proc.$(OBJEXT): $(top_srcdir)/internal/proc.h diff --git a/proc.c b/proc.c index 8e82c13720..95b0d04791 100644 --- a/proc.c +++ b/proc.c @@ -15,6 +15,7 @@ #include "internal/error.h" #include "internal/eval.h" #include "internal/gc.h" +#include "internal/hash.h" #include "internal/object.h" #include "internal/proc.h" #include "internal/symbol.h" @@ -1437,8 +1438,24 @@ rb_hash_proc(st_index_t hash, VALUE prc) { rb_proc_t *proc; GetProcPtr(prc, proc); - hash = rb_hash_uint(hash, (st_index_t)proc->block.as.captured.code.val); - hash = rb_hash_uint(hash, (st_index_t)proc->block.as.captured.self); + + switch (vm_block_type(&proc->block)) { + case block_type_iseq: + hash = rb_st_hash_uint(hash, (st_index_t)proc->block.as.captured.code.iseq->body); + break; + case block_type_ifunc: + hash = rb_st_hash_uint(hash, (st_index_t)proc->block.as.captured.code.ifunc->func); + break; + case block_type_symbol: + hash = rb_st_hash_uint(hash, rb_any_hash(proc->block.as.symbol)); + break; + case block_type_proc: + hash = rb_st_hash_uint(hash, rb_any_hash(proc->block.as.proc)); + break; + default: + rb_bug("rb_hash_proc: unknown block type %d", vm_block_type(&proc->block)); + } + return rb_hash_uint(hash, (st_index_t)proc->block.as.captured.ep); } diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb index fcf00776a6..6f100c225f 100644 --- a/test/ruby/test_proc.rb +++ b/test/ruby/test_proc.rb @@ -168,6 +168,24 @@ class TestProc < Test::Unit::TestCase assert_operator(procs.map(&:hash).uniq.size, :>=, 500) end + def test_hash_does_not_change_after_compaction + # [Bug #20853] + [ + "proc {}", # iseq backed proc + "{}.to_proc", # ifunc backed proc + ":hello.to_proc", # symbol backed proc + ].each do |proc| + assert_separately([], <<~RUBY) + p1 = #{proc} + hash = p1.hash + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + assert_equal(hash, p1.hash, "proc is `#{proc}`") + RUBY + end + end + def test_block_par assert_equal(10, Proc.new{|&b| b.call(10)}.call {|x| x}) assert_equal(12, Proc.new{|a,&b| b.call(a)}.call(12) {|x| x})