benf.org : other : cfr : Java 1.4 class constants. |
(Thanks to Jarrko for pointing out this oddity)
As of 0_65, CFR correctly resugars Java 1.4 class constants - the approach for class constants seems to have changed considerably between 1.4 and 1.5 - here's some example code
public class Test { private final Class c; public Test(Class c) { this.c = c; } public static Test instance = new Test(String.class); }
Nice and simple, yes? Let's compile this up in javac 1.8, and look at the generated bytecode for the static initialiser..
static {}; Code: 0: new #3 // class cfrtest/Test 3: dup 4: ldc_w #4 // class java/lang/String 7: invokespecial #5 // Method "<init>":(Ljava/lang/Class;)V 10: putstatic #6 // Field instance:Lcfrtest/Test; 13: return
Yes, simple! But what do we get if we compile it up in javac 1.4?...
static {}; Code: 0: new #8 // class cfrtest/Test 3: dup 4: getstatic #9 // Field class$java$lang$String:Ljava/lang/Class; 7: ifnonnull 22 10: ldc #10 // String java.lang.String 12: invokestatic #11 // Method class$:(Ljava/lang/String;)Ljava/lang/Class; 15: dup 16: putstatic #9 // Field class$java$lang$String:Ljava/lang/Class; 19: goto 25 22: getstatic #9 // Field class$java$lang$String:Ljava/lang/Class; 25: invokespecial #12 // Method "<init>":(Ljava/lang/Class;)V 28: putstatic #13 // Field instance:Lcfrtest/Test; 31: return
That's a bit more complex! At this point, it might be easier to look at CFR's decompilation (in 0_65+ you'll need to specify --j14classobj false to turn resugaring OFF.
public class Test { private final Class c; public static Test instance = new Test(Test.class$java$lang$String == null ? (Test.class$java$lang$String = Test.class$("java.lang.String")) : Test.class$java$lang$String); static /* synthetic */ Class class$java$lang$String; public Test(Class class_) { this.c = class_; } static /* synthetic */ Class class$(String string) { try { return Class.forName(string); } catch (ClassNotFoundException var1_1) { throw new NoClassDefFoundError().initCause((Throwable)var1_1); } } }
That's interesting! Rather than a simple ldc_w of a ConstantClass, a synthetic method "class$" is used to explicitly call Class.forName - a static Class variable is used to cache the value of this class, and it's initialised with a ternary check everywhere it's used! It's also annoying that this won't quite compile, as, while NoClassDefFoundError is not checked, its 'initCause' method returns a checked Throwable.
It appears that this was done because earlier jvms didn't support ldc_w against class constants - the draft of the old 'java virtual machine book' says "the following java types can be pushed using ldc_w [ int float string ]", as compared to the current spec, which is much more flexible.
This isn't mentioned very much on the web - the only useful link apparent is on A. Sundararajan's oracle blog from 2006
public class Test { private final Class c; public static Test instance = new Test(String.class); public Test(Class class_) { this.c = class_; } }
03/2014 - Thanks to Michael Schierl for pointing out mistakes and filling in the blanks in my original.
Last updated 03/2014 |