"Method" objects assigned to a Context appear in every subsequent Context
abscondment opened this issue · 0 comments
Problem Setup
Say I have defined a method in Ruby, and I want to make it available in JS.
I might do that like this:
class UserProxy
attr_reader :name
def initialize(name)
@name = name
end
end
user = UserProxy.new('Brendan')
context = V8::Context.new
context['getMyName'] = user.method(:name)
context.scope.getMyName
# => "Brendan"
This seems very straightforward.
Say I try to do this multiple times, with different UserProxy
s. Maybe I create a new V8::Context
each time, or I might simply assign a new method to context['getMyName']
. Either way, it won't work as expected.
Expectation
I should be able to assign a different method to getMyName
in this V8::Context
, or in a new instance of V8::Context
, and receive a different return value.
Reality
I cannot change getMyName
-- it always returns "Brendan". Additionally, the original method is called after trying to assign getMyName
to any brand new V8::Context
.
I notice that if I wrap my ruby method in a lambda
, things work. If I assign the Method
object, however, it is broken.
Test Case
Here's a test case that demonstrates this in more detail:
gem 'therubyracer', '~> 0.12.2'#, platforms: :ruby
require 'therubyracer'
require 'set'
require 'minitest/autorun'
require 'ostruct'
Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
class UserProxy
attr_reader :name
def initialize(name)
@name = name
end
end
class FakeContext
attr_reader :scope
def initialize
@scope = OpenStruct.new
end
def []=(k,v)
@scope[k] = v
end
end
class BugTest < Minitest::Test
def test_call_via_method_FAILS
billy_context = by_method('Billy')
bobby_context = by_method('Bobby')
refute_equal billy_context.scope.name(),
bobby_context.scope.name(),
'Billy and Bobby really should have different names'
end
def test_call_many_methods_FAILS
names = %w{ Brooke Bella Ben Bethany Blair Betsy }
results = names.map do |name|
by_method(name).scope.name()
end
assert_equal Set.new(names),
Set.new(results),
"Every name should be returned."
end
def test_call_via_lambda_SUCCEEDS
billy_context = by_lambda('Billy')
bobby_context = by_lambda('Bobby')
refute_equal billy_context.scope.name(),
bobby_context.scope.name(),
'Billy and Bobby really should have different names'
end
def test_call_fake_via_method_SUCCEEDS
billy_context = fake_by_method('Billy')
bobby_context = fake_by_method('Bobby')
refute_equal billy_context.scope.name(),
bobby_context.scope.name(),
'Billy and Bobby really should have different names'
end
protected
def fake_by_method(name)
user = UserProxy.new(name)
context = FakeContext.new
context['name'] = user.method(:name)
context
end
def by_method(name)
user = UserProxy.new(name)
context = V8::Context.new
context['name'] = user.method(:name)
context
end
def by_lambda(name)
user = UserProxy.new(name)
context = V8::Context.new
context['name'] = lambda { user.name }
context
end
end
You'll notice that, depending on which test executes first, a different name method might get stuck.
E.g. if I run with --seed 54026
, "Brooke" is the only name ever returned. If I run with --seed 29795
, "Billy" gets set first and therefore is stuck forever.
Environment Details
- ruby 2.2.5p319 (2016-04-26 revision 54774) [x86_64-darwin15]
- therubyracer 0.12.2
- libv8 3.16.14.15