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

Passing self to java code from ctor in a class that extends a java class causes NPE #6140

Open
byteit101 opened this issue Mar 26, 2020 · 7 comments

Comments

@byteit101
Copy link
Member

Environment Information

Provide at least:

  • Version: 9.3.0.0-SNAPSHOT (master)
  • OS: Debian 10

Expected Behavior

class Demo < java.lang.Exception # any java class works so far as I've tested
  def initialize
    self.to_java # or anything that calls this implicitly, such as java.lang.String.valueOf(self)
  end
end
Demo.new

Should not do anything observable

Actual Behavior

Traceback (most recent call last):
       16: from org.jruby.java.proxies.ConcreteJavaProxy$NewMethod.call(ConcreteJavaProxy.java:151)
       15: from org.jruby.internal.runtime.methods.JavaMethod$JavaMethodZeroOrNBlock.call(JavaMethod.java:327)
       14: from org.jruby.RubyClass$INVOKER$i$newInstance.call(RubyClass$INVOKER$i$newInstance.gen)
       13: from org.jruby.RubyClass.newInstance(RubyClass.java:887)
       12: from org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:149)
       11: from org.jruby.runtime.callsite.CachingCallSite.cacheAndCall(CachingCallSite.java:329)
       10: from org.jruby.internal.runtime.methods.MixedModeIRMethod.call(MixedModeIRMethod.java:125)
        9: from org.jruby.internal.runtime.methods.MixedModeIRMethod.INTERPRET_METHOD(MixedModeIRMethod.java:138)
        8: from org.jruby.ir.interpreter.InterpreterEngine.interpret(InterpreterEngine.java:80)
        7: from org.jruby.ir.interpreter.StartupInterpreterEngine.interpret(StartupInterpreterEngine.java:72)
        6: from org.jruby.ir.interpreter.InterpreterEngine.processCall(InterpreterEngine.java:346)
        5: from org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:139)
        4: from org.jruby.runtime.callsite.CachingCallSite.cacheAndCall(CachingCallSite.java:318)
        3: from org.jruby.java.addons.KernelJavaAddons$INVOKER$s$to_java.call(KernelJavaAddons$INVOKER$s$to_java.gen)
        2: from org.jruby.java.addons.KernelJavaAddons.to_java(KernelJavaAddons.java:18)
        1: from org.jruby.java.proxies.ConcreteJavaProxy.toJava(ConcreteJavaProxy.java:215)
Java::JavaLang::NullPointerException ()
@headius
Copy link
Member

headius commented Mar 26, 2020

This might not be possible to fix.

At the point where you're calling self.to_java, self has not been completely initialized. Specifically, the initialize call from the subclass needs to call super to ensure the superclass constructors have fired. Until this happens, there is no Java object, and the getObject call in toJava will return null, causing this error.

Because we can't "really" extend a Java class from Ruby (because all Ruby objects must extend RubyBasicObject, and can't extend another Java class as well) the lifecycle of this case has some inherent challenges. If we construct the Java object first and then initialize the Ruby object, the Java object will not be able to see a constructed Ruby object. If we initialize the Ruby object first (which is how we do it now), the Java object won't be available.

In JRuby, we opted to construct the Ruby object first, since it's at the bottom (it represents the subclass) and because it allows the Ruby initialize to decide how to construct the superclass object (via the super call).

We could perhaps make a better error here... something along the lines of "uninitialized subclass object, please invoke `super' first". I'm not sure there's a way to make this work, though. Thoughts, @enebo?

BTW, what outputs this reversed trace? I never implemented reversed backtraces in JRuby!

@byteit101
Copy link
Member Author

We could perhaps make a better error here... something along the lines of "uninitialized subclass object, please invoke `super' first". I'm not sure there's a way to make this work, though.

That's fine, I just don't think we want raw NPE's bouncing around, hence the filing

BTW, what outputs this reversed trace? I never implemented reversed backtraces in JRuby!

IRB, as installed via rvm for jruby-9.2.7.0

@enebo
Copy link
Member

enebo commented Mar 26, 2020

I don't know if this was obvious from @headius reply but if you add super before that self reference this does not crash. This seemingly is a workaround for you at least.

The bulk of the problem is Ruby is a language of values. If we knew the value came from the keyword self as the holder then this would be simple. Of course, we could add that to our runtime but it would penalize all code.

@headius I believe we discussed implicit super in JI initialize when none is present? I don't recall the issues (I can guess picking zsuper vs super() as one) but implicit addition would eliminate the error if we could.

@byteit101
Copy link
Member Author

@enebo thanks, yes, I figured out the workaround before I filed, though probably should have mentioned that

@headius
Copy link
Member

headius commented Mar 27, 2020

Perhaps we should revisit having a hard error if you don't call super before proceeding to make other calls on the object? Hard to do with a dynlang but I hate these random fails people get.

@headius
Copy link
Member

headius commented Mar 27, 2020

At the very least I think we can make it a hard error if initialize completes without having called super. It wouldn't have prevented this issue but it prevents the major flub of not calling super at all.

@headius
Copy link
Member

headius commented Mar 27, 2020

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