I've been toying with ASM, and I believe I succeeded in adding the final modifier to an instance field of a class; however I then proceeded to instantiate said class and invoke a setter on it, which successfully changed the value of the now-final field. Am I doing something wrong with my bytecode changes, or is final enforced only by the Java compiler?
Update: (31 Jul) Here's some code for you. The main parts are
- a simple POJO with a
private int x
andprivate final int y
, - the MakeFieldsFinalClassAdapter, which makes every field it visits final unless it already is,
- and the AddSetYMethodVisitor, which causes the setX() method of the POJO to also set y to the same value it set x to.
In other words, we start with a class with one final (x) and one non-final (y) field. We make x final. We make setX() set y in addition to setting x. We run. Both x and y get set with no errors. The code is on github. You can clone it with:
git clone git://github.com/zzantozz/testbed.git tmp
cd tmp/asm-playground
Two things of note: The reason I asked this question in the first place: both a field that I made final and a field that was already final are able to be set with what I believe to be normal bytecode instructions.
Another update: (1 Aug) Tested with both 1.6.0_26-b03 and 1.7.0-b147 with the same results. That is, the JVM happily modifies final fields at runtime.
Final(?) update: (19 Sep) I'm removing the full source from this post because it was rather lengthy, but it's still available on github (see above).
I believe I've conclusively proven the JDK7 JVM is in violation of the specification. (See the excerpt in Stephen's answer.) After using ASM to modify bytecode as described previously, I wrote it back out to a class file. Using the excellent JD-GUI, this class file decompiles to the following code:
package rds.asm;
import java.io.PrintStream;
public class TestPojo
{
private final int x;
private final int y;
public TestPojo(int x)
{
this.x = x;
this.y = 1;
}
public int getX() {
return this.x;
}
public void setX(int x) {
System.out.println("Inside setX()");
this.x = x; this.y = x;
}
public String toString()
{
return "TestPojo{x=" +
this.x +
", y=" + this.y +
'}';
}
public static void main(String[] args) {
TestPojo pojo = new TestPojo(10);
System.out.println(pojo);
pojo.setX(42);
System.out.println(pojo);
}
}
A brief glance at that should tell you that class will never compile due to reassigning a final field, and yet running that class in plain vanilla JDK 6 or 7 looks like this:
$ java rds.asm.TestPojo
TestPojo{x=10, y=1}
Inside setX()
TestPojo{x=42, y=42}
- Does anyone else have input before I report a bug on this?
- Can anyone confirm whether this should be a bug in JDK 6 or only in 7?