Project

General

Profile

Bug #9603

unusual reference class-variable with cloned class.

Added by Chiether (Norikaz Ishii) over 5 years ago. Updated 10 days ago.

Status:
Rejected
Priority:
Normal
Assignee:
-
Target version:
-
ruby -v:
ruby 2.1.2p80 (2014-03-01 revision 45231) [x86_64-linux]
Backport:
[ruby-core:61336]

Description

description

Maybe panic reference to class-variable in cloned class. Not really sure about bug. But hang over my head.
I think minor irritant it which whether problem or not. because impractical code.

sample code

class A
  @@value = 1
  def self.make_instance
    @@value = 2
    new
  end
  def data
    @@value
  end
end
puts A.make_instance.data # => 2 -- collect!!

class B
  def self.make_clone
    C.clone
  end
  class C
    @@value = 1
    def self.make_instance
      @@value = 2
      new
    end
    def data
      @@value
    end
  end
end
[pattern 1]
puts B.make_clone.make_instance.data  # => 1 -- wrong
puts B.make_clone.make_instance.data  # => 2 -- collect
[pattern 2]
puts B::C.make_instance.data          # => 2 -- collect
puts B.make_clone.make_instance.data  # => 2 -- collect

expected

[pattern 1]
2
2
[pattern 2]
2
2

actual

[pattern 1]
1
2
[pattern 2]
2
2

noticed

ruby-1.8.6-p420, ruby-1.9.3-p545, ruby 2.1.2p80 all problem.
but codepad(1.8.6) is looking for expected. http://codepad.org/1VPpyCvy

History

Updated by Chiether (Norikaz Ishii) over 5 years ago

maybe...

first cloned-class write to original-class's class-variable; but new instance reference cloned-class's class-variable.
second cloned-class reference original-class that class-variable updated by first cloned-class.

.new has problem when cloned class.

additional example code

class A
  @@value = 1
  def self.make_clone
    puts self.inspect # => A
    return A.clone
  end
  def self.check
    puts self.inspect      # => #<Class:0x007f7ce1160b38>
    puts self.data.inspect # => 1
    puts A.data.inspect    # => 1
    @@value = 2
    puts self.data.inspect # => 2
    puts A.data.inspect    # => 2 ((weak)except:1, cloned-class reference A? let's say its good for example.)
    instance = new
    puts instance.inspect            # => #<#<Class:0x007f7ce1160b38>:0x007f7ce1543d18>
    puts instance.data.inspect       # => 1 (expected:2)
    puts instance.class.inspect      # => #<Class:0x007f7ce1160b38>
    puts instance.class.data.inspect # => 2
    puts instance.class.new.inspect  # => #<#<Class:0x007f7ce1160b38>:0x007f7ce1543b88>
    puts instance.class.new.data     # => 1 (expected:2)
    return instance
  end
  def self.data
    @@value
  end
  def data
    @@value
  end
end
A.make_clone.check

Updated by jeremyevans0 (Jeremy Evans) 10 days ago

  • Backport deleted (1.9.3: UNKNOWN, 2.0.0: UNKNOWN, 2.1: UNKNOWN)
  • Status changed from Open to Rejected

While not intuitive, I think this behavior is expected and not a bug. It is not relating to class cloning, it is due to class variable lookup. Normal class variable lookup (e.g. not using Module#class_variable_{g,s}et), uses crefs, making it more similar to constant lookup than method lookup. It skips crefs added by singleton classes as well as crefs added by *eval. See vm_get_cvar_base in vm_inshelper.c for the algorithm used to find the base class used for class variable lookup. Class variable lookup will look in the ancestors of the base class for the class variable.

Taking the original post as an example. The first time B.make_clone.make_instance is called, it runs @@value = 2. At the point in that call, the active cref is C (not the clone of C created by B.make_clone). So at that point, it sets @@value = 2 in C, not in the clone of C. When data is called on that it, that is a regular method and not a singleton method, so the class variable lookup uses the cloned class variable and not the original class variable in C. The second time B.make_clone.make_instance is run, the value for @@value has already been set to 2 in C, so there is no behavior change.

Here's an example allowing you to see the difference:

class B
  def self.make_clone
    C.clone
  end
  class C
    @@value = 1
    def self.make_instance
      @@value += 1
      [data, new.data]
    end
    def self.data
      @@value
    end
    def data
      @@value
    end
  end
end
p B.make_clone.make_instance
p B.make_clone.make_instance
p B::C.make_instance
p B.make_clone.make_instance

output is:

[2, 1]
[3, 2]
[4, 4]
[5, 4]

To show this is not related to cloning, here's a similar example that does not use cloning:

class A
  @@value = 1

  def foo
    @@value
  end
end

class B
  @@value = 2
  def A.bar
    @@value = 3
  end
  def foo
    @@value
  end
end

p A.new.foo
p B.new.foo
A.bar
p A.new.foo
p B.new.foo

The output for this program is:

1
2
1
3

Note how the call to A.bar set the @@value for B, not A.

Also available in: Atom PDF