JavaMemoryModel: Final fields and deserialization

From: Bill Pugh (pugh@cs.umd.edu)
Date: Tue May 11 2004 - 11:20:35 EDT


Previously, we had made some fields final, such as fields of the String
and Integer classes,
so that these classes would be truly immutable, even if references to
them were passed between
threads via data races.

This turns out to have caused some critical applications to fail. These
applications
were using their own deserialization mechanisms, and they were changing
fields via
reflection. With the additional fields being final, the applications no
longer worked.

These applications in are the category of applications that must work
in order for
Sun to consider the JVM acceptable.

So the solution that has been decided on is that if you call
setAccessible on
a java.lang.reflect.Field object, this will allow you not only to
change private
fields of other classes, but also to change final fields. Note that
special security
permissions are required to call setAccessible, and if you have these
permissions you
can also remove or change the SecurityManager, so there isn't a big new
security hole
created by this.

At this point, it is about 99.99% certain that this is the solution
that will be used,
although there is a slight chance it will make it into the release
candidate rather
than beta2.

This is probably for the best. When people start using final fields
more widely,
it was going to become impossible for people to writing deserialization
code without
using special magic like Unsafe.

We are making some slight adjustments to the details of changing final
fields in
the formal semantics, so that normally written deserialization code
will (almost always)
just work. The semantics are only designed to handle the case where the
final fields
are modified via reflection before the field is read. There is one case
that is
problematic:

class A {
   final int x;
   A() {
     x = 1;
   }
   int f() {
     return d(this,this);
   }
   int d(A a1, A a2) {
     int i = a1.x;
     g(a1);
     int j = a2.x;
     return j - i;
   }
   static void g(A a) {
     // uses reflection to change a.x to 2
   }
}

We want to allow compilers to reorder reads of final fields across
unknown method calls.
Thus, the read of a1.x can be moved to after the call to g(a1), and the
read of a2.x can
be moved above the call to g(a1). As a result, f() can return either
-1, 0 or 1.

To guarantee that this cannot happen, we allow a block of code to be
performed in a
final field safe context (probably by giving a runnable to some static
executor method,
or by using an annotation on a method).

The semantics of this are that if an object is initialized and then the
final field
modified within the final field safe context, reads of the final field
that occur
after the final field safe context are guaranteed to see the correct
value. More
generally, reads of final fields may not be moved into or out of a
final field safe context,
although they may be moved across it (thus, if g used a final field
safe context above,
the anomalous behavior could still occur).

We can also use a final field safe context in something such as an
executor or thread pool,
to ensure that if one runnable uses an incorrectly published reference
to an object,
a subsequent runnable can use a correctly published reference and be
guaranteed to
see the correct values for the final fields of the object.

Now, although the spec will describe it, the 1.5 Java API won't provide
any way to
obtain a final field safe context. I believe we will get this
introduced in the 1.5.1
API. Aggressive optimization of final fields probably won't come until
1.5.1 anyway,
so this should be OK.

Note also that changing a final field initialized to a compile time
constant
is highly questionable, since the value is substituted for uses of the
field by
the compiler. It is allowed, but any use is allowed to see the compile
time constant
value.

So the decision to allow reflection to change final fields, assuming
the appropriate
security permissions, is pretty much a done deal. There really wasn't
much time to
debate it, sorry about that.

However, we are open to feedback on the formal semantics of doing so.

Previously we described a method realloc method that needed to be used
when
modifying final fields. After thinking about it, we decided that this
was unlikely
to be something that could be easily retrofitted to most
deserialization techniques.
Instead, we are tweak the semantics so that deserialization code
written to handle
non-final fields will also work for final fields, with a requirement
for using this new "final field safe context" to absolutely guarantee
that the
compiler won't reorder a read of a final field and a reflective change
to a final
field within a thread.

Bill

-------------------------------
JavaMemoryModel mailing list - http://www.cs.umd.edu/~pugh/java/memoryModel



This archive was generated by hypermail 2b29 : Thu Oct 13 2005 - 07:01:06 EDT