From 04eb7c7e462d1fcbab9efc7022c1b43636ab878a Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Sun, 25 Aug 2019 21:11:46 -0700 Subject: Call initialize_clone with freeze: false if clone called with freeze: false This makes it possible to initialize_clone to correctly not freeze internal state if the freeze: false keyword is passed to clone. If clone is called with freeze: true or no keyword, do not pass a second argument to initialize_clone to keep backwards compatibility. This makes it so that external libraries that override initialize_clone but do not support the freeze keyword will fail with ArgumentError if passing freeze: false to clone. I think that is better than the current behavior, which succeeds but results in an unfrozen object with frozen internals. Fix related issues in set and delegate in stdlib. Fixes [Bug #14266] --- test/ruby/test_module.rb | 6 ++++++ test/ruby/test_object.rb | 22 ++++++++++++++++++++++ test/test_delegate.rb | 15 +++++++++++++++ test/test_set.rb | 11 +++++++++++ 4 files changed, 54 insertions(+) (limited to 'test') diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index 2e7e5804d0..9373c48a9a 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -2491,6 +2491,12 @@ class TestModule < Test::Unit::TestCase assert_equal :M2, CloneTestC2.new.foo, '[Bug #15877]' end + def test_clone_freeze + m = Module.new.freeze + assert_predicate m.clone, :frozen? + assert_not_predicate m.clone(freeze: false), :frozen? + end + private def assert_top_method_is_private(method) diff --git a/test/ruby/test_object.rb b/test/ruby/test_object.rb index add5b9fb15..bfc7d7de1d 100644 --- a/test/ruby/test_object.rb +++ b/test/ruby/test_object.rb @@ -75,6 +75,28 @@ class TestObject < Test::Unit::TestCase assert_raise_with_message(ArgumentError, /\u{1f4a9}/) do Object.new.clone(freeze: x) end + + c = Class.new do + attr_reader :f + end + o = c.new + f = true + def o.initialize_clone(_, freeze: true) + @f = freeze + super + end + clone = o.clone + assert_kind_of c, clone + assert_equal true, clone.f + clone = o.clone(freeze: false) + assert_kind_of c, clone + assert_equal false, clone.f + + def o.initialize_clone(_) + super + end + assert_kind_of c, o.clone + assert_raise(ArgumentError) { o.clone(freeze: false) } end def test_init_dupclone diff --git a/test/test_delegate.rb b/test/test_delegate.rb index 303cd16897..80bf41638a 100644 --- a/test/test_delegate.rb +++ b/test/test_delegate.rb @@ -50,6 +50,21 @@ class TestDelegateClass < Test::Unit::TestCase assert_equal(SimpleDelegator,simple.clone.class) end + def test_simpledelegator_clone + simple=SimpleDelegator.new([]) + simple.freeze + + clone = simple.clone + assert_predicate clone, :frozen? + assert_predicate clone.__getobj__, :frozen? + assert_equal true, Kernel.instance_method(:frozen?).bind(clone).call + + clone = simple.clone(freeze: false) + assert_not_predicate clone, :frozen? + assert_not_predicate clone.__getobj__, :frozen? + assert_equal false, Kernel.instance_method(:frozen?).bind(clone).call + end + class Object def m :o diff --git a/test/test_set.rb b/test/test_set.rb index b0f669ce86..68ee7ce8a3 100644 --- a/test/test_set.rb +++ b/test/test_set.rb @@ -730,6 +730,17 @@ class TC_Set < Test::Unit::TestCase } end + def test_freeze_clone_false + set1 = Set[1,2,3] + set1.freeze + set2 = set1.clone(freeze: false) + + assert_not_predicate set2, :frozen? + set2.add 5 + assert_equal Set[1,2,3,5], set2 + assert_equal Set[1,2,3], set1 + end + def test_inspect set1 = Set[1, 2] assert_equal('#', set1.inspect) -- cgit v1.2.3