Java bytecode final field assignment (jasmin)

1.5k views Asked by At

In the java language, a final field takes an immediate value at initialisation and cannot be changed anymore. In java bytecode (jasmin), if I make a final field, it ignores the immediate value I assigned it at initialisation and I can change it like any other variable later on.

Ex. java code :

public class App{
    final int CONST = 2;
    App(){
        CONST = 3;
    }
    public static void main(String[] args){
        App app = new App();
    }
}

Output:

App.java:4 error: cannot assign a value to final variable CONST

Ex. jasmin bytecode :

.class App
.super java/lang/Object

.field private final CONST I = 2 ;!!! the immediate value is ignored, 0 assigned

.method public <init>()V
    .limit stack 3
    .limit locals 1
    aload_0
    invokespecial java/lang/Object/<init>()V

    aload_0
    bipush 3
    putfield App/CONST I ;!!! overwritting final field

    return
.end method

.method public static main([Ljava/lang/String;)V
    .limit stack 1
    .limit locals 1

    new App
    invokespecial App/<init>()V

    return
.end method

Output:

Generated: App.class

No error? I also tested printing out the new CONST value and it works just like a normal variable. Why isn't the final field working like it does in java code?

1

There are 1 answers

4
Antimony On BEST ANSWER

The Java language enforces a lot of constraints that are not enforced at the bytecode level. One of them is handling of final fields.

In Java bytecode, the only constraints on final fields are that static final fields cannot be assigned outside the <clinit> method and nonstatic final fields cannot be assigned outside the <init> methods (i.e constructors).

You can assign to a final field 0, 1, 2 or any number of times. If you chain constructors, you can assign to it in one constructor and then overwrite it in a different constructor.

To find out more about how bytecode works, you should read the JVM Specification.

On a side note, the following are completely different things, despite the deceptively similar syntax.

final int CONST = 2;

.field private final CONST I = 2 ;!!! the immediate value is ignored, 0 assigned

In the first example, this is a (nonstatic) initializer. The body of all initializers is effectively just copypasted into each of your constructors. So you get the same effect as if you had written CONST = 2 in the constructor.

By contrast, the Jasmin syntax is creating a ConstantValue attribute. This is normally used to give an initial value for static final fields. It can be specified for any field but is ignored for non-static fields, hence why you see the value being ignored.