summaryrefslogtreecommitdiff
path: root/test/ruby/test_object.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/ruby/test_object.rb')
-rw-r--r--test/ruby/test_object.rb204
1 files changed, 163 insertions, 41 deletions
diff --git a/test/ruby/test_object.rb b/test/ruby/test_object.rb
index 7d00422629..53ae4fb110 100644
--- a/test/ruby/test_object.rb
+++ b/test/ruby/test_object.rb
@@ -280,6 +280,12 @@ class TestObject < Test::Unit::TestCase
assert_equal([:foo], k.private_methods(false))
end
+ class ToStrCounter
+ def initialize(str = "@foo") @str = str; @count = 0; end
+ def to_str; @count += 1; @str; end
+ def count; @count; end
+ end
+
def test_instance_variable_get
o = Object.new
o.instance_eval { @foo = :foo }
@@ -291,9 +297,7 @@ class TestObject < Test::Unit::TestCase
assert_raise(NameError) { o.instance_variable_get("bar") }
assert_raise(TypeError) { o.instance_variable_get(1) }
- n = Object.new
- def n.to_str; @count = defined?(@count) ? @count + 1 : 1; "@foo"; end
- def n.count; @count; end
+ n = ToStrCounter.new
assert_equal(:foo, o.instance_variable_get(n))
assert_equal(1, n.count)
end
@@ -308,9 +312,7 @@ class TestObject < Test::Unit::TestCase
assert_raise(NameError) { o.instance_variable_set("bar", 1) }
assert_raise(TypeError) { o.instance_variable_set(1, 1) }
- n = Object.new
- def n.to_str; @count = defined?(@count) ? @count + 1 : 1; "@foo"; end
- def n.count; @count; end
+ n = ToStrCounter.new
o.instance_variable_set(n, :bar)
assert_equal(:bar, o.instance_eval { @foo })
assert_equal(1, n.count)
@@ -327,9 +329,7 @@ class TestObject < Test::Unit::TestCase
assert_raise(NameError) { o.instance_variable_defined?("bar") }
assert_raise(TypeError) { o.instance_variable_defined?(1) }
- n = Object.new
- def n.to_str; @count = defined?(@count) ? @count + 1 : 1; "@foo"; end
- def n.count; @count; end
+ n = ToStrCounter.new
assert_equal(true, o.instance_variable_defined?(n))
assert_equal(1, n.count)
end
@@ -356,38 +356,43 @@ class TestObject < Test::Unit::TestCase
end
def test_remove_instance_variable_re_embed
- require "objspace"
-
- c = Class.new do
- def a = @a
-
- def b = @b
-
- def c = @c
- end
-
- o1 = c.new
- o2 = c.new
-
- o1.instance_variable_set(:@foo, 5)
- o1.instance_variable_set(:@a, 0)
- o1.instance_variable_set(:@b, 1)
- o1.instance_variable_set(:@c, 2)
- refute_includes ObjectSpace.dump(o1), '"embedded":true'
- o1.remove_instance_variable(:@foo)
- assert_includes ObjectSpace.dump(o1), '"embedded":true'
-
- o2.instance_variable_set(:@a, 0)
- o2.instance_variable_set(:@b, 1)
- o2.instance_variable_set(:@c, 2)
- assert_includes ObjectSpace.dump(o2), '"embedded":true'
-
- assert_equal(0, o1.a)
- assert_equal(1, o1.b)
- assert_equal(2, o1.c)
- assert_equal(0, o2.a)
- assert_equal(1, o2.b)
- assert_equal(2, o2.c)
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # Determine the RVALUE pool's embed capacity from GC constants.
+ rvalue_size = GC::INTERNAL_CONSTANTS[:RVALUE_SIZE]
+ rbasic_size = GC::INTERNAL_CONSTANTS[:RBASIC_SIZE]
+ embed_cap = (rvalue_size - rbasic_size) / RbConfig::SIZEOF["void*"]
+
+ # Build a class whose initialize sets embed_cap ivars so objects
+ # are allocated in the RVALUE pool with embedded storage.
+ init_body = embed_cap.times.map { |i| "@v#{i} = nil" }.join("; ")
+ c = Class.new { class_eval("def initialize; #{init_body}; end") }
+
+ o1 = c.new
+ o2 = c.new
+
+ # All embed_cap ivars fit - should be embedded
+ embed_cap.times { |i| o1.instance_variable_set(:"@v#{i}", i) }
+ assert_includes ObjectSpace.dump(o1), '"embedded":true'
+
+ # One more ivar overflows embed capacity
+ o1.instance_variable_set(:@overflow, 99)
+ refute_includes ObjectSpace.dump(o1), '"embedded":true'
+
+ # Remove the overflow ivar - should re-embed
+ o1.remove_instance_variable(:@overflow)
+ assert_includes ObjectSpace.dump(o1), '"embedded":true'
+
+ # An object that never overflowed is also embedded
+ embed_cap.times { |i| o2.instance_variable_set(:"@v#{i}", i) }
+ assert_includes ObjectSpace.dump(o2), '"embedded":true'
+
+ # Verify values survived re-embedding
+ embed_cap.times do |i|
+ assert_equal(i, o1.instance_variable_get(:"@v#{i}"))
+ assert_equal(i, o2.instance_variable_get(:"@v#{i}"))
+ end
+ end;
end
def test_convert_string
@@ -950,6 +955,82 @@ class TestObject < Test::Unit::TestCase
assert_match(/\bInspect\u{3042}:.* @\u{3044}=42\b/, x.inspect)
x.instance_variable_set("@\u{3046}".encode(Encoding::EUC_JP), 6)
assert_match(/@\u{3046}=6\b/, x.inspect)
+
+ x = Object.new
+ x.singleton_class.class_eval do
+ private def instance_variables_to_inspect = [:@host, :@user]
+ end
+
+ x.instance_variable_set(:@host, "localhost")
+ x.instance_variable_set(:@user, "root")
+ x.instance_variable_set(:@password, "hunter2")
+ s = x.inspect
+ assert_include(s, "@host=\"localhost\"")
+ assert_include(s, "@user=\"root\"")
+ assert_not_include(s, "@password=")
+ end
+
+ def test_inspect_mutating_ivar
+ obj = Object.new
+ evil = Object.new
+ evil.define_singleton_method(:inspect) do
+ obj.instance_variables.each { |v| obj.remove_instance_variable(v) }
+ "evil"
+ end
+ obj.instance_variable_set(:@evil, evil)
+ 10.times { |i| obj.instance_variable_set(:"@v#{i}", 0) }
+ # Buffered iteration: inspect sees a snapshot of the original ivars
+ result = obj.inspect
+ assert_include result, "@evil=evil"
+ 10.times { |i| assert_include result, "@v#{i}=0" }
+ end
+
+ def test_inspect_mutating_ivar_complex
+ # Force complex by creating many shape variations on the same class
+ c = Class.new
+ 50.times do |i|
+ o = c.new
+ o.instance_variable_set(:"@unique_#{i}", 0)
+ end
+
+ obj = c.new
+ evil = Object.new
+ evil.define_singleton_method(:inspect) do
+ obj.instance_variables.each { |v| obj.remove_instance_variable(v) }
+ ""
+ end
+ obj.instance_variable_set(:@evil, evil)
+ 10.times { |i| obj.instance_variable_set(:"@v#{i}", 0) }
+ # complex objects use st_foreach which handles mutation gracefully
+ obj.inspect
+ end
+
+ def test_inspect_complex
+ kernel_inspect = Kernel.instance_method(:inspect)
+
+ klasses = [
+ Class.new,
+ Class.new(String),
+ Class.new(Array),
+ Class.new(Hash),
+ Struct.new(:x),
+ Class.new(Thread::Mutex),
+ # It's very difficult to get a complex T_CLASS, so that isn't tested here
+ ]
+
+ klasses.each_with_index do |klass, idx|
+ 8.times do |i|
+ klass.new.instance_variable_set(:"@sib_#{rand(999999)}", 1)
+ end
+
+ obj = klass.new
+ obj.instance_variable_set(:@a, 1)
+ obj.instance_variable_set(:@b, 2)
+
+ s = kernel_inspect.bind_call(obj)
+ assert_include(s, "@a=1")
+ assert_include(s, "@b=2")
+ end
end
def test_singleton_methods
@@ -1009,6 +1090,47 @@ class TestObject < Test::Unit::TestCase
assert_predicate(ys, :frozen?, '[Bug #19169]')
end
+ def test_singleton_class_of_singleton_class_freeze
+ x = Object.new
+ xs = x.singleton_class
+ xxs = xs.singleton_class
+ xxxs = xxs.singleton_class
+ x.freeze
+ assert_predicate(xs, :frozen?, '[Bug #20319]')
+ assert_predicate(xxs, :frozen?, '[Bug #20319]')
+ assert_predicate(xxxs, :frozen?, '[Bug #20319]')
+
+ y = Object.new
+ ys = y.singleton_class
+ ys.prepend(Module.new)
+ yys = ys.singleton_class
+ yys.prepend(Module.new)
+ yyys = yys.singleton_class
+ yyys.prepend(Module.new)
+ y.freeze
+ assert_predicate(ys, :frozen?, '[Bug #20319]')
+ assert_predicate(yys, :frozen?, '[Bug #20319]')
+ assert_predicate(yyys, :frozen?, '[Bug #20319]')
+
+ c = Class.new
+ cs = c.singleton_class
+ ccs = cs.singleton_class
+ cccs = ccs.singleton_class
+ d = Class.new(c)
+ ds = d.singleton_class
+ dds = ds.singleton_class
+ ddds = dds.singleton_class
+ d.freeze
+ assert_predicate(d, :frozen?, '[Bug #20319]')
+ assert_predicate(ds, :frozen?, '[Bug #20319]')
+ assert_predicate(dds, :frozen?, '[Bug #20319]')
+ assert_predicate(ddds, :frozen?, '[Bug #20319]')
+ assert_not_predicate(c, :frozen?, '[Bug #20319]')
+ assert_not_predicate(cs, :frozen?, '[Bug #20319]')
+ assert_not_predicate(ccs, :frozen?, '[Bug #20319]')
+ assert_not_predicate(cccs, :frozen?, '[Bug #20319]')
+ end
+
def test_redef_method_missing
bug5473 = '[ruby-core:40287]'
['ArgumentError.new("bug5473")', 'ArgumentError, "bug5473"', '"bug5473"'].each do |code|