Bug #9603
closedunusual reference class-variable with cloned class.
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
Updated by Chiether (Norikaz Ishii) about 10 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) over 4 years ago
- Status changed from Open to Rejected
- Backport deleted (
1.9.3: UNKNOWN, 2.0.0: UNKNOWN, 2.1: UNKNOWN)
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
.