Bug 57436 - Java 1.5 fails to run classes produced by ajc
Summary: Java 1.5 fails to run classes produced by ajc
Status: RESOLVED FIXED
Alias: None
Product: AspectJ
Classification: Tools
Component: Compiler (show other bugs)
Version: 1.1.1   Edit
Hardware: PC Windows XP
: P2 normal (vote)
Target Milestone: 1.2   Edit
Assignee: Jim Hugunin CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2004-04-05 05:42 EDT by Wes Isberg CLA
Modified: 2004-05-13 05:09 EDT (History)
1 user (show)

See Also:


Attachments
HelloWorld + logging (1.50 KB, application/octet-stream)
2004-04-05 12:21 EDT, Matthew Webster CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Wes Isberg CLA 2004-04-05 05:42:29 EDT
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.
Comment 1 Wes Isberg CLA 2004-04-05 05:43:31 EDT
P2/1.2: Recommending for investigation before the 1.2 release, at least to
document the results.
Comment 2 Matthew Webster CLA 2004-04-05 12:21:43 EDT
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"
Comment 3 Andrew Clement CLA 2004-04-07 11:34:40 EDT
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 !
Comment 4 Andrew Clement CLA 2004-04-08 06:53:50 EDT
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.
Comment 5 Andrew Clement CLA 2004-04-09 14:08:34 EDT
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.
Comment 6 Adrian Colyer CLA 2004-05-13 05:09:54 EDT
fixed by Andy