summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Zhu <peter@peterzhu.ca>2023-03-17 10:12:37 -0400
committerPeter Zhu <peter@peterzhu.ca>2023-03-18 09:07:05 -0400
commitcb22d78354e201ca74eba68a8b4edefb593e6754 (patch)
treed67779338e1182ff6c5a28432bbaabb3dcde758b
parentdc28ccbb6dd0921a75ed21e9d5a6c6c05a0deecf (diff)
downloadruby-cb22d78354e201ca74eba68a8b4edefb593e6754.tar.gz
Fix frozen status loss when moving objects
[Bug #19536] When objects are moved between size pools, their frozen status is lost in the shape. This will cause the frozen check to be bypassed when there is an inline cache. For example, the following script should raise a FrozenError, but doesn't on Ruby 3.2 and master. class A def add_ivars @a = @b = @c = @d = 1 end def set_a @a = 10 end end a = A.new a.add_ivars a.freeze b = A.new b.add_ivars b.set_a # Set the inline cache in set_a GC.verify_compaction_references(expand_heap: true, toward: :empty) a.set_a
-rw-r--r--shape.c2
-rw-r--r--test/ruby/test_gc_compact.rb28
2 files changed, 29 insertions, 1 deletions
diff --git a/shape.c b/shape.c
index 01ec0f0604..e264e7bee5 100644
--- a/shape.c
+++ b/shape.c
@@ -451,6 +451,7 @@ rb_shape_traverse_from_new_root(rb_shape_t *initial_shape, rb_shape_t *dest_shap
switch ((enum shape_type)dest_shape->type) {
case SHAPE_IVAR:
+ case SHAPE_FROZEN:
if (!next_shape->edges) {
return NULL;
}
@@ -464,7 +465,6 @@ rb_shape_traverse_from_new_root(rb_shape_t *initial_shape, rb_shape_t *dest_shap
}
break;
case SHAPE_ROOT:
- case SHAPE_FROZEN:
case SHAPE_CAPACITY_CHANGE:
case SHAPE_INITIAL_CAPACITY:
case SHAPE_T_OBJECT:
diff --git a/test/ruby/test_gc_compact.rb b/test/ruby/test_gc_compact.rb
index 72d79be037..bef2ba9605 100644
--- a/test/ruby/test_gc_compact.rb
+++ b/test/ruby/test_gc_compact.rb
@@ -416,4 +416,32 @@ class TestGCCompact < Test::Unit::TestCase
assert_include(ObjectSpace.dump(ary[0]), '"embedded":true')
end;
end
+
+ def test_moving_objects_between_size_pools_keeps_shape_frozen_status
+ # [Bug #19536]
+ assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}")
+ begin;
+ class A
+ def add_ivars
+ @a = @b = @c = @d = 1
+ end
+
+ def set_a
+ @a = 10
+ end
+ end
+
+ a = A.new
+ a.add_ivars
+ a.freeze
+
+ b = A.new
+ b.add_ivars
+ b.set_a # Set the inline cache in set_a
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_raise(FrozenError) { a.set_a }
+ end;
+ end
end