Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autoload results in "Java package 'org.ruboto.jruby9k_poc' does not have a method `const_missing' with 1 argument" #7

Closed
donv opened this issue Aug 8, 2019 · 13 comments
Assignees

Comments

@donv
Copy link
Member

donv commented Aug 8, 2019

Example added to the app:

    require 'active_support/dependencies'
    ActiveSupport::Dependencies.autoload_paths << "uri:classloader:"
    AutoloadedClass.new.perform
I/System.out: Exception `ArgumentError' at org/jruby/javasupport/JavaPackage.java:258 - Java package 'org.ruboto.jruby9k_poc' does not have a method `const_missing' with 1 argument
I/System.out: ArgumentError
    Java package 'org.ruboto.jruby9k_poc' does not have a method `const_missing' with 1 argument
I/System.out: org/jruby/javasupport/JavaPackage.java:258:in `method_missing'
    uri:classloader:/active_support/dependencies.rb:542:in `load_missing_constant'
    uri:classloader:/active_support/dependencies.rb:195:in `const_missing'
    org/jruby/java/proxies/JavaProxy.java:560:in `const_missing'
I/chatty: uid=10086(org.ruboto.jruby9k_poc) identical 6 lines
I/System.out: org/jruby/java/proxies/JavaProxy.java:560:in `const_missing'
@donv
Copy link
Member Author

donv commented Aug 8, 2019

A couple of findings:

Referring to ::AutoloadedClass works with autoloading.

Using autoloading in a regular Ruby class gives a different error than using autoloading in an activity class backed by a Java class:

I/System.out: Java::JavaLang::ClassCastException
    org.jruby.RubyNil cannot be cast to org.jruby.javasupport.JavaClass
    Exception testing autload: org.jruby.RubyNil cannot be cast to org.jruby.javasupport.JavaClass
I/System.out: org.jruby.javasupport.JavaClass.getJavaClass(JavaClass.java:172)
I/System.out: org.jruby.javasupport.Java.getProxyUnderClass(Java.java:1184)
    org.jruby.javasupport.Java.get_inner_class(Java.java:1197)
    org.jruby.java.proxies.JavaProxy$ClassMethods.const_missing(JavaProxy.java:560)
I/System.out: org.jruby.java.proxies.JavaProxy$ClassMethods$INVOKER$s$1$0$const_missing.call(Unknown Source:10)
    org.jruby.RubyClass.finvoke(RubyClass.java:798)
I/System.out: org.jruby.runtime.Helpers.invoke(Helpers.java:449)
    org.jruby.RubyBasicObject.callMethod(RubyBasicObject.java:364)
I/System.out: org.jruby.ir.instructions.SearchConstInstr.cache(SearchConstInstr.java:101)
I/System.out: org.jruby.ir.interpreter.StartupInterpreterEngine.processOtherOp(StartupInterpreterEngine.java:160)
    org.jruby.ir.interpreter.StartupInterpreterEngine.interpret(StartupInterpreterEngine.java:104)
    org.jruby.ir.interpreter.InterpreterEngine.interpret(InterpreterEngine.java:80)
I/System.out: org.jruby.internal.runtime.methods.InterpretedIRMethod.INTERPRET_METHOD(InterpretedIRMethod.java:153)
    org.jruby.internal.runtime.methods.InterpretedIRMethod.call(InterpretedIRMethod.java:140)
    org.jruby.runtime.callsite.CachingCallSite.cacheAndCall(CachingCallSite.java:317)
    org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:138)
    org.jruby.ir.interpreter.InterpreterEngine.processCall(InterpreterEngine.java:345)
    org.jruby.ir.interpreter.StartupInterpreterEngine.interpret(StartupInterpreterEngine.java:72)
    org.jruby.ir.interpreter.Interpreter.INTERPRET_BLOCK(Interpreter.java:114)
    org.jruby.runtime.InterpretedIRBlockBody.commonYieldPath(InterpretedIRBlockBody.java:137)
    org.jruby.runtime.IRBlockBody.call(IRBlockBody.java:79)
    org.jruby.runtime.Block.call(Block.java:129)
    org.jruby.RubyProc.call(RubyProc.java:295)
    org.jruby.RubyProc.call(RubyProc.java:274)
    org.jruby.RubyProc.call(RubyProc.java:270)
    org.jruby.javasupport.Java$ProcToInterface.callProc(Java.java:1133)
    org.jruby.javasupport.Java$ProcToInterface.access$300(Java.java:1110)
    org.jruby.javasupport.Java$ProcToInterface$ConcreteMethod.call(Java.java:1156)
    org.jruby.internal.runtime.methods.DynamicMethod.call(DynamicMethod.java:200)
    org.jruby.RubyClass.finvoke(RubyClass.java:798)
    org.jruby.runtime.Helpers.invoke(Helpers.java:449)
I/System.out: org.jruby.javasupport.Java$InterfaceProxyHandler.invoke(Java.java:1421)
    java.lang.reflect.Proxy.invoke(Proxy.java:1006)
    $Proxy9.onClick(Unknown Source)
    android.view.View.performClick(View.java:6597)
    android.view.View.performClickInternal(View.java:6574)
    android.view.View.access$3100(View.java:778)
    android.view.View$PerformClick.run(View.java:25885)
I/System.out: android.os.Handler.handleCallback(Handler.java:873)
    android.os.Handler.dispatchMessage(Handler.java:99)
    android.os.Looper.loop(Looper.java:193)
    android.app.ActivityThread.main(ActivityThread.java:6669)
    java.lang.reflect.Method.invoke(Native Method)
    com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
    com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

@donv
Copy link
Member Author

donv commented Aug 9, 2019

In our production app, I added two patches to work around the issue.

Firstly, I added this patch to active_support/dependencies.rb line 546:

          if qualified_name.start_with?('Java::')
            parent = Module
          end

This prevents calling const_missing on the Java package module.

Secondly, I patched Dir[] to scan using the APK file jar path instead of the classloader path since JRuby fails to scan directories in the classpath inside the APK.

module DirGlobPatch
  def [](*patterns, **opts)
    puts "patterns: #{patterns.inspect}"
    super(*patterns.map{|pattern| pattern.sub('uri:classloader:', APK_PATH)}, **opts)
  end
end

Dir.singleton_class.prepend DirGlobPatch

With these two patches, I am able to start the application, log in, and list orders, so a lot is working now including communication with the server by TCP socket and HTTP.

The issue with the ClassCastException for org.jruby.RubyNil still remains.

@donv
Copy link
Member Author

donv commented Aug 12, 2019

I am tracing the call down to org.jruby.javasupport.JavaClass.getJavaClass(), but I am not sure what the expected values are.

@headius I'll hang around Gitter this evening (utc) if you have time to help me?

@donv
Copy link
Member Author

donv commented Aug 12, 2019

I can reproduce in command line in a smallish example:

import org.jruby.embed.LocalContextScope;
import org.jruby.embed.LocalVariableBehavior;
import org.jruby.embed.ScriptingContainer;

public class Main {
    private static ScriptingContainer ruby;

    public static void main(String[] args) {
        ruby = new ScriptingContainer(LocalContextScope.SINGLETON, LocalVariableBehavior.TRANSIENT);
        SuperClass component = new SuperClass();
        ruby.runScriptlet("Java::" + ((Object) component).getClass().getName() + ".__persistent__ = true");
        ruby.put("SuperClass", runRubyMethod(component, "singleton_class"));

        if (ruby.getCompatVersion().is1_9()) {
            ruby.runScriptlet(
                    "Gem.paths = ENV.merge('GEM_HOME' => File.expand_path('~/.gem/jruby/1.9.3'))\n"
            );
        }

        ruby.runScriptlet("class SuperClass\n" +
                "  def ruby_method\n" +
                "    require 'active_support/dependencies'\n" +
                "    ActiveSupport::Dependencies.autoload_paths << 'uri:classloader:/'\n" +
                "    AutoloadedClass.new.perform\n" +
                "    puts 'Autoload OK'\n" +
                "  rescue => e\n" +
                "    puts \"Exception testing autload: #{e}\"\n" +
                "  end\n" +
                "end\n");
        component.testAutoload();
    }

    public static Object runRubyMethod(Object receiver, String methodName) {
        return ruby.runRubyMethod(Object.class, receiver, methodName);
    }
}
public class SuperClass {
    public void testAutoload() {
        Main.runRubyMethod(this, "ruby_method");
    }
}

autoloaded_class.rb:

class AutoloadedClass
  def perform
    puts 'AutoloadedClass#perform'
  end
end

@donv
Copy link
Member Author

donv commented Aug 12, 2019

The example above works with JRuby 1.7.x and fails with JRuby 9.2.x.x.

@donv
Copy link
Member Author

donv commented Aug 15, 2019

Here is an even smaller example without ActiveSupport. It works with JRuby 1.7.x and fails with JRuby 9.2.8.0.

If the instance is a regular Ruby object, not a Java object, the example works on JRuby 1.7.x, JRuby 9.2.8.0, MRI 2.6.3, and TruffleRuby 19.1.0.

instance = java.lang.String.new
instance.class.__persistent__ = true if instance.class.respond_to?('__persistent__=')

MyClass = instance.singleton_class

class MyClass
  def my_method
    MissingConstant
    raise 'Expected NameError'
  rescue NameError
    puts "Success!"
  end
end

instance.my_method

@headius
Copy link
Member

headius commented Aug 15, 2019

This issue is in code written by @kares for jruby/jruby#3333, which allows accessing non-public inner classes lazily using a const_missing hook.

The issue seems to be that a JavaClass is not being created for this particular path anymore, resulting in a nil being cast incorrectly.

I've tried to patch this with simpler logic but it doesn't pass the tests that @kares wrote:

diff --git a/core/src/main/java/org/jruby/java/proxies/JavaProxy.java b/core/src/main/java/org/jruby/java/proxies/JavaProxy.java
index ab32c909c9..5ab6d28acf 100644
--- a/core/src/main/java/org/jruby/java/proxies/JavaProxy.java
+++ b/core/src/main/java/org/jruby/java/proxies/JavaProxy.java
@@ -557,7 +557,16 @@ public class JavaProxy extends RubyObject {
         // handling non-public inner classes retrieval ... like private constants
         @JRubyMethod(name = "const_missing", required = 1, meta = true, visibility = Visibility.PRIVATE, frame = true)
         public static IRubyObject const_missing(ThreadContext context, IRubyObject self, IRubyObject name) {
-            return Java.get_inner_class(context, (RubyModule) self, name);
+            Class<?> javaClass = JavaClass.getJavaClass(context, (RubyModule) self);
+
+            for (Class<?> declaredClass : javaClass.getDeclaredClasses()) {
+                if (declaredClass.getSimpleName().equals(name.toString())) {
+                    return ((RubyModule) self).setConstant(name.toString(), Java.getProxyClass(context.runtime, declaredClass));
+                }
+            }
+
+            // else dispatch to super const_missing
+            return Helpers.invokeSuper(context, self, name, Block.NULL_BLOCK);
         }
 
         @JRubyMethod(meta = true)

@donv
Copy link
Member Author

donv commented Aug 16, 2019

I tested with the patched jruby-complete-9.2.9.0-SNAPSHOT from your branch, and it solves the ClassCastException ! Woohoo!

The original error for this issue still remains:

Java package 'org.ruboto.jruby9k_poc' does not have a method `const_missing' with 1 argument

I will try to make a smaller reproduction than the POC.

headius added a commit to jruby/jruby that referenced this issue Aug 16, 2019
@headius
Copy link
Member

headius commented Aug 21, 2019

Aha, I have a theory. I suspect that ActiveSupport is basically doing its own lookup+const_missing rather than just going after the constant directly. We have a const_missing under the covers in our package modules, but the Ruby side does not expose a const_missing method (since there could be a "const_missing" package).

If I'm right, this is a simple enough reproduction:

$ jruby -e 'java.lang.const_missing(:System)'
ArgumentError: Java package 'java.lang' does not have a method `const_missing' with 1 argument
  method_missing at org/jruby/javasupport/JavaPackage.java:258
          <main> at -e:1

@donv
Copy link
Member Author

donv commented Aug 21, 2019

Yes, I think you are right.

headius added a commit to jruby/jruby that referenced this issue Aug 21, 2019
The error from ruboto/JRuby9K_POC#7 was caused (most likely) by
ActiveSupport attempting to call `const_missing` directly on the
package object, but finding it marked private and refusing to make
the call. This change removes the private visibility from both
const_missing and method_missing.
@headius
Copy link
Member

headius commented Aug 21, 2019

I believe this is fixed on master. I also made method_missing visible.

@kares Do you know why these were private?

@kares
Copy link

kares commented Aug 22, 2019

do not recall any particular reason, fix should be fine

@donv donv closed this as completed in 047ee3c Aug 22, 2019
@donv
Copy link
Member Author

donv commented Aug 22, 2019

Tried the jruby-complete-20190822.050313-17.jar without the dependencies.rb patch, and it looks good!

Adithya-copart pushed a commit to Adithya-copart/jruby that referenced this issue Aug 25, 2019
Adithya-copart pushed a commit to Adithya-copart/jruby that referenced this issue Aug 25, 2019
The error from ruboto/JRuby9K_POC#7 was caused (most likely) by
ActiveSupport attempting to call `const_missing` directly on the
package object, but finding it marked private and refusing to make
the call. This change removes the private visibility from both
const_missing and method_missing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants