Saturday, January 26, 2008

Circumventing Java's Initialization Process

This is really not a circumvention per se, but rather an understanding of how the Java initialization behaviour works, and highlight the violation of the JVM specification, even in Sun's version, so that Java will be able to function pragmatically. The JVM specification (§2.17.4) indicates that:
A class or interface type T will be initialized immediately before one of the following occurs: * T is a class and an instance of T is created. [...]
This means that if object T is being created, then the class of T will have to be initialized. But is it always being upheld? For a fact, it's never upheld in a cyclic self-referencing case:


public class T {
int value;
T my_object;

static {
my_object = new T();
my_object.print();
value = 1;
}

public void print() {
System.out.println("The value is " + value);
}
}


If you ran the application, even after the object for T is being created, the value returned is '0' rather than '1' which violates the definition as described above. If this rule is not violated, if I am correct, on older JVMs like 1.0, this will translate into a cyclic situation, as the object creation will trigger it's object constructor, which will trigger the class constructor, which it is already in, and so on.

So what's the problem with that? The reason why initialization is important, is so that you'll expect a reliable value to be set by the time you use the class/object, which may not be necessarily be the case if that constraint is relaxed. Consider a fringe example like this:



// ---- DualWait1.java -----

public class DualWait1 {
public static int value;
static DualWait2 field;

static {
System.out.println("DualWait1.clinit() invoked");
try { Thread.sleep(1000); }
catch (Exception e) { throw new AssertionError(e); }

System.out.println("DualWait1.clinit(): creating a new DualWait2() object");
field = new DualWait2(2);
System.out.println("DualWait1.clinit(): setting value to 1");
value = 1;
System.out.println("DualWait1.clinit(): DualWait2.value="+DualWait2.value);
}

public DualWait1(int id) {
System.out.println("DualWait1: constructor called id["+id+"]");
}
}

// ---- DualWait2.java -----

public class DualWait2 {
public static int value;
static DualWait1 field;

static {
System.out.println("DualWait2.clinit() invoked");
try { Thread.sleep(1000); }
catch (Exception e) { throw new AssertionError(e); }

System.out.println("DualWait2.clinit(): creating a new DualWait1() object");
field = new DualWait1(1);
System.out.println("DualWait2.clinit(): done creating a DualWait1 object");
System.out.println("DualWait2.clinit(): setting value to 1");
value = 1;
System.out.println("DualWait2.clinit(): DualWait1.value="+DualWait1.value);
}

/** Only for a placeholder. */
public static void main(String args[]) {
System.out.println("main(): DualWait1.field = " + DualWait1.field);
System.out.println("main(): DualWait2.field = " + DualWait2.field);
}

public DualWait2(int id) {
System.out.println("DualWait2: constructor called, id["+id+"]");
}

}


Compile the two classfiles separately and once you've done so, you'll get the following output after execution:


DualWait2.clinit() invoked
DualWait2.clinit(): creating a new DualWait1() object
DualWait1.clinit() invoked
DualWait1.clinit(): creating a new DualWait2() object
DualWait2: constructor called, id[2]
DualWait1.clinit(): setting value to 1
DualWait1.clinit(): DualWait2.value=0
DualWait1: constructor called id[1]
DualWait2.clinit(): done creating a DualWait1 object
DualWait2.clinit(): setting value to 1
DualWait2.clinit(): DualWait1.value=1
main(): DualWait1.field = DualWait2@15ff48b
main(): DualWait2.field = DualWait1@affc70


The line in red is where the problem lies. At DualWait1's static constructor, we see that the value for DualWait2's value field is still not initialized, even after a DualWait2 object has already been constructed.

Normally this shouldn't be a problem for normal functioning cases, given that no coder in his right mind would write code like this to trigger the anomaly, so it's more of a curiosity than anything really useful. It's just useful to note, that specifications, while good-intentioned, may sometimes turn out to be impossible to fulfill anyway.

0 comments:

Post a Comment