Bug 171184 - [compiler] Java compiler does not generate InnerClass attribute as per JVMS
Summary: [compiler] Java compiler does not generate InnerClass attribute as per JVMS
Status: VERIFIED FIXED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 3.3   Edit
Hardware: PC Windows XP
: P3 normal (vote)
Target Milestone: 3.3 M5   Edit
Assignee: Olivier Thomann CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2007-01-22 02:22 EST by Peter Arrenbrecht CLA
Modified: 2009-05-07 17:21 EDT (History)
4 users (show)

See Also:


Attachments
First draft (45.28 KB, patch)
2007-01-24 14:43 EST, Olivier Thomann CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Peter Arrenbrecht CLA 2007-01-22 02:22:36 EST
Build ID: I20061214-1445

Steps To Reproduce:
Compile the following:

package temp;
public class JodeTest {
	public static interface Foo 	{
		void foo();
	}
}

and:

package temp_other;
import temp.JodeTest;
public final class Bar implements JodeTest.Foo {
	public void foo() { /* dummy */ }
}

Then decompile the file temp_other/Bar.class using "javap -private -verbose temp_other.Bar". The resulting output for a javac 1.6.0-compiled .class file is:

Compiled from "Bar.java"
public final class temp_other.Bar extends java.lang.Object implements temp.JodeTest$Foo
  SourceFile: "Bar.java"
  InnerClass: 
   public abstract #17= #4 of #15; //Foo=class temp/JodeTest$Foo of class temp/JodeTest
  minor version: 0
...

The output for Eclipse's java compiler, however, is:

Compiled from "Bar.java"
public final class temp_other.Bar extends java.lang.Object implements temp.JodeTest$Foo
  SourceFile: "Bar.java"
  minor version: 0
...

This missing attribute does, for instance, break proper decompilation by jode.sourceforge.net.

More information:
The relevant part of the JVM spec is probably:

http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#79996

I verified the bug with both Eclipse 3.3 M4, and with 3.2.
Comment 1 Philipe Mulet CLA 2007-01-22 04:57:12 EST
Olivier - pls check the need for an inner attribute for a toplevel class (implementing a member interface) ?!?
Comment 2 Olivier Thomann CLA 2007-01-22 14:17:59 EST
Indeed it looks like we need the entry in the InnerClasses info.

"The InnerClasses attribute5 is a variable-length attribute in the attributes table of the ClassFile (ยง4.1) structure. If the constant pool of a class or interface refers to any class or interface that is not a member of a package, its ClassFile structure must have exactly one InnerClasses attribute in its attributes table."

So as soon as we refer to a member class, we need an InnerClasses attribute.

Comment 3 Olivier Thomann CLA 2007-01-22 14:21:39 EST
Even in this case, javac and jikes generates an inner classes info.
class JodeTest {
        public static class Foo     {
                public void foo() {}
        }
}
public final class X {
        public Object foo() {
        	return new JodeTest.Foo();
        }
}

So I'll fix it for our compiler as well.
Comment 4 Olivier Thomann CLA 2007-01-23 09:20:47 EST
It seems that any reference in the constant pool to a member or a local type requires an innerclass info for that type.
I'll work on a fix for it.
Comment 5 Olivier Thomann CLA 2007-01-23 14:30:50 EST
Would you have an example where jode is broken?
I'd like to try a fix.
Thanks.
Comment 6 Peter Arrenbrecht CLA 2007-01-23 15:35:17 EST
Well, the example I gave in the bug report itself shows the problem. Try to decompile temp_other.Bar. The javac output decompiles correctly as:

/* Bar - Decompiled by JODE
 * Visit http://jode.sourceforge.net/
 */
package temp_other;
import temp.JodeTest;

public final class Bar implements JodeTest.Foo
{
    public void foo() {
	/* empty */
    }
}

whereas Eclipse's output decompiles as

/* Bar - Decompiled by JODE
 * Visit http://jode.sourceforge.net/
 */
package temp_other;
import temp.JodeTest$Foo;

public final class Bar implements JodeTest$Foo
{
    public void foo() {
	/* empty */
    }
}

Note the "$" sign in JodeTest$Foo.
Comment 7 Peter Arrenbrecht CLA 2007-01-23 15:37:41 EST
PS. I use jode trunk. The binary release is pretty old. "ant release-bindist" builds jode.jar in the release/jode-1.90-CVS folder.
Comment 8 Olivier Thomann CLA 2007-01-24 14:43:49 EST
Created attachment 57466 [details]
First draft

You might want to try this patch. It works fine. I'll get results for the performance tests tomorrow. If perfs are good, I'll release it.
With this patch, JODE works fine on the examples you provided.
Comment 9 Peter Arrenbrecht CLA 2007-01-24 15:56:51 EST
Olivier, I have never yet built Eclipse from sources. And I shall be leaving for a 2 1/2 month holiday next Tuesday, so I don't think I'll try it anymore before that. I'll just trust your JODE check. You might also want to use javap -verbose to compare the output for Eclipse compiled files vs. javac compiled files.
Comment 10 Olivier Thomann CLA 2007-01-24 16:00:16 EST
Do you use the eclipse compiler inside Eclipse or as a jar file outside of Eclipse?
I can send you a binary plugin for jdt/core if you want to try it.
Comment 11 Peter Arrenbrecht CLA 2007-01-24 16:20:50 EST
I use it from within Eclipse. If you send a plugin I can test with Eclipse 3.3 M4, I'll gladly do it.
Comment 12 Olivier Thomann CLA 2007-01-25 13:15:13 EST
Released for 3.3M5.
To verify, you need to check the disassembled generated class file.
Regression tests added in org.eclipse.jdt.core.tests.compiler.regression.InnerEmulationTest#test140 -> test148.
Comment 13 Peter Arrenbrecht CLA 2007-01-25 15:38:57 EST
Olivier, I tested the build you sent me. It basically works OK. However, if I compile the following more elaborate set of tests, there is a slight discrepancy with javac, which still breaks jode. The test is:

package temp;
public class JodeTest {
	public static interface Foo1 { void foo(); }
	public static interface Foo2 { void foo(); }
	public static interface Foo3 { void foo(); }
	public static interface Foo4 { void foo(); }
	public static interface Foo5 { void foo(); }
	public static interface Foo6 { void foo(); }
	public static interface Foo7 { void foo(); }
}

package temp_other;
import temp.JodeTest;
public final class Bar implements JodeTest.Foo1 {
	public void foo() {}
	public JodeTest.Foo2 foo2() { 	return null; }
	public void foo3( JodeTest.Foo3 foo ) {}
	public void foo4() { JodeTest.Foo4 foo = null; }
	public void foo5()
	{
		new JodeTest.Foo5() {
			public void foo() {}
		}.foo();
	}
	public static class Foo6 implements JodeTest.Foo6 {
		public void foo() {}
	}
	public void foo7() { Bar2.foo7().foo(); }
}

package temp_other;
import temp.JodeTest;
public class Bar2 {
	public static JodeTest.Foo7 foo7() { return null; }
}


Javac does (order of lines modified slightly):

  InnerClass: 
   public #11= #10 of #7; //Foo6=class temp_other/Bar$Foo6 of class temp_other/Bar
   public abstract #20= #19 of #41; //Foo2=class temp/JodeTest$Foo2 of class temp/JodeTest
   public abstract #24= #23 of #41; //Foo3=class temp/JodeTest$Foo3 of class temp/JodeTest
   public abstract #43= #9 of #41; //Foo1=class temp/JodeTest$Foo1 of class temp/JodeTest
   public abstract #49= #38 of #41; //Foo7=class temp/JodeTest$Foo7 of class temp/JodeTest
   final #2; //class temp_other/Bar$1

The new eclipse compiler does:

  InnerClass: 
   public #56= #54 of #1; //Foo6=class temp_other/Bar$Foo6 of class temp_other/Bar
   public abstract #49= #47 of #44; //Foo2=class temp/JodeTest$Foo2 of class temp/JodeTest
   public abstract #52= #50 of #44; //Foo3=class temp/JodeTest$Foo3 of class temp/JodeTest
   public abstract #46= #5 of #44; //Foo1=class temp/JodeTest$Foo1 of class temp/JodeTest
   public abstract #53= #39 of #44; //Foo7=class temp/JodeTest$Foo7 of class temp/JodeTest
   #25; //class temp_other/Bar$1

Javac flags the anonymous inner class Bar$1 as final, Eclipse does not. This seems to break jode as the flags in the InnerClass attribute do not match the actual class's flags.

The disassembly of the Eclipse-generated Bar$1 class shows that Eclipse, too, makes the anonymous inner class final:

Compiled from "Bar.java"
final class temp_other.Bar$1 extends java.lang.Object implements temp.JodeTest$Foo5
  SourceFile: "Bar.java"
  EnclosingMethod: length = 0x4
   00 19 00 1B
  InnerClass:
   public abstract #32= #5 of #30; //Foo5=class temp/JodeTest$Foo5 of class temp/JodeTest
   #1; //class temp_other/Bar$1

Comment 14 Olivier Thomann CLA 2007-01-25 22:50:07 EST
I opened bug 171749 to track this issue.
This one was about missing inner class infos.
Comment 15 Eric Jodet CLA 2007-02-06 06:25:02 EST
Verified for 3.3 M5 using build I20070205-0009
Comment 16 Ben Wagner CLA 2007-09-27 17:28:17 EDT
I too ran into this problem and was about to submit a new bug report when I discovered this one. In my case this is quite an issue as we have plugins being compiled using the eclipse compiler and then there will be other people using other compilers to compile against the resulting class files. In some cases this causes their compile to fail with a "bad class file" error message. It then took a while to track down why this was happening. Unfortunatly I will be stuck with the 3.2 stream for a long time to come; is it possible for this to be back ported to a 3.2 maintenance release?
Comment 17 Iulian Dragos CLA 2009-05-07 08:20:18 EDT
This (or a very similar bug) is still in Eclipse 3.4.2. If a class references an inner class only as a type argument to a generic class, the Signature attribute references it, but no entry in InnerClasses is generated. For instance:

http://oauth.googlecode.com/svn/code/java/core/src/main/java/net/oauth/client/OAuthClient.java

uses Map.Entry in several generic method signatures, but InnerClasses does not mention Map$Entry. Compiling the same file with javac works fine. See the discussion here:

https://lampsvn.epfl.ch/trac/scala/ticket/1879

iulian
Comment 18 Olivier Thomann CLA 2009-05-07 17:21:10 EDT
I entered bug 275381 to track it down. Fix should be ready soon.