[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.
This commit is contained in:
Peter Zhu 2024-10-30 16:41:55 -04:00
Родитель 40cd292f95
Коммит 29c480dd6f
3 изменённых файлов: 38 добавлений и 2 удалений

Просмотреть файл

@ -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

21
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);
}

Просмотреть файл

@ -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})