Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is "final" final at runtime?

Tags:

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

  1. a simple POJO with a private int x and private final int y,
  2. the MakeFieldsFinalClassAdapter, which makes every field it visits final unless it already is,
  3. 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} 
  1. Does anyone else have input before I report a bug on this?
  2. Can anyone confirm whether this should be a bug in JDK 6 or only in 7?
like image 581
Ryan Stewart Avatar asked Jul 30 '11 05:07

Ryan Stewart


People also ask

What is known as final variable?

A final variable is called a blank final variable if it is not initialized while declaration. Below are the two ways to initialize a blank final variable. A blank final variable can be initialized inside an instance-initializer block or inside the constructor.

What is final final and final variable method?

If we initialize a variable with the final keyword, then we cannot modify its value. If we declare a method as final, then it cannot be overridden by any subclasses. And, if we declare a class as final, we restrict the other classes to inherit or extend it.

Is final faster Java?

In general, final methods are faster than non-final methods because they are not required to be resolved during run-time and they are bonded at compile-time, See these Java Performance courses for more details.

When should variable be final?

4. Effectively Final. The term effectively final variable was introduced in Java 8. A variable is effectively final if it isn't explicitly declared final but its value is never changed after initialization.


1 Answers

Is “final” final at runtime?

Not in the sense you mean.

AFAIK, the semantics of the final modifier are only enforced by the bytecode compiler.

There are no special bytecodes for initializing final fields, and the bytecode verifier (apparently) doesn't check for "illegal" assignments either.

However, the JIT compiler might treat the final modifier as a hint that things don't need to be refetched. So, if your bytecodes modify a variable marked as final you are liable to cause unpredictable behavior. (And the same thing can happen if you use reflection to modify a final variable. The spec clearly says so ...)

And, of course, you can modify a final field using reflection.


UPDATE

I took a look at the Java 7 JVM spec, and it partly contradicts what I said above. Specifically, the description of the PutField opcode says:

"Linking Exceptions ... Otherwise, if the field is final, it must be declared in the current class, and the instruction must occur in an instance initialization method (<init>) of the current class. Otherwise, an IllegalAccessError is thrown.".

So, while you could (in theory) assign to a final field multiple times in the object's constructor, the bytecode verifier should prevent any attempt to load a method that contains bytecode that assign to a final. Which ... when you think about Java security sandboxes ... is a good thing.

like image 96
Stephen C Avatar answered Sep 23 '22 07:09

Stephen C