Skip to content

Java method wrapped in Ruby is not seen by subclasses #5395

@headius

Description

@headius

There appears to be a regression in how we handle method lookup and monkey-patching of Java classes.

The following script produces an arity error when run with JRuby master (9.2.1) and current execjs and therubyrhino:

require 'execjs'

src = <<JS
;function compile(script, options) {
  function (a, b) {}
}
JS

func = ExecJS.compile(src)

func.call("compile", 1, 2) # error

And the output:

$ jruby asdf.rb
ArgumentError: wrong number of arguments (2 for 4)
    call at /Users/headius/projects/jruby/lib/ruby/gems/shared/gems/execjs-2.7.0/lib/execjs/ruby_rhino_runtime.rb:39
  <main> at asdf.rb:13

This pattern originates from the coffee-script gem, which uses JS similar to the above to call the coffeescript compiler and return a function. The func.call line invokes the compile function defined above, which returns another anonymous function.

That anonymous function is from Rhino, of Java types InterpretedFunction. InterpretedFunction does define a 4-arity call method, but it is originally declared two classes higher in the hierarchy, at BaseFunction. BaseFunction gets patched by TheRubyRhino to have a different version of call:

lib/rhino/rhino_ext.rb from therubyrhino

...
# The base class for all JavaScript function objects.
class Java::OrgMozillaJavascript::BaseFunction
  
  # Object call(Context context, Scriptable scope, Scriptable this, Object[] args)
  alias_method :__call__, :call
  
  # make JavaScript functions callable Ruby style e.g. `fn.call('42')`
  # 
  # NOTE: That invoking #call does not have the same semantics as
  # JavaScript's Function#call but rather as Ruby's Method#call !
  # Use #apply or #bind before calling to achieve the same effect.
  def call(*args)
    context = Rhino::JS::Context.enter; scope = current_scope(context)
    # calling as a (var) stored function - no this === undefined "use strict"
    # TODO can't pass Undefined.instance as this - it's not a Scriptable !?
    this = Rhino::JS::ScriptRuntime.getGlobal(context)
    js_args = Rhino.args_to_javascript(args, scope)
    Rhino.to_ruby __call__(context, scope, this, js_args)
  rescue Rhino::JS::JavaScriptException => e
    raise Rhino::JSError.new(e)
  ensure
    Rhino::JS::Context.exit
  end
...

It appears this replaced call method is getting wiped out or otherwise ignored when calling against Java descendants of BaseFunction like InterpretedFunction.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions