JDK-2210448 : IncompatibleClassChangeError with unreferenced local class with subclass
  • Type: Backport
  • Backport of: JDK-7003595
  • Component: tools
  • Sub-Component: javac
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • Submitted: 2011-06-06
  • Updated: 2012-02-24
  • Resolved: 2012-02-24
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 6 JDK 8
6u32Fixed 8 b08Fixed
Description
Copied from main CR (6u29):

FULL PRODUCT VERSION :
1.6.0_22-b04 and 1.7.0-ea-b119

A DESCRIPTION OF THE PROBLEM :
Compiling and running a class with a local class that is only used via a subclass causes an IncompatibleClassChangeError. See the attached example.

It could be either a jvm or a compiler bug.
I suspect it is a compiler bug because it works with the Eclipse compiler, and adding a dummy reference to the class fixes it. My guess is that the compiler forgets to add an entry in the InnerClasses attribute because it doesn't notice that the class is being referenced.

The problem does not occur when running with a Java 5 jvm (compiled with 5,6 or 7-ea compiler), but that might be because that version doesn't check the InnerClasses attribute (or is less strict).


ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "main" java.lang.IncompatibleClassChangeError: LocalClassTest and LocalClassTest$1A disagree on InnerClasses attribute
        at java.lang.Class.getDeclaringClass(Native Method)
        at LocalClassTest.main(LocalClassTest.java:5)


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
public class LocalClassTest {
	public static void main(String... args) {
		class A {}
		class B extends A {}
		B.class.getSuperclass().getDeclaringClass();
		// using A directly anywhere works around the problem:
		// new A();
	}
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Add a reference to the local class, e.g. the dummy "new A()" in the example.
*** (#1 of 1): 2010-11-30 15:17:37 PST ###@###.###

Comments
SUGGESTED FIX A webrev of this fix is available at the following URL: http://hg.openjdk.java.net/jdk8/tl/langtools/rev/f595d8bc0599
13-09-2011

EVALUATION The JVM spec is a bit tricky on this point - at first it would seem that javac behavior is correct: Section 4.7.5: "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 it would seem: no constant pool ref, no InnerClass attribute entry. But wait, here's more (also from 4.7.5): "If a class has members that are classes or interfaces, its constant_pool table (and hence its InnerClasses attribute) must refer to each such member, even if that member is not otherwise mentioned by the class. These rules imply that a nested class or interface member will have InnerClasses information for each enclosing class and for each immediate member." In other words, the JVMS assumes that an implicit constant pool ref is generated _for each_ inner class, regardless of whether they are actively used or not. So, what does javac do? *) Member/Static inner classes - those classes are always added to the InnerClasses attribute. In fact, those types live in the outer class' scope, and ClassWriter has explicit code to emit proper info for each member type in th outer class scope. *) anonymous inner classes - those classes do not live in the outer class' scope. However, they are always added, since they can only appear in the context of an instance creation expression - as such a constant pool reference will always be present and will guarantee that the class is added to the InnerClasses attribute. *) local inner classes - as for anonymous classes, those classes do not live in the outer class' scope. The only way javac adds them to the InnerClasses attribute is if some bytecode refers to them. If a local class goes unreferenced, it is not added to the InnerClasses attribute, which is buggy.
13-09-2011