Alan Macek pointed
out an interesting bug in the verifier.
First, some background. Let's look at this code fragment:
class Test
{
Test()
{
new Runnable()
{
public void run()
{}
};
}
public static void main(String[]
args) {
new Test();
}
}
Inner classes aren't supported by the VM, it's a compiler fiction, so when you compile
the above code you get the equivalent of the following:
class Test
{
Test()
{
new Test$1(this);
}
public static void main(String[]
args) {
new Test();
}
}
class Test$1 implements Runnable
{
private Test
outer;
Test$1(Test
outer) {
this.outer
= outer;
}
public void run()
{}
}
The interesting bit here is that the outer field is initialized after the base class
constructor runs. In this case the base class is Object so this isn't significant,
but when you have a base class that calls virtual methods it is:
abstract class Base
{
Base()
{
run();
}
public abstract void run();
}
class Test
{
Test()
{
new Base()
{
public void run()
{
System.out.println(Test.this);
}
};
}
public static void main(String[]
args) {
new Test();
}
}
When you compile this and run it, it prints out: null. The outer this reference is
not yet initialized when the Base constructor runs.
However, when you compile it with the -target 1.4 option, it prints out: Test@17182c1
(or something similar). So clearly an interesting change was made to the compiler.
Let's take a look at the bytecode of the constructor of the original code fragment:
<init>(LTest;)V
0 aload_0
1 invokespecial java/lang/Object/<init>()V
4 aload_0
5 aload_1
6 putfield Test$1/this$0 Test
9 return
First, the base class constructor is called and then the this$0 field is initialized.
Now take a look at the same method, but now as compiled with the -target 1.4 option:
<init>(LTest;)V
0 aload_0
1 aload_1
2 putfield Test$1/this$0 Test
5 aload_0
6 invokespecial java/lang/Object/<init>()V
9 return
The order of the two steps is reversed. Now it first initializes the this$0 field
and then calls the base class constructor.
I had missed the fact that it is legal to initialize fields in an unitialized object.
So when the latter code was run, the verifier compiled that an uninitialized object
reference was used.
The big question is why the compiler only does this when the -target 1.4 option
is specified. This rule of allowing instance fields of unitialized object to be written
has always been in the JVM specification, as far as I can tell.
New Snapshots
Friday, I put up new snapshots. Source
and binaries and binaries only.
Changes since the last (on the blog announced) snapshot:
-
New version of netexp based on Java reflection (all .NET to Java mapping is now in
the IKVM reflection runtime).
-
To support netexp, the .NET type reflection is now much better. This includes support
for delegates and byref arguments. Instead of lower casing the .NET namespaces, all
.NET types are now prefixed with the "cli." package name. Remapped this are also available
and appear as final classes without constructors, but with static methods for all
instance methods (taking the remapped type as the first argument). Constructors are
available as static __new methods.
-
Bootstrap class loader is now a flat type space. All loaded assemblies are searched
for bootstrap types when Class.forName is used.
-
Enums are now treated as other value types (i.e. boxed). This allows better accesses
to overloaded .NET methods.
-
Lots of clean up and refactoring to support the new reflection capabilities.
-
Various bug fixes.
-
Updated to work with latest GNU Classpath CVS.