test_object.rb   [plain text]


# -*- coding: us-ascii -*-
require 'test/unit'
require_relative 'envutil'

class TestObject < Test::Unit::TestCase
  def setup
    @verbose = $VERBOSE
    $VERBOSE = nil
  end

  def teardown
    $VERBOSE = @verbose
  end

  def test_dup
    assert_raise(TypeError) { 1.dup }
    assert_raise(TypeError) { true.dup }
    assert_raise(TypeError) { nil.dup }

    assert_raise(TypeError) do
      Object.new.instance_eval { initialize_copy(1) }
    end
  end

  def test_init_dupclone
    cls = Class.new do
      def initialize_clone(orig); throw :initialize_clone; end
      def initialize_dup(orig); throw :initialize_dup; end
    end

    obj = cls.new
    assert_throws(:initialize_clone) {obj.clone}
    assert_throws(:initialize_dup) {obj.dup}
  end

  def test_instance_of
    assert_raise(TypeError) { 1.instance_of?(1) }
  end

  def test_kind_of
    assert_raise(TypeError) { 1.kind_of?(1) }
  end

  def test_taint_frozen_obj
    o = Object.new
    o.freeze
    assert_raise(RuntimeError) { o.taint }

    o = Object.new
    o.taint
    o.freeze
    assert_raise(RuntimeError) { o.untaint }
  end

  def test_freeze_under_safe_4
    o = Object.new
    assert_raise(SecurityError) do
      Thread.new do
        $SAFE = 4
        o.freeze
      end.join
    end
  end

  def test_freeze_immediate
    assert_equal(true, 1.frozen?)
    1.freeze
    assert_equal(true, 1.frozen?)
    assert_equal(true, 2.frozen?)
  end

  def test_nil_to_f
    assert_equal(0.0, nil.to_f)
  end

  def test_not
    assert_equal(false, Object.new.send(:!))
    assert_equal(true, nil.send(:!))
  end

  def test_true_and
    assert_equal(true, true & true)
    assert_equal(true, true & 1)
    assert_equal(false, true & false)
    assert_equal(false, true & nil)
  end

  def test_true_or
    assert_equal(true, true | true)
    assert_equal(true, true | 1)
    assert_equal(true, true | false)
    assert_equal(true, true | nil)
  end

  def test_true_xor
    assert_equal(false, true ^ true)
    assert_equal(false, true ^ 1)
    assert_equal(true, true ^ false)
    assert_equal(true, true ^ nil)
  end

  def test_false_and
    assert_equal(false, false & true)
    assert_equal(false, false & 1)
    assert_equal(false, false & false)
    assert_equal(false, false & nil)
  end

  def test_false_or
    assert_equal(true, false | true)
    assert_equal(true, false | 1)
    assert_equal(false, false | false)
    assert_equal(false, false | nil)
  end

  def test_false_xor
    assert_equal(true, false ^ true)
    assert_equal(true, false ^ 1)
    assert_equal(false, false ^ false)
    assert_equal(false, false ^ nil)
  end

  def test_methods
    o = Object.new
    a1 = o.methods
    a2 = o.methods(false)

    def o.foo; end

    assert_equal([:foo], o.methods(true) - a1)
    assert_equal([:foo], o.methods(false) - a2)
  end

  def test_methods2
    c0 = Class.new
    c1 = Class.new(c0)
    c1.module_eval do
      public   ; def foo; end
      protected; def bar; end
      private  ; def baz; end
    end
    c2 = Class.new(c1)
    c2.module_eval do
      public   ; def foo2; end
      protected; def bar2; end
      private  ; def baz2; end
    end

    o0 = c0.new
    o2 = c2.new

    assert_equal([:baz, :baz2], (o2.private_methods - o0.private_methods).sort)
    assert_equal([:baz2], (o2.private_methods(false) - o0.private_methods(false)).sort)

    assert_equal([:bar, :bar2], (o2.protected_methods - o0.protected_methods).sort)
    assert_equal([:bar2], (o2.protected_methods(false) - o0.protected_methods(false)).sort)

    assert_equal([:foo, :foo2], (o2.public_methods - o0.public_methods).sort)
    assert_equal([:foo2], (o2.public_methods(false) - o0.public_methods(false)).sort)
  end

  def test_instance_variable_get
    o = Object.new
    o.instance_eval { @foo = :foo }
    assert_equal(:foo, o.instance_variable_get(:@foo))
    assert_equal(nil, o.instance_variable_get(:@bar))
    assert_raise(NameError) { o.instance_variable_get('@') }
    assert_raise(NameError) { o.instance_variable_get(:'@') }
    assert_raise(NameError) { o.instance_variable_get(:foo) }
  end

  def test_instance_variable_set
    o = Object.new
    o.instance_variable_set(:@foo, :foo)
    assert_equal(:foo, o.instance_eval { @foo })
    assert_raise(NameError) { o.instance_variable_set(:'@', 1) }
    assert_raise(NameError) { o.instance_variable_set('@', 1) }
    assert_raise(NameError) { o.instance_variable_set(:foo, 1) }
  end

  def test_instance_variable_defined
    o = Object.new
    o.instance_eval { @foo = :foo }
    assert_equal(true, o.instance_variable_defined?(:@foo))
    assert_equal(false, o.instance_variable_defined?(:@bar))
    assert_raise(NameError) { o.instance_variable_defined?(:'@') }
    assert_raise(NameError) { o.instance_variable_defined?('@') }
    assert_raise(NameError) { o.instance_variable_defined?(:foo) }
  end

  def test_remove_instance_variable
    o = Object.new
    o.instance_eval { @foo = :foo }
    o.remove_instance_variable(:@foo)
    assert_equal(false, o.instance_variable_defined?(:@foo))
  end

  def test_convert_type
    o = Object.new
    def o.to_s; 1; end
    assert_raise(TypeError) { String(o) }
    def o.to_s; "o"; end
    assert_equal("o", String(o))
    def o.respond_to?(*) false; end
    assert_raise(TypeError) { String(o) }
  end

  def test_check_convert_type
    o = Object.new
    def o.to_a; 1; end
    assert_raise(TypeError) { Array(o) }
    def o.to_a; [1]; end
    assert_equal([1], Array(o))
    def o.respond_to?(*) false; end
    assert_equal([o], Array(o))
  end

  def test_convert_hash
    assert_equal({}, Hash(nil))
    assert_equal({}, Hash([]))
    assert_equal({key: :value}, Hash(key: :value))
    assert_raise(TypeError) { Hash([1,2]) }
    assert_raise(TypeError) { Hash(Object.new) }
    o = Object.new
    def o.to_hash; {a: 1, b: 2}; end
    assert_equal({a: 1, b: 2}, Hash(o))
    def o.to_hash; 9; end
    assert_raise(TypeError) { Hash(o) }
  end

  def test_to_integer
    o = Object.new
    def o.to_i; nil; end
    assert_raise(TypeError) { Integer(o) }
    def o.to_i; 42; end
    assert_equal(42, Integer(o))
    def o.respond_to?(*) false; end
    assert_raise(TypeError) { Integer(o) }
  end

  class MyInteger
    def initialize(n); @num = n; end
    def to_int; @num; end
    def <=>(n); @num <=> n.to_int; end
    def <=(n); @num <= n.to_int; end
    def +(n); MyInteger.new(@num + n.to_int); end
  end

  def test_check_to_integer
    o1 = MyInteger.new(1)
    o9 = MyInteger.new(9)
    n = 0
    Range.new(o1, o9).step(2) {|x| n += x.to_int }
    assert_equal(1+3+5+7+9, n)
  end

  def test_add_method_under_safe4
    o = Object.new
    assert_raise(SecurityError) do
      Thread.new do
        $SAFE = 4
        def o.foo
        end
      end.join
    end
  end

  def test_redefine_method_under_verbose
    assert_in_out_err([], <<-INPUT, %w(2), /warning: method redefined; discarding old foo$/)
      $VERBOSE = true
      o = Object.new
      def o.foo; 1; end
      def o.foo; 2; end
      p o.foo
    INPUT
  end

  def test_redefine_method_which_may_case_serious_problem
    assert_in_out_err([], <<-INPUT, [], /warning: redefining `object_id' may cause serious problems$/)
      $VERBOSE = false
      def (Object.new).object_id; end
    INPUT

    assert_in_out_err([], <<-INPUT, [], /warning: redefining `__send__' may cause serious problems$/)
      $VERBOSE = false
      def (Object.new).__send__; end
    INPUT

    bug10421 = '[ruby-dev:48691] [Bug #10421]'
    assert_in_out_err([], <<-INPUT, ["1"], [], bug10421)
      $VERBOSE = false
      class C < BasicObject
        def object_id; 1; end
      end
      puts C.new.object_id
    INPUT
  end

  def test_remove_method
    assert_raise(SecurityError) do
      Thread.new do
        $SAFE = 4
        Object.instance_eval { remove_method(:foo) }
      end.join
    end

    assert_raise(SecurityError) do
      Thread.new do
        $SAFE = 4
        Class.instance_eval { remove_method(:foo) }
      end.join
    end

    c = Class.new
    c.freeze
    assert_raise(RuntimeError) do
      c.instance_eval { remove_method(:foo) }
    end

    c = Class.new do
      def meth1; "meth" end
    end
    d = Class.new(c) do
      alias meth2 meth1
    end
    o1 = c.new
    assert_respond_to(o1, :meth1)
    assert_equal("meth", o1.meth1)
    o2 = d.new
    assert_respond_to(o2, :meth1)
    assert_equal("meth", o2.meth1)
    assert_respond_to(o2, :meth2)
    assert_equal("meth", o2.meth2)
    d.class_eval do
      remove_method :meth2
    end
    bug2202 = '[ruby-core:26074]'
    assert_raise(NoMethodError, bug2202) {o2.meth2}

    %w(object_id __send__ initialize).each do |m|
      assert_in_out_err([], <<-INPUT, %w(:ok), /warning: removing `#{m}' may cause serious problems$/)
        $VERBOSE = false
        begin
          Class.new.instance_eval { remove_method(:#{m}) }
        rescue NameError
          p :ok
        end
      INPUT
    end
  end

  def test_method_missing
    assert_raise(ArgumentError) do
      1.instance_eval { method_missing }
    end

    c = Class.new
    c.class_eval do
      protected
      def foo; end
    end
    assert_raise(NoMethodError) do
      c.new.foo
    end

    assert_raise(NoMethodError) do
      1.instance_eval { method_missing(:method_missing) }
    end

    c.class_eval do
      undef_method(:method_missing)
    end

    assert_raise(ArgumentError) do
      c.new.method_missing
    end

    bug2494 = '[ruby-core:27219]'
    c = Class.new do
      def method_missing(meth, *args)
        super
      end
    end
    b = c.new
    foo rescue nil
    assert_nothing_raised(bug2494) {[b].flatten}
  end

  def test_respond_to_missing_string
    c = Class.new do
      def respond_to_missing?(id, priv)
        !(id !~ /\Agadzoks\d+\z/) ^ priv
      end
    end
    foo = c.new
    assert_equal(false, foo.respond_to?("gadzooks16"))
    assert_equal(true, foo.respond_to?("gadzooks17", true))
    assert_equal(true, foo.respond_to?("gadzoks16"))
    assert_equal(false, foo.respond_to?("gadzoks17", true))
  end

  def test_respond_to_missing
    c = Class.new do
      def respond_to_missing?(id, priv)
        if id == :foobar
          true
        else
          false
        end
      end
      def method_missing(id, *args)
        if id == :foobar
          return [:foo, *args]
        else
          super
        end
      end
    end

    foo = c.new
    assert_equal([:foo], foo.foobar);
    assert_equal([:foo, 1], foo.foobar(1));
    assert_equal([:foo, 1, 2, 3, 4, 5], foo.foobar(1, 2, 3, 4, 5));
    assert(foo.respond_to?(:foobar))
    assert_equal(false, foo.respond_to?(:foobarbaz))
    assert_raise(NoMethodError) do
      foo.foobarbaz
    end

    foobar = foo.method(:foobar)
    assert_equal(-1, foobar.arity);
    assert_equal([:foo], foobar.call);
    assert_equal([:foo, 1], foobar.call(1));
    assert_equal([:foo, 1, 2, 3, 4, 5], foobar.call(1, 2, 3, 4, 5));
    assert_equal(foobar, foo.method(:foobar))
    assert_not_equal(foobar, c.new.method(:foobar))

    c = Class.new(c)
    assert_equal(false, c.method_defined?(:foobar))
    assert_raise(NameError, '[ruby-core:25748]') do
      c.instance_method(:foobar)
    end

    m = Module.new
    assert_equal(false, m.method_defined?(:foobar))
    assert_raise(NameError, '[ruby-core:25748]') do
      m.instance_method(:foobar)
    end
  end

  def test_implicit_respond_to
    bug5158 = '[ruby-core:38799]'

    p = Object.new

    called = []
    p.singleton_class.class_eval do
      define_method(:to_ary) do
        called << [:to_ary, bug5158]
      end
    end
    [[p]].flatten
    assert_equal([[:to_ary, bug5158]], called, bug5158)

    called = []
    p.singleton_class.class_eval do
      define_method(:respond_to?) do |*a|
        called << [:respond_to?, *a]
        false
      end
    end
    [[p]].flatten
    assert_equal([[:respond_to?, :to_ary, true]], called, bug5158)
  end

  def test_implicit_respond_to_arity_1
    p = Object.new

    called = []
    p.singleton_class.class_eval do
      define_method(:respond_to?) do |a|
        called << [:respond_to?, a]
        false
      end
    end
    [[p]].flatten
    assert_equal([[:respond_to?, :to_ary]], called, '[bug:6000]')
  end

  def test_implicit_respond_to_arity_3
    p = Object.new

    called = []
    p.singleton_class.class_eval do
      define_method(:respond_to?) do |a, b, c|
        called << [:respond_to?, a, b, c]
        false
      end
    end

    e = assert_raise(ArgumentError, '[bug:6000]') do
      [[p]].flatten
    end

    assert_equal('respond_to? must accept 1 or 2 arguments (requires 3)', e.message)
  end

  def test_method_missing_passed_block
    bug5731 = '[ruby-dev:44961]'

    c = Class.new do
      def method_missing(meth, *args) yield(meth, *args) end
    end
    a = c.new
    result = nil
    assert_nothing_raised(LocalJumpError, bug5731) do
      a.foo {|x| result = x}
    end
    assert_equal(:foo, result, bug5731)
    result = nil
    e = a.enum_for(:foo)
    assert_nothing_raised(LocalJumpError, bug5731) do
      e.each {|x| result = x}
    end
    assert_equal(:foo, result, bug5731)

    c = Class.new do
      def respond_to_missing?(id, priv)
        true
      end
      def method_missing(id, *args, &block)
        return block.call(:foo, *args)
      end
    end
    foo = c.new

    result = nil
    assert_nothing_raised(LocalJumpError, bug5731) do
      foo.foobar {|x| result = x}
    end
    assert_equal(:foo, result, bug5731)
    result = nil
    assert_nothing_raised(LocalJumpError, bug5731) do
      foo.enum_for(:foobar).each {|x| result = x}
    end
    assert_equal(:foo, result, bug5731)

    result = nil
    foobar = foo.method(:foobar)
    foobar.call {|x| result = x}
    assert_equal(:foo, result, bug5731)

    result = nil
    foobar = foo.method(:foobar)
    foobar.enum_for(:call).each {|x| result = x}
    assert_equal(:foo, result, bug5731)
  end

  def test_send_with_no_arguments
    assert_raise(ArgumentError) { 1.send }
  end

  def test_send_with_block
    x = :ng
    1.send(:times) { x = :ok }
    assert_equal(:ok, x)

    x = :ok
    o = Object.new
    def o.inspect
      yield if block_given?
      super
    end
    begin
      nil.public_send(o) { x = :ng }
    rescue
    end
    assert_equal(:ok, x)
  end

  def test_public_send
    c = Class.new do
      def pub
        :ok
      end

      def invoke(m)
        public_send(m)
      end

      protected
      def prot
        :ng
      end

      private
      def priv
        :ng
      end
    end.new
    assert_equal(:ok, c.public_send(:pub))
    assert_raise(NoMethodError) {c.public_send(:priv)}
    assert_raise(NoMethodError) {c.public_send(:prot)}
    assert_raise(NoMethodError) {c.invoke(:priv)}
    bug7499 = '[ruby-core:50489]'
    assert_raise(NoMethodError, bug7499) {c.invoke(:prot)}
  end

  def test_no_superclass_method
    bug2312 = '[ruby-dev:39581]'

    o = Object.new
    e = assert_raise(NoMethodError) {
      o.method(:__send__).call(:never_defined_test_no_superclass_method)
    }
    m1 = e.message
    assert_no_match(/no superclass method/, m1, bug2312)
    e = assert_raise(NoMethodError) {
      o.method(:__send__).call(:never_defined_test_no_superclass_method)
    }
    assert_equal(m1, e.message, bug2312)
    e = assert_raise(NoMethodError) {
      o.never_defined_test_no_superclass_method
    }
    assert_equal(m1, e.message, bug2312)
  end

  def test_superclass_method
    bug2312 = '[ruby-dev:39581]'
    assert_in_out_err(["-e", "module Enumerable;undef min;end; (1..2).min{}"],
                      "", [], /no superclass method/, bug2312)
  end

  def test_specific_eval_with_wrong_arguments
    assert_raise(ArgumentError) do
      1.instance_eval("foo") { foo }
    end

    assert_raise(ArgumentError) do
      1.instance_eval
    end

    assert_raise(ArgumentError) do
      1.instance_eval("", 1, 1, 1)
    end
  end

  class InstanceExec
    INSTANCE_EXEC = 123
  end

  def test_instance_exec
    x = 1.instance_exec(42) {|a| self + a }
    assert_equal(43, x)

    x = "foo".instance_exec("bar") {|a| self + a }
    assert_equal("foobar", x)

    assert_raise(NameError) do
      InstanceExec.new.instance_exec { INSTANCE_EXEC }
    end
  end

  def test_extend
    assert_raise(ArgumentError) do
      1.extend
    end
  end

  def test_untrusted
    obj = lambda {
      $SAFE = 4
      x = Object.new
      x.instance_eval { @foo = 1 }
      x
    }.call
    assert_equal(true, obj.untrusted?)
    assert_equal(true, obj.tainted?)

    x = Object.new
    assert_equal(false, x.untrusted?)
    assert_raise(SecurityError) do
      lambda {
        $SAFE = 4
        x.instance_eval { @foo = 1 }
      }.call
    end

    x = Object.new
    x.taint
    assert_raise(SecurityError) do
      lambda {
        $SAFE = 4
        x.instance_eval { @foo = 1 }
      }.call
    end

    x.untrust
    assert_equal(true, x.untrusted?)
    assert_nothing_raised do
      lambda {
        $SAFE = 4
        x.instance_eval { @foo = 1 }
      }.call
    end

    x.trust
    assert_equal(false, x.untrusted?)
    assert_raise(SecurityError) do
      lambda {
        $SAFE = 4
        x.instance_eval { @foo = 1 }
      }.call
    end

    a = Object.new
    a.untrust
    assert_equal(true, a.untrusted?)
    b = a.dup
    assert_equal(true, b.untrusted?)
    c = a.clone
    assert_equal(true, c.untrusted?)

    a = Object.new
    b = lambda {
      $SAFE = 4
      a.dup
    }.call
    assert_equal(true, b.untrusted?)

    a = Object.new
    b = lambda {
      $SAFE = 4
      a.clone
    }.call
    assert_equal(true, b.untrusted?)
  end

  def test_to_s
    x = Object.new
    x.taint
    x.untrust
    s = x.to_s
    assert_equal(true, s.untrusted?)
    assert_equal(true, s.tainted?)

    x = eval(<<-EOS)
      class ToS\u{3042}
        new.to_s
      end
    EOS
    assert_match(/\bToS\u{3042}:/, x)
  end

  def test_inspect
    x = Object.new
    assert_match(/\A#<Object:0x\h+>\z/, x.inspect)

    x.instance_variable_set(:@ivar, :value)
    assert_match(/\A#<Object:0x\h+ @ivar=:value>\z/, x.inspect)

    x = Object.new
    x.instance_variable_set(:@recur, x)
    assert_match(/\A#<Object:0x\h+ @recur=#<Object:0x\h+ \.\.\.>>\z/, x.inspect)

    x = Object.new
    x.instance_variable_set(:@foo, "value")
    x.instance_variable_set(:@bar, 42)
    assert_match(/\A#<Object:0x\h+ (?:@foo="value", @bar=42|@bar=42, @foo="value")>\z/, x.inspect)

    # #inspect does not call #to_s anymore
    feature6130 = '[ruby-core:43238]'
    x = Object.new
    def x.to_s
      "to_s"
    end
    assert_match(/\A#<Object:0x\h+>\z/, x.inspect, feature6130)

    x = eval(<<-EOS)
      class Inspect\u{3042}
        new.inspect
      end
    EOS
    assert_match(/\bInspect\u{3042}:/, x)

    x = eval(<<-EOS)
      class Inspect\u{3042}
        def initialize
          @\u{3044} = 42
        end
        new.inspect
      end
    EOS
    assert_match(/\bInspect\u{3042}:.* @\u{3044}=42\b/, x)
  end

  def test_exec_recursive
    Thread.current[:__recursive_key__] = nil
    a = [[]]
    a.inspect

    assert_nothing_raised do
      -> do
        $SAFE = 4
        begin
          a.hash
        rescue ArgumentError
        end
      end.call
    end

    -> do
      assert_nothing_raised do
        $SAFE = 4
        a.inspect
      end
    end.call

    -> do
      o = Object.new
      def o.to_ary(x); end
      def o.==(x); $SAFE = 4; false; end
      a = [[o]]
      b = []
      b << b

      assert_nothing_raised do
        b == a
      end
    end.call
  end

  def test_singleton_class
    x = Object.new
    xs = class << x; self; end
    assert_equal(xs, x.singleton_class)

    y = Object.new
    ys = y.singleton_class
    assert_equal(class << y; self; end, ys)

    assert_equal(NilClass, nil.singleton_class)
    assert_equal(TrueClass, true.singleton_class)
    assert_equal(FalseClass, false.singleton_class)

    assert_raise(TypeError) do
      123.singleton_class
    end
    assert_raise(TypeError) do
      :foo.singleton_class
    end
  end

  def test_redef_method_missing
    bug5473 = '[ruby-core:40287]'
    ['ArgumentError.new("bug5473")', 'ArgumentError, "bug5473"', '"bug5473"'].each do |code|
      out, err, status = EnvUtil.invoke_ruby([], <<-SRC, true, true)
      class ::Object
        def method_missing(m, *a, &b)
          raise #{code}
        end
      end

      p((1.foo rescue $!))
      SRC
      assert_send([status, :success?], bug5473)
      assert_equal("", err, bug5473)
      assert_equal((eval("raise #{code}") rescue $!.inspect), out.chomp, bug5473)
    end
  end

  def assert_not_initialize_copy
    a = yield
    b = yield
    assert_nothing_raised("copy") {a.instance_eval {initialize_copy(b)}}
    c = a.dup.freeze
    assert_raise(RuntimeError, "frozen") {c.instance_eval {initialize_copy(b)}}
    d = a.dup.trust
    assert_raise(SecurityError, "untrust") do
      proc {
        $SAFE = 4
        d.instance_eval {initialize_copy(b)}
      }.call
    end
    [a, b, c, d]
  end

  def test_bad_initialize_copy
    assert_not_initialize_copy {Object.new}
    assert_not_initialize_copy {[].to_enum}
    assert_not_initialize_copy {Enumerator::Generator.new {}}
    assert_not_initialize_copy {Enumerator::Yielder.new {}}
    assert_not_initialize_copy {File.stat(__FILE__)}
    assert_not_initialize_copy {open(__FILE__)}.each(&:close)
    assert_not_initialize_copy {ARGF.class.new}
    assert_not_initialize_copy {Random.new}
    assert_not_initialize_copy {//}
    assert_not_initialize_copy {/.*/.match("foo")}
    st = Struct.new(:foo)
    assert_not_initialize_copy {st.new}
  end

  def test_type_error_message
    issue = "Bug #7539"
    err = assert_raise(TypeError){ Integer([42]) }
    assert_equal "can't convert Array into Integer", err.message, issue
    err = assert_raise(TypeError){ [].first([42]) }
    assert_equal 'no implicit conversion of Array into Integer', err.message, issue
  end

  def test_copied_ivar_memory_leak
    bug10191 = '[ruby-core:64700] [Bug #10191]'
    assert_no_memory_leak([], <<-"end;", <<-"end;", bug10191, rss: true, timeout: 60)
      def (a = Object.new).set; @v = nil; end
      num = 500_000
    end;
      num.times {a.clone.set}
    end;
  end
end