This is at least a flaw in the description of the verification
process as expressed in Chapter 4, Section 9 of the JVM
Specification, 2nd Edition. If there are any clarifications that I
have somehow missed, my apologies.
The check for final fields being set exactly once clearly should
belong to either pass 3 or pass 4, as the code array must be examined
to determine this property. The description of the final field in
Section 2.9.2 states that:
If a field declaration contains a variable initializer, then it has the
semantics of an assignment to the declared variable, and:
o If the declaration is for a class variable (that is a static field),
then the variable initializer is evaluated and the assignment performed
exactly once, when the class is initialized.
The problem here is that for the JVM, there is no explicit notion of
"variable initializer." Luckily, there is a single point where the
"once and only once" property for static fields can be checked, in
the code array of the "<clinit>" method. Since only one "<clinit>"
method is allowed, then the code array can be checked to insure that
over any path from start to end, there is exactly one definition of
of the final field. There is no indication of the need to ensure
this property in the text for pass 3 or pass 4.
It continues with the case of instance variables:
o If the declaration is for an instance variable (that is, a field that is
not static), then the variable initializer is evaluated and the
assignment performed each time an instance of the class is created.
The typical approach for insuring that instance variable
initialization takes place is to have the beginning of every
constructor "<init>" contain duplicate initialization code. If the
.class file is constructed this way, then the technique for checking
"<clinit>" applies also. However, I don't believe that
there is a requirement, either in the Java language specification or
in the JVM specifications, that requires that this technique be used.
From my reading of them, a Java compiler could place all
free-standing instance variable initializations into a private
instance method, and have each constructor call that method.
Yikes!
The verification code my student Alex Sotirov and I wrote for Kaffe
needs to be fixed, as we didn't check final field initializations at
all.
Joel Jones
At 4:12 PM -0400 9/30/03, Bill Pugh wrote:
>The rules on final fields being set exactly once are part of the
>JVM/classfile specification. A classfile in which a final field is
>written to outside the constructor (or either zero or more than once
>during construction) is an invalid classfile that should be rejected
>by the JVM with a verification error.
>
>So the example below would be rejected by the JVM if MyFieldJ was final.
>
>Bill
>
>
>At 3:12 PM -0400 9/30/03, Allan Kielstra wrote:
>>My colleague Andrew Johnson has observed that:
>> * the Java Language Specification ensures that final fields are only set
>>once, in the constructor.
>> * The byte code verifier just ensures that final fields are only set by
>>methods in the defining class.
>>
>>Depending on how pedantic one wants to be, this may limit the flexibility
>>of the Just-in-time compiler in making optimizations with final fields, as
>>there is nothing prohibiting such fields changing. Given a program such
>>as:
>>/**
>> * Test final fields
>> */
>>public class finalt1 {
>> /** private final field */
>> private final int MyFieldi;
>> /** private field - modify to be final using debugger */
>> private int MyFieldj;
>> public int get() {
>> return MyFieldi;
>> }
>> public int get1(int y) {
>> if (y > 0) return get1(y-1);
>> return MyFieldj;
>> }
>> /**
>> * Sets MyFieldj to y
>> */
>> public int set1(int y, int y2) {
>> if (y2 > 0) return set1(y, y2-1);
>> return MyFieldj = y;
>> }
>> /**
>> * constructs object and calls set1 to set MyFieldj
>> */
>> public finalt1(int z) {
>> if (z == Integer.MIN_VALUE) {
>> return;
>> }
>> MyFieldi = z;
>> set1(z, 0);
>> MyFieldj = 0;
>> }
>> public static void main(String[] args) {
>> finalt1 q = new finalt1(args.length+Integer.MIN_VALUE);
>> System.out.println("Object "+q+" MyFieldi "+q.get()+" MyFieldj
>>"+q.get1(0));
>> q.set1(2, 0);
>> System.out.println("Object "+q+" MyFieldi "+q.get()+" MyFieldj
>>"+q.get1(0));
>> int ss = 0;
>> for (int i = 0; i < 3000; ++i) {
>> ss += q.r(i);
>> }
>> System.out.println(""+q+" MyFieldi "+q.get()+" MyFieldj
>>"+q.get1(0)+" "+ss);
>> }
>> public int r(int t) {
>> int y = 0;
>> for (int i = 0; i < 100; ++i) {
>> set1(t, 0);
>> y += MyFieldj;
>> }
>> return y;
>> }
>>}
>>
>>If one were to use a binary editor to modify the resulting class file such
>>that MyFieldj appears to be final, we get a case where the compiler assumes
>>that the
>> "y += MyFieldj"
>>in the loop in method "r" can be statically computed (i.e., y +=
>>100*MyFieldj) despite the fact that the method "set1" changes the value of
>>the field.
>>
>>My feeling is that if you lie to the VM (by using a class file editor or
>>other chicanery) you get what you get. That is, a JIT (or anything else)
>>should be allowed to assume that you're telling the truth. Are there other
>>opinions?
>>
>>Andrew has also noted that the jikes compiler (an open source Java compiler
>>available here:
>>http://oss.software.ibm.com/developerworks/opensource/jikes/) handles
>>field initializers by putting all the code into a method called:
>>private void this()
>>which is then invoked at the start of each constructor.
>>
>>Unfortunately, one can re-run the "this" method at arbitrary program points
>>using reflection:
>>
>>import java.lang.reflect.*
>>class C {
>> static int StaticGlobal = 0;
>>
>> private final int MyFinalField = ++StaticGlobal; // this statement
>>will be placed in a method called "this"
>>
>> public void set() {
>> Class z = getClass();
>> try {
>> /* invoke the private void this() method from Jikes */
>> /* Cannot do it directly from java code */
>> Method m = z.getDeclaredMethod("this", new Class[]{});
>> m.invoke(this, new Object[0]);
>> } catch (Exception e) {
>> e.printStackTrace();
>> }
>> }
>> }
>>
>>Given an object "c" of class "C", the invocation "c.set()" will cause
>>"MyFinalField" to assume a new value. This is trickier because the cheat
>>can be performed with Java source code (instead of a class file editor).
>>In a slightly more perfect world, there would be a mechanism for a compiler
>>to encode rules in the class file which the VM would use for prohibiting
>>some operations that use reflection. In the meantime, I believe that this
>>is a cheat and a JIT or VM is allowed to assume that it doesn't happen.
>>Again, I am curious about other opinions. Are there any?
>>
>>
>>Allan Kielstra
>>IBM Canada Lab
>>Phone: +1 (905) 413-3558 T/L 969-3558
>>kielstra@ca.ibm.com
>>
>>-------------------------------
>>JavaMemoryModel mailing list - http://www.cs.umd.edu/~pugh/java/memoryModel
>
>-------------------------------
>JavaMemoryModel mailing list - http://www.cs.umd.edu/~pugh/java/memoryModel
-------------------------------
JavaMemoryModel mailing list - http://www.cs.umd.edu/~pugh/java/memoryModel
This archive was generated by hypermail 2b29 : Thu Oct 13 2005 - 07:00:51 EDT