Search code examples
scalainitializationlazy-evaluation

Initialisation order throws null pointer on lazy val access


Expectedly, the following initialisation order without lazy val throws null pointer exception

class Foo {
  Bar.x // NullPointerException
}

object Bar extends Foo {
  val x = 42
}

object Hello extends App {
  Bar
}

Examining -Xprint:jvm output, and referencing @paradigmatic answer, we see this is due to Foo's constructor running first and calling Bar.x() before Bar.this.x is initialised in Bar's constructor:

  class Foo extends Object {
    def <init>(): example.Foo = {
      Foo.super.<init>();
      Bar.x();
      ()
    }
  };

  object Bar extends example.Foo {
    private[this] val x: Int = _;
    <stable> <accessor> def x(): Int = Bar.this.x;
    def <init>(): example.Bar.type = {
      Bar.super.<init>();
      Bar.this.x = 42;
      ()
    }
  };

However, why is null pointer also thrown when x is lazy like so

object Bar extends Foo {
  lazy val x = 42
}

Analysing -Xprint:jvm output in lazy case we have

  class Foo extends Object {
    def <init>(): example.Foo = {
      Foo.super.<init>();
      Bar.x();
      ()
    }
  };
  object Bar extends example.Foo {
    final <synthetic> lazy private[this] var x: Int = _;
    @volatile private[this] var bitmap$0: Boolean = _;
    private def x$lzycompute(): Int = {
      Bar.this.synchronized(if (Bar.this.bitmap$0.unary_!())
        {
          Bar.this.x = (42: Int);
          Bar.this.bitmap$0 = true
        });
      Bar.this.x
    };
    <stable> <accessor> lazy def x(): Int = if (Bar.this.bitmap$0.unary_!())
      Bar.this.x$lzycompute()
    else
      Bar.this.x;
    def <init>(): example.Bar.type = {
      Bar.super.<init>();
      ()
    }
  };

where it seems to me it should work due to the bitmap$0 guard

    <stable> <accessor> lazy def x(): Int = if (Bar.this.bitmap$0.unary_!())
      Bar.this.x$lzycompute()
    else
      Bar.this.x;

Runtime field accessors check -Xcheckinit seems to be satisfied on my machine with Scala 2.12.8, so why NullPointerException when lazy val x?


Solution

  • I don't think this NPE is related to val at all. Check this:

    class Foo {
      Bar.anyMethod
    }
    
    object Bar extends Foo {
      def anyMethod = ???
    }
    
    object Hello extends App {
      Bar
    }
    
    //java.lang.NullPointerException
    

    Foo is trying to run constructor on Bar while Bar is still under construction. So that's what your Foo is doing too before calling x.

    Btw if you put everything into Hello with main method you will get StackOverflow instead of NPE in both mine and your cases.

    object Hello {
    
       def main(args: Array[String]): Unit = {
    
         class Foo {
           Bar.anyMethod
         }
    
         object Bar extends Foo { //<- Bar is like local val now instead of field 
           def anyMethod= ???     // of package object, so stack is available now.
         }
    
         Bar
       }
    
    }