Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Uninitialized fields inside an Object

I am currently fixing a very odd error where a private final val inside an object fields aren't initialised before they are accessed. The location of the code can be found at https://github.com/mdedetrich/soda-time/blob/master/jvm/src/main/scala/org/joda/time/chrono/GregorianChronology.scala#L12-L33.

You can simulate this error by pulling the above repo and then running sodatimeJVM/console and then in console running ` import org.joda.time._; DateTime.now().minusDays(10)

The code has been posted here

object GregorianChronology {

  private final val MILLIS_PER_YEAR = (365.2425 * DateTimeConstants.MILLIS_PER_DAY).toLong
  private final val MILLIS_PER_MONTH = (365.2425 * DateTimeConstants.MILLIS_PER_DAY / 12).toLong
  private final val DAYS_0000_TO_1970 = 719527
  private final val MIN_YEAR = -292275054
  private final val MAX_YEAR = 292278993
  private final val INSTANCE_UTC = getInstance(DateTimeZone.UTC)

  private final val cCache = new ConcurrentHashMap[DateTimeZone, Array[GregorianChronology]]()

  def getInstanceUTC(): GregorianChronology = INSTANCE_UTC

  def getInstance(): GregorianChronology = getInstance(DateTimeZone.getDefault, 4)

  def getInstance(zone: DateTimeZone): GregorianChronology = getInstance(zone, 4)

  def getInstance(zone: DateTimeZone, minDaysInFirstWeek: Int): GregorianChronology = {
    var _zone: DateTimeZone = zone
    if (_zone == null) {
      _zone = DateTimeZone.getDefault
    }
    var chrono: GregorianChronology = null
    var chronos: Array[GregorianChronology] = cCache.get(_zone)

The last line, i.e. var chronos: Array[GregorianChronology] = cCache.get(_zone) throws a java.lang.NullPointerException. The value that is null is cCache however this doesn't make sense since its clearly being initialized at private final val cCache = new ConcurrentHashMap[DateTimeZone, Array[GregorianChronology]](). If I turn on "-Xcheckinit" Scala then tells me scala.UninitializedFieldError: Uninitialized field: GregorianChronology.scala: 19 which points to private final val cCache = new ConcurrentHashMap[DateTimeZone, Array[GregorianChronology]](). This isn't very useful as I know that the value isn't initialized, the problem is that I don't know why. Since its a final val I assume it should be one of the first values that are initialized, especially before getInstance ever happens to be called.

I know that I can make the value lazy to fix it, that would however introduce an unneeded performance hit. More importantly though, the equivalent Java version private static final ConcurrentHashMap<DateTimeZone, GregorianChronology[]> cCache = new ConcurrentHashMap<DateTimeZone, GregorianChronology[]>() works absolutely fine.

like image 529
mdedetrich Avatar asked Apr 24 '16 15:04

mdedetrich


People also ask

Is an uninitialized variable null?

Null serves as the default value of any uninitialized reference variable, including instance variables and static variables (although you will still receive a compiler warning for uninitialized local variables).

Are objects by default null?

In Java, class and instance variables assume a default value (null, 0, false) if they are not initialized manually. However, local variables aren't given a default value. You can declare but not use an uninitialised local variable. In Java, the default value of any object is null.

How do you initialize null?

A pointer can also be initialized to null using any integer constant expression that evaluates to 0, for example char *a=0; . Such a pointer is a null pointer. It does not point to any object.

How do you initialize a null object in Java?

You should initialize your variables at the top of the class or withing a method if it is a method-local variable. You can initialize to null if you expect to have a setter method called to initialize a reference from another class. You can, but IMO you should not do that. Instead, initialize with a non-null value.


1 Answers

The problem is here:

private final val INSTANCE_UTC = getInstance(DateTimeZone.UTC)

It calls:

def getInstance(zone: DateTimeZone): GregorianChronology = getInstance(zone, 4)

Which calls:

def getInstance(zone: DateTimeZone, minDaysInFirstWeek: Int): GregorianChronology = {
  ..
  var chronos: Array[GregorianChronology] = cCache.get(_zone)
  ..
}

But INSTANCE_UTC is still being initialized, which means we haven't reached cCache in the initialization order, so cCache is null at that point at run time.

This is similar to:

object Test {
    val a = foo("a") // Calls a def which references and uses an uninitialized val, NPE
    val b = "b"
    def foo(c: String): Int = b.length + c.length
}

The solution is simple though, just move the initialization of cCache to the top of the object, since it doesn't reference anything else. That way it will always be initialized first.

like image 73
Michael Zajac Avatar answered Sep 26 '22 13:09

Michael Zajac