summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Zhu <[email protected]>2024-10-30 16:41:55 -0400
committerPeter Zhu <[email protected]>2024-11-01 10:49:50 -0400
commit29c480dd6fca993590c82078ba797e2c4e876ac7 (patch)
treeb2a488f417fb6ebd6111c0965e3ef1e1683da427
parent40cd292f9573b763074f08dea451144c3aaf19c6 (diff)
[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.
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/11966
-rw-r--r--common.mk1
-rw-r--r--proc.c21
-rw-r--r--test/ruby/test_proc.rb18
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})