Skip to content

Commit 29c480d

Browse files
committed
[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.
1 parent 40cd292 commit 29c480d

File tree

3 files changed

+38
-2
lines changed

3 files changed

+38
-2
lines changed

common.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13032,6 +13032,7 @@ proc.$(OBJEXT): $(top_srcdir)/internal/compilers.h
1303213032
proc.$(OBJEXT): $(top_srcdir)/internal/error.h
1303313033
proc.$(OBJEXT): $(top_srcdir)/internal/eval.h
1303413034
proc.$(OBJEXT): $(top_srcdir)/internal/gc.h
13035+
proc.$(OBJEXT): $(top_srcdir)/internal/hash.h
1303513036
proc.$(OBJEXT): $(top_srcdir)/internal/imemo.h
1303613037
proc.$(OBJEXT): $(top_srcdir)/internal/object.h
1303713038
proc.$(OBJEXT): $(top_srcdir)/internal/proc.h

proc.c

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "internal/error.h"
1616
#include "internal/eval.h"
1717
#include "internal/gc.h"
18+
#include "internal/hash.h"
1819
#include "internal/object.h"
1920
#include "internal/proc.h"
2021
#include "internal/symbol.h"
@@ -1437,8 +1438,24 @@ rb_hash_proc(st_index_t hash, VALUE prc)
14371438
{
14381439
rb_proc_t *proc;
14391440
GetProcPtr(prc, proc);
1440-
hash = rb_hash_uint(hash, (st_index_t)proc->block.as.captured.code.val);
1441-
hash = rb_hash_uint(hash, (st_index_t)proc->block.as.captured.self);
1441+
1442+
switch (vm_block_type(&proc->block)) {
1443+
case block_type_iseq:
1444+
hash = rb_st_hash_uint(hash, (st_index_t)proc->block.as.captured.code.iseq->body);
1445+
break;
1446+
case block_type_ifunc:
1447+
hash = rb_st_hash_uint(hash, (st_index_t)proc->block.as.captured.code.ifunc->func);
1448+
break;
1449+
case block_type_symbol:
1450+
hash = rb_st_hash_uint(hash, rb_any_hash(proc->block.as.symbol));
1451+
break;
1452+
case block_type_proc:
1453+
hash = rb_st_hash_uint(hash, rb_any_hash(proc->block.as.proc));
1454+
break;
1455+
default:
1456+
rb_bug("rb_hash_proc: unknown block type %d", vm_block_type(&proc->block));
1457+
}
1458+
14421459
return rb_hash_uint(hash, (st_index_t)proc->block.as.captured.ep);
14431460
}
14441461

test/ruby/test_proc.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,24 @@ def self.capture(&block)
168168
assert_operator(procs.map(&:hash).uniq.size, :>=, 500)
169169
end
170170

171+
def test_hash_does_not_change_after_compaction
172+
# [Bug #20853]
173+
[
174+
"proc {}", # iseq backed proc
175+
"{}.to_proc", # ifunc backed proc
176+
":hello.to_proc", # symbol backed proc
177+
].each do |proc|
178+
assert_separately([], <<~RUBY)
179+
p1 = #{proc}
180+
hash = p1.hash
181+
182+
GC.verify_compaction_references(expand_heap: true, toward: :empty)
183+
184+
assert_equal(hash, p1.hash, "proc is `#{proc}`")
185+
RUBY
186+
end
187+
end
188+
171189
def test_block_par
172190
assert_equal(10, Proc.new{|&b| b.call(10)}.call {|x| x})
173191
assert_equal(12, Proc.new{|a,&b| b.call(a)}.call(12) {|x| x})

0 commit comments

Comments
 (0)