JRuby (please use github issues at http://bugs.jruby.org)
  1. JRuby (please use github issues at http://bugs.jruby.org)
  2. JRUBY-2748

Can't subclass a Java class that calls an abstract function in its constructor

    Details

    • Type: Bug Bug
    • Status: Open Open
    • Priority: Major Major
    • Resolution: Unresolved
    • Affects Version/s: JRuby 1.1.2
    • Fix Version/s: JRuby 2
    • Component/s: Java Integration
    • Labels:
      None
    • Number of attachments :
      1

      Description

      I have found that while subclassing a Java class that calls an abstract function in its constructor will cause the initialization of the subclass in JRuby to throw an ArgumentError with message "Constructor invocation failed: null."

      Here is my Java base class:

      package com.allstontrading.logalyzer;
      import java.util.List;
      
      public abstract class AbstractTest {
           protected String obj;
      
           public AbstractTest() {
                this("dog");
                this.run();
           }
           public AbstractTest(String obj) {
                this.obj = obj;
           }
      	
           protected abstract List<String> myAbstractFunc();
           public void run() {
                if (this.myAbstractFunc() != null)
                     for (String string : this.myAbstractFunc())
                          System.out.println("Big " + this.obj + "s Eat: " + string);
                else
                     System.out.println("OMG");
           }
      }
      

      Here is my example JRuby script:

      include Java
      
      import 'com.allstontrading.logalyzer.AbstractTest'
      import 'java.util.ArrayList'
      
      class RubyTest < com.allstontrading.logalyzer.AbstractTest
          def initialize()
              super("Pink Elephant")
          end
      
          def myAbstractFunc()
              list = ArrayList.new
              list << "Cancerous Rocks"
              list << "Candy Apples"
              list << "Moldy Bread"
              return list
          end
      end
      
      puts "This test does NOT call an abstract function in the constructor."
      test = RubyTest.new
      test.run
      
      class AnotherRubyTest < com.allstontrading.logalyzer.AbstractTest
          def initialize()
              super
          end
          
          def myAbstractFunc()
              list = ArrayList.new
              list << "Cancerous Rocks"
              list << "Candy Apples"
              list << "Moldy Bread"
              return list
          end 
      end     
              
      puts ""
      puts ""
      puts "This test does call an abstract function in the constructor."
      anotherTest = AnotherRubyTest.new
      

      Here is the output:

      This test does NOT call an abstract function in the constructor.
      Big Pink Elephants Eat: Cancerous Rocks
      Big Pink Elephants Eat: Candy Apples
      Big Pink Elephants Eat: Moldy Bread
      
      This test does call an abstract function in the constructor.
      /home/mbohn/jruby-1.1.2/lib/ruby/site_ruby/1.8/builtin/javasupport/utilities/base.rb:26:in `new_instance2': Constructor invocation failed: null (ArgumentError)
              from /home/mbohn/jruby-1.1.2/lib/ruby/site_ruby/1.8/builtin/javasupport/utilities/base.rb:26:in `__jcreate!'
              from /home/mbohn/jruby-1.1.2/lib/ruby/site_ruby/1.8/builtin/javasupport/proxy/concrete.rb:23:in `initialize'
              from rubyTestAbstract.rb:26:in `initialize'
              from /home/mbohn/jruby-1.1.2/lib/ruby/site_ruby/1.8/builtin/javasupport/proxy/concrete.rb:6:in `new'
              from /home/mbohn/jruby-1.1.2/lib/ruby/site_ruby/1.8/builtin/javasupport/proxy/concrete.rb:6:in `new'
              from rubyTestAbstract.rb:41
      

        Issue Links

          Activity

          Hide
          Charles Oliver Nutter added a comment -

          Feel like doing a little more homework and providing a case that fails too? And you can put LEFTBRACEnoformatRIGHTBRACE around your code snippits to format them right.

          Show
          Charles Oliver Nutter added a comment - Feel like doing a little more homework and providing a case that fails too? And you can put LEFTBRACEnoformatRIGHTBRACE around your code snippits to format them right.
          Hide
          Michael Bohn added a comment -

          Actually, the case I provided does fail. My additional homework involved copy-pasting the above code into the appropriate files only to run them and encounter the bug. Attached are the files I just created to ensure the bug still exists. Try running and analyzing the files I submitted and you should see the error.

          Here is the output I get when I run the attached:
          This test does NOT call an abstract function in the constructor.
          Big Pink Elephants Eat: Cancerous Rocks
          Big Pink Elephants Eat: Candy Apples
          Big Pink Elephants Eat: Moldy Bread

          This test does call an abstract function in the constructor.
          /home/mbohn/jruby-1.1.2/lib/ruby/site_ruby/1.8/builtin/javasupport/utilities/base.rb:26:in `new_instance2': Constructor invocation failed: null (ArgumentError)
          from /home/mbohn/jruby-1.1.2/lib/ruby/site_ruby/1.8/builtin/javasupport/utilities/base.rb:26:in `__jcreate!'
          from /home/mbohn/jruby-1.1.2/lib/ruby/site_ruby/1.8/builtin/javasupport/proxy/concrete.rb:23:in `initialize'
          from abstractSubclass.rb:26:in `initialize'
          from /home/mbohn/jruby-1.1.2/lib/ruby/site_ruby/1.8/builtin/javasupport/proxy/concrete.rb:6:in `new'
          from /home/mbohn/jruby-1.1.2/lib/ruby/site_ruby/1.8/builtin/javasupport/proxy/concrete.rb:6:in `new'
          from abstractSubclass.rb:40

          Show
          Michael Bohn added a comment - Actually, the case I provided does fail. My additional homework involved copy-pasting the above code into the appropriate files only to run them and encounter the bug. Attached are the files I just created to ensure the bug still exists. Try running and analyzing the files I submitted and you should see the error. Here is the output I get when I run the attached: This test does NOT call an abstract function in the constructor. Big Pink Elephants Eat: Cancerous Rocks Big Pink Elephants Eat: Candy Apples Big Pink Elephants Eat: Moldy Bread This test does call an abstract function in the constructor. /home/mbohn/jruby-1.1.2/lib/ruby/site_ruby/1.8/builtin/javasupport/utilities/base.rb:26:in `new_instance2': Constructor invocation failed: null (ArgumentError) from /home/mbohn/jruby-1.1.2/lib/ruby/site_ruby/1.8/builtin/javasupport/utilities/base.rb:26:in `__jcreate!' from /home/mbohn/jruby-1.1.2/lib/ruby/site_ruby/1.8/builtin/javasupport/proxy/concrete.rb:23:in `initialize' from abstractSubclass.rb:26:in `initialize' from /home/mbohn/jruby-1.1.2/lib/ruby/site_ruby/1.8/builtin/javasupport/proxy/concrete.rb:6:in `new' from /home/mbohn/jruby-1.1.2/lib/ruby/site_ruby/1.8/builtin/javasupport/proxy/concrete.rb:6:in `new' from abstractSubclass.rb:40
          Hide
          Michael Bohn added a comment -

          This bug also exists in 1.1.3. Anyone know if there is a plan to fix it?

          Show
          Michael Bohn added a comment - This bug also exists in 1.1.3. Anyone know if there is a plan to fix it?
          Hide
          Charles Oliver Nutter added a comment -

          I'll try to look at this one, seems like it could be a simpler issue buried in the guts of JI.

          Show
          Charles Oliver Nutter added a comment - I'll try to look at this one, seems like it could be a simpler issue buried in the guts of JI.
          Hide
          Charles Oliver Nutter added a comment -

          Bumping...not going to make it in 1.1.4

          Show
          Charles Oliver Nutter added a comment - Bumping...not going to make it in 1.1.4
          Hide
          Charles Oliver Nutter added a comment -

          Ok, I finally managed to look at what's happening here. And I don't know how to fix it. Here's the sequence of events:

          1. new is called on the Ruby subclass of a Java type
          2. new for a Ruby/Java subclass does two things internally:
            1. first, construct the Ruby wrapper object; this calls initialize on the Ruby-land code. If super is called, the Java constructor also eventually gets invoked.
            2. then, if it hasn't already been constructed, construct the actual Java object by invoking its constructor
          3. After the Java object gets constructed, it is set into the Ruby wrapper, and the Ruby wrapper is set into it. This completes the construction process, enabling bidirectional calling to work correctly.

          The problem, I believe, is that because the constructor gets invoked before the wrapper/object association is "complete", the "early" constructor invocation ends up in Java-land trying to invoke a Ruby-implemented method without all the pieces in the right place. So if Ruby to Java to Ruby depends on the wrapper/object relationship already being set, and that relationship depends on the Java constructor completing, invoking Ruby-implemented methods from a Java constructor can't work right.

          At the moment I don't see that this is a reconcilable problem, because with two constructor chains one of them has to complete first, which means one side always has the potential to invoke methods before the other side is ready. Once we move JRuby toward passing Object everywhere we will be able to work around this (since the Ruby subclass will just be a Java subclass), but until then I think we're stuck.

          Workaround would be to provide an intermediate class that implements the abstract method...or don't call the abstract method in the constructor.

          Marking for 1.2, the current target for "big changes" in JRuby.

          Show
          Charles Oliver Nutter added a comment - Ok, I finally managed to look at what's happening here. And I don't know how to fix it. Here's the sequence of events: new is called on the Ruby subclass of a Java type new for a Ruby/Java subclass does two things internally: first, construct the Ruby wrapper object; this calls initialize on the Ruby-land code. If super is called, the Java constructor also eventually gets invoked. then, if it hasn't already been constructed, construct the actual Java object by invoking its constructor After the Java object gets constructed, it is set into the Ruby wrapper, and the Ruby wrapper is set into it. This completes the construction process, enabling bidirectional calling to work correctly. The problem, I believe, is that because the constructor gets invoked before the wrapper/object association is "complete", the "early" constructor invocation ends up in Java-land trying to invoke a Ruby-implemented method without all the pieces in the right place. So if Ruby to Java to Ruby depends on the wrapper/object relationship already being set, and that relationship depends on the Java constructor completing, invoking Ruby-implemented methods from a Java constructor can't work right. At the moment I don't see that this is a reconcilable problem, because with two constructor chains one of them has to complete first, which means one side always has the potential to invoke methods before the other side is ready. Once we move JRuby toward passing Object everywhere we will be able to work around this (since the Ruby subclass will just be a Java subclass), but until then I think we're stuck. Workaround would be to provide an intermediate class that implements the abstract method...or don't call the abstract method in the constructor. Marking for 1.2, the current target for "big changes" in JRuby.
          Hide
          Reto Schüttel added a comment -

          This is really bad, during constructions all overriden methods are simply ignored. Lets say you have:

          public abstract class AbstractBase {
              public AbstractBase() {
                  System.out.println(getJobName());
              }
          
              public String getJobName() {
                  return "default";
              }
          
          }
          

          Now if you subclass this class in ruby and override getJobName you'll get the base class behavior during construction, and then the new behavior later on:

          class Foo < AbstractBase
            def getJobName
              "baz"
            end
          end
          
          f = Foo.new
          # prints 'default'
          puts f.getJobName
          # prints 'baz'
          

          Thank you!

          Tested with jruby 1.6.4

          Show
          Reto Schüttel added a comment - This is really bad, during constructions all overriden methods are simply ignored. Lets say you have: public abstract class AbstractBase { public AbstractBase() { System .out.println(getJobName()); } public String getJobName() { return " default " ; } } Now if you subclass this class in ruby and override getJobName you'll get the base class behavior during construction, and then the new behavior later on: class Foo < AbstractBase def getJobName "baz" end end f = Foo. new # prints ' default ' puts f.getJobName # prints 'baz' Thank you! Tested with jruby 1.6.4
          Hide
          Reto Schüttel added a comment -

          (I'm really surprised that this doesn't cause more problems..., it isn't that unusual to call 'abstract' methods during construction, is it?)

          Show
          Reto Schüttel added a comment - (I'm really surprised that this doesn't cause more problems..., it isn't that unusual to call 'abstract' methods during construction, is it?)
          Hide
          Carlo Scarioni added a comment -

          Hi,

          Althought this is ok to be fixed, it is a said bad practice to call an overridable method from the constructor in Java, actually the book Java Puzzlers in the Puzzler 51 have this exact text on it: "To summarize, you must never call an overridable method from a constructor under any circumstances. The resulting circularities in instance initialization can be fatal".

          The problem is well explained in the puzzle, but the basic idea is that the overriden method gets called before the subclass constructor which can produce fatal errors.

          In the book Java Concurrency in Practice, there is also a hint that this is not good practice for multi-threaded programs either as it can let the "this" reference escape in a not fully constructed state yet.

          So, even if this is fixed, it is is not an encouraged practice to do so.

          Show
          Carlo Scarioni added a comment - Hi, Althought this is ok to be fixed, it is a said bad practice to call an overridable method from the constructor in Java, actually the book Java Puzzlers in the Puzzler 51 have this exact text on it: "To summarize, you must never call an overridable method from a constructor under any circumstances. The resulting circularities in instance initialization can be fatal". The problem is well explained in the puzzle, but the basic idea is that the overriden method gets called before the subclass constructor which can produce fatal errors. In the book Java Concurrency in Practice, there is also a hint that this is not good practice for multi-threaded programs either as it can let the "this" reference escape in a not fully constructed state yet. So, even if this is fixed, it is is not an encouraged practice to do so.
          Hide
          Roger Pack added a comment -

          NB if you get this method creating a JFrame, see https://github.com/jruby/jruby/issues/2243

          Show
          Roger Pack added a comment - NB if you get this method creating a JFrame, see https://github.com/jruby/jruby/issues/2243

            People

            • Assignee:
              Charles Oliver Nutter
              Reporter:
              Michael Bohn
            • Votes:
              1 Vote for this issue
              Watchers:
              6 Start watching this issue

              Dates

              • Created:
                Updated: