Skip to content

JRuby 9.3 Hash eats key String subclass instance variables #6895

@shibz

Description

@shibz

Environment Information

Provide at least:

  • JRuby version (jruby -v) and command line (flags, JRUBY_OPTS, etc)
jruby 9.3.1.0 (2.6.8) 2021-10-13 2e01e7199d OpenJDK 64-Bit Server VM 25.302-b08 on 1.8.0_302-b08 +jit [linux-x86_64]
  • Operating system and platform (e.g. uname -a)
Linux [hostname removed] 5.4.134-73.228.amzn2int.x86_64 #1 SMP Wed Jul 21 17:54:58 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

Expected Behavior

Reproduction:

class CoolString < String
  def initialize(foo)
    @foo = foo
	super(foo)
  end
end

obj1 = CoolString.new('hello!')

evil_variable_eating_hash = {}
evil_variable_eating_hash[obj1] = 'oh no!'
obj2 = evil_variable_eating_hash.keys[0]

puts "Obj1: #{obj1.instance_variable_get(:@foo)}"
puts "Obj2: #{obj2.instance_variable_get(:@foo)}"

obj3 = JRuby.runtime.freezeAndDedupString(obj1)

puts "Obj1: #{obj1.instance_variable_get(:@foo)}"
puts "Obj3: #{obj3.instance_variable_get(:@foo)}"

Trying to upgrade an app from JRuby 9.2.16.0 to 9.3.0.0/9.3.0.1 and am seeing this bug when inserting a subclass of String into a Hash as a key. The instance variables on the String subclass are removed. This logic works fine in MRI Ruby 2.1, 2.3, 2.4, 2.5, and 2.7.

Working Example from MRI Ruby 2.7.4

irb(main):001:0> RUBY_VERSION
=> "2.7.4"
irb(main):002:0> RUBY_PLATFORM
=> "x86_64-linux"
irb(main):003:1* class CoolString < String
irb(main):004:2*   def initialize(foo)
irb(main):005:2*     @foo = foo
irb(main):006:2*     super(foo)
irb(main):007:1*   end
irb(main):008:0> end
=> :initialize
irb(main):009:0> obj1 = CoolString.new('hello!')
=> "hello!"
irb(main):010:0> evil_variable_eating_hash = {}
=> {}
irb(main):011:0> evil_variable_eating_hash[obj1] = 'oh no!'
=> "oh no!"
irb(main):012:0> obj2 = evil_variable_eating_hash.keys[0]
=> "hello!"
irb(main):013:0> puts "Obj1: #{obj1.instance_variable_get(:@foo)}"
Obj1: hello!
=> nil
irb(main):014:0> puts "Obj2: #{obj2.instance_variable_get(:@foo)}"
Obj2: hello!

You can see on line 13 and 14 that both obj1 and obj2 still have the @foo instance variable set.

Actual Behavior

The CoolString object, after being retrieved from Hash.keys, no longer has the @foo instance variable set.

irb(main):001:0> class CoolString < String
irb(main):002:1> def initialize(foo)
irb(main):003:2> @foo = foo
irb(main):004:2> super(foo)
irb(main):005:2> end
irb(main):006:1> end
=> :initialize
irb(main):007:0> obj1 = CoolString.new('hello!')
=> "hello!"
irb(main):008:0> evil_variable_eating_hash = {}
=> {}
irb(main):009:0> evil_variable_eating_hash[obj1] = 'oh no!'
=> "oh no!"
irb(main):010:0> obj2 = evil_variable_eating_hash.keys[0]
=> "hello!"
irb(main):011:0> puts "Obj1: #{obj1.instance_variable_get(:@foo)}"
Obj1: hello!
=> nil
irb(main):012:0> puts "Obj2: #{obj2.instance_variable_get(:@foo)}"
Obj2:
=> nil
irb(main):013:0> require 'jruby'
=> true
irb(main):014:0> obj3 = JRuby.runtime.freezeAndDedupString(obj1)
=> "hello!"
irb(main):015:0> puts "Obj1: #{obj1.instance_variable_get(:@foo)}"
Obj1: hello!
=> nil
irb(main):016:0> puts "Obj3: #{obj3.instance_variable_get(:@foo)}"
Obj3:
=> nil

You can see on line 11 and 12 that obj2 no longer has the @foo instance variable set. I traced the issue to JRuby.runtime.freezeAndDedupString which can be seen eating the instance variable in lines 14-16.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions