Community
Participate
Working Groups
Java 1.5 beta reports a ClassFormatError when running programs (e.g., spacewar) compiled by ajc 1.1.1 and the latest CVS head. Sun's Java 1.5 beta binary-compatibility docs say that some obfuscators violated the .class format specification, so those .class files will fail when run under 1.5. (The docs also say they are still incomplete.) Although 1.5 is still beta, we would want to submit a bug to Sun if our implementation techniques are valid, so we don't have to change those techniques. For 1.2, we should at document if we don't fix, since many people are using 1.5.
P2/1.2: Recommending for investigation before the 1.2 release, at least to document the results.
Created attachment 9217 [details] HelloWorld + logging Reproduce problem: java version "1.5.0-beta" Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0-beta-b32c) Java HotSpot(TM) Client VM (build 1.5.0-beta-b32c, mixed mode) java.lang.ClassFormatError: Invalid index 0 in LocalVariableTable in class file ras/HelloWorldLogging at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:604) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:123) at java.net.URLClassLoader.defineClass(URLClassLoader.java:260) at java.net.URLClassLoader.access+100(URLClassLoader.java:56) at java.net.URLClassLoader+1.run(URLClassLoader.java:195) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:188) at java.lang.ClassLoader.loadClass(ClassLoader.java:289) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:279) at java.lang.ClassLoader.loadClass(ClassLoader.java:235) at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:302) Exception in thread "main"
Here is a simple test program that causes the problem: public class H { public static void main(String[] argv) { } } aspect A { before(): execution(* *(..)) { } } Now, I'm not saying this is the actual problem but there have always been issues with 'decompiling' the aspectOf() method. Running jad against the A.class file produced after compiling the above code, jad says it has problems - it can't understand the aspectOf() method. And indeed the gumpf that is produced looks like this on decompilation: public static A aspectOf() { ajc$perSingletonInstance; if(ajc$perSingletonInstance == null) goto _L2; else goto _L1 _L1: return; _L2: throw new NoAspectBoundException("A", ajc$initFailureCause); } When it perhaps ought to read public static A aspectOf() { if(ajc$perSingletonInstance != null) return ajc$perSingletonInstance; throw new NoAspectBoundException("A", ajc$initFailureCause); } It could be that JVM 1.5 is being more strict in what it allows in - I'm not sure how to find out if thats actually whats happening !
I've checked in a fix for this. It works for my testcase and it passes all the tests I can find. I will write it up when I get back from my run.
Ok, here is the full gory details - mostly pasting bits and pieces in from a discussion I had with Jim. Prior to the fix I put in, anyone who tried to run 'javap -verbose' against an aspect might have noticed something unusual. Here is a snippet of javap - verbose for the aspect 'A' described earlier in this bug report: public void ajc$before$A$50(); org.aspectj.weaver.Advice: length = 0x45 01 01 03 01 00 00 00 00 05 05 00 01 2A 00 01 04 FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00 00 FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00 00 FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00 00 00 64 00 00 00 6A 00 00 00 5A 00 00 00 6B 00 00 00 00 50 00 00 00 6B Code: Stack=0, Locals=1, Args_size=1 0: return LocalVariableTable: Start Length Slot Name Signature 0 0 0 this LA; LineNumberTable: line 7: 0 public static A aspectOf(); org.aspectj.weaver.AjSynthetic: length = 0x Code: Stack=3, Locals=0, Args_size=0 0: getstatic #25; //Field ajc$perSingletonInstance:LA; 3: dup 4: ifnull 8 7: areturn 8: new #27; //class NoAspectBoundException 11: dup 12: invokespecial #28; //Method org/aspectj/lang/NoAspectBoundException."< init>":()V 15: athrow LocalVariableTable: Start Length Slot Name Signature 0 16 0 this LA; I have included the generated advice and the generated aspectOf() method. Observe that the aspectOf method - a *static* method has a local variable table with an entry in for 'this'... This is wrong - we should have no local variable table at all for this aspectOf method (other static methods generated in the aspect exhibit the same problem). The 1.5 beta appears to be policing this kind of error situation where previous JDKs don't seem to have been checking. SUN might remove this agressive checking before 1.5 final ships - but clearly AspectJ is generated incorrect information here. So how do we fix it? Where did the local variable table come from? The answer is that in AspectDeclaration.generateMethod() where we are generating the code for static methods like aspectOf(), hasAspect(), etc - we are using the codeStream object incorrectly. We are calling codeStream.init() rather than codeStream.reset(). Not using reset() means that we don't alter the method declaration object that the code stream thinks it is working with. This means that when we ask the classfile to fix up its code attributes (around line AspectDeclaration line 306 we call classFile.completeCodeAttribute ()) the classfile asks the methoddeclaration for the codestream whether it is static - based on the result of this check, it generates a local variable table entry for 'this' (See line 1413 in ClassFile). Because we never updated the codestream with a new method declaration, it still points to the method declaration for our previous created method - the advice itself (public void ajc$before$A$50();) - this was *not* static and so we end up creating a 'this' local variable entry for all the subsequent generated methods in the aspect. The fix is to replace these calls to the codeStream: codeStream.init(classFile); codeStream.initializeMaxLocals(methodBinding); with codeStream.reset(md,classFile); // md is a method declaration object representing our static method (reset calls init and initializeMaxLocals and correctly updates the method declaration in the codestream). So far, that all seems the perfect fix. Unfortunately there is one twist. Constructing a methoddeclaration of quite the right type to satisfy the codestream is awkward. Here is the fix I have put into the codebase: MethodDeclaration md = AstUtil.makeMethodDeclaration(methodBinding); md.scope = initializerScope; codeStream.reset(md,classFile); The makeMethodDeclaration() call conjures up a method declaration object OK and the 'is this method static?' check in the code to generate the attributes works correctly. However, code inside the reset() method tries to obtain a compiler setting through 'methodDeclaration.scope'. I *have* to put something in the scope field or the reset() method (a JDT internal method) blows up with an NPE. I didn't want to hack the JDT code to address this. I have a local scope around: initializerScope. If I use that then reset() is happy. It isn't the right scope object but I'm not sure how to construct a valid one. The scope seems to be used for only two things along this codepath: Determining a compiler option and reporting an error about code_length>64k. Because the value in 'scope' doesn't seem to be used for anything more than this, I did check the above code in. With that fix in, we don't get rogue local variable table entries for 'this' for static methods in the aspect - and the code runs on Java 1.5. I have tagged the scope setting with a XXX in case this causes any problem in the future. Since all tests I can find still pass, this seems a valid fix for the problem. I hope others agree.
fixed by Andy