How can I keep -Xcheckinit from interfering with the deserialization of Scala objects?

When using the -Xcheckinit compiler option and implementing my own readObject method in a serialized class, I cannot call any access functions in the fields declared in the body of my class from the readObject method. Fields declared as constructor arguments are fine. When I try to access a field declared in a class, I get scala.UninitializedFieldError.

That is, the following code does not work in println(y) in the readObject method, even after y was set in the previous line!

 @serializable case class XYPointWithRWAndPrint(var x: Int) { var y = 0 @throws(classOf[java.io.IOException]) private def writeObject(out: java.io.ObjectOutputStream) { out.writeInt(x) out.writeInt(y) } @throws(classOf[java.io.IOException]) @throws(classOf[ClassNotFoundException]) private def readObject(in: java.io.ObjectInputStream) { x = in.readInt() println(x) y = in.readInt() println(y) } } 

Why?

+4
source share
1 answer

Using the -Xcheckinit compiler option, the compiler creates a bitmap field that it uses to verify initialization.

 public volatile int bitmap$0; 

In accessories, the compiler checks the bitmap:

 public int y(){ if ((this.bitmap$0 & 0x1) != 0){ return this.y; } throw new UninitializedFieldError("Uninitialized field: Test.scala: 2".toString()); } 

In the constructor, the compiler updates the bitmap:

 public XYPointWithRW(int x) { Product.class.$init$(this); this.y = 0; this.bitmap$0 |= 1; } 

Note that it does not update the bitmap for constructor arguments, only for fields declared in the class. He does this because he assumes that you are calling the constructor and that these fields will be initialized immediately.

However, when deserializing, the no-arg constructor of the first non-serializable superclass is called (Object in this case) instead of the one-arg constructor shown above. Then readObject is called. The above constructor is never called. Therefore, the bitmap is never updated. An accessory call would not succeed anywhere in its called, and not just in the readObject method.

To get around this, you must update the bitmap manually. I decided to do this from the readObject method. I set all the bits in the bitmap to 1, for example:

 getClass.getField("bitmap$0").set(this, -1) 

By setting all the bits to 1, it will work for all fields (up to 32 fields anyway ... what happens further than possible).

+3
source

All Articles