Hi Jan,
Once again, I'm using EqualsVerifier in a Scala project. I recently wanted to enable the
scalac -Xcheckinit flag in a test build to ensure I wasn't accessing any fields before they were initialized. Unfortunately, once I enabled this flag, many of my EqualsVerifier tests fail with a root cause of
scala.UninitializedFieldError.
Below is an MCVE that reproduces this problem:
// build.sbtlazy val root = (project in file("."))
.settings(
name := "equalsverifier-checkinit",
version := "1.0",
scalaVersion := "2.11.7",
libraryDependencies ++= Seq(
"nl.jqno.equalsverifier" % "equalsverifier" % "1.7.5"
),
scalacOptions ++= Seq(
"-Xcheckinit"
)
)
// project/build.properties// src/main/scala/Foo.scalaimport nl.jqno.equalsverifier._
final class Foo(constant: Double) {
override def equals(other: Any): Boolean = other match {
case that: Foo => value.compareTo(that.value) == 0
case _ => false
}
override def hashCode(): Int = value.hashCode()
val value: Double = constant
}
object Foo {
def main(args: Array[String]): Unit = {
EqualsVerifier.forClass(classOf[Foo])
.suppress(Warning.NULL_FIELDS)
.verify()
println("OK")
}
}
Running "sbt run" for the above project produces an exception with the following stack trace:
java.lang.AssertionError: scala.UninitializedFieldError: Uninitialized field: Foo.scala: 11
For more information, go to: http://www.jqno.nl/equalsverifier/errormessages
at nl.jqno.equalsverifier.EqualsVerifier.handleError(EqualsVerifier.java:406)
at nl.jqno.equalsverifier.EqualsVerifier.verify(EqualsVerifier.java:392)
at Foo$.main(Foo.scala:18)
at Foo.main(Foo.scala)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
Caused by: scala.UninitializedFieldError: Uninitialized field: Foo.scala: 11
at Foo.value(Foo.scala:11)
at Foo.equals(Foo.scala:5)
at nl.jqno.equalsverifier.util.Assert.assertEquals(Assert.java:42)
at nl.jqno.equalsverifier.ExamplesChecker.checkReflexivity(ExamplesChecker.java:81)
at nl.jqno.equalsverifier.ExamplesChecker.checkSingle(ExamplesChecker.java:74)
at nl.jqno.equalsverifier.ExamplesChecker.check(ExamplesChecker.java:58)
at nl.jqno.equalsverifier.EqualsVerifier.verifyWithExamples(EqualsVerifier.java:450)
at nl.jqno.equalsverifier.EqualsVerifier.performVerification(EqualsVerifier.java:418)
at nl.jqno.equalsverifier.EqualsVerifier.verify(EqualsVerifier.java:386)
at Foo$.main(Foo.scala:18)
at Foo.main(Foo.scala)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)If you disassemble the
Foo.class file, you'll see that
scalac adds the following field to the
Foo class when
-Xcheckinit is enabled:
private volatile boolean bitmap$init$0;
This field is apparently used to track whether the
value field has been initialized. My guess is that EqualsVerifier is modifying
bitmap$init$0 as part of permuting the class fields during the equality tests, and one of those permutations is setting it to
false, thus causing the
UninitializedFieldError when the
value field is accessed.
I don't believe this is an EqualsVerifier problem, as I wouldn't expect it to guess which fields are added by the compiler or any third-party bytecode enhancer. (It probably would work as expected if
scalac marked this field
transient, right?) But I was wondering if you had any ideas for how I can work around this using the EqualsVerifier API so I don't have to wade through a slew of false positive
UninitializedFieldErrors looking for any real problems.
Thanks,
Steve