JDK-8037934 : Javac generates invalid signatures for local types
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 7u51,8,9
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: linux_ubuntu
  • CPU: x86_64
  • Submitted: 2014-03-18
  • Updated: 2014-08-18
  • Resolved: 2014-05-29
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 8 JDK 9
8u20 b20Fixed 9Fixed
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
Tested on OpenJDK 7 and seen the same bug in OpenJDK 8 source code

ADDITIONAL OS VERSION INFORMATION :
Irrelevant: this is generic code

A DESCRIPTION OF THE PROBLEM :
The following code compiles but fails to run:

class Foo<Outer> {
    void m(){
        class Local1{}
        class Local2 extends Local1{}
        Local2.class.getTypeParameters();
    }
    public static void main(String[] args) {
        new Foo().m();
        System.err.println("Test passed");
    }
}

This is because the signature attribute for Local2's super class is incorrectly generated by javac as "Lfoo/Foo.1Local1;" while it should be "Lfoo/Foo$1Local1;" (a dollar instead of a dot). Eclipse's compiler correctly generates the signature. Javac for OpenJDK 7 will generate an invalid signature resulting in the following exception at runtime:

Exception in thread "main" java.lang.reflect.GenericSignatureFormatError
	at sun.reflect.generics.parser.SignatureParser.error(SignatureParser.java:126)
	at sun.reflect.generics.parser.SignatureParser.parsePackageNameAndSimpleClassTypeSignature(SignatureParser.java:350)
	at sun.reflect.generics.parser.SignatureParser.parseClassTypeSignature(SignatureParser.java:312)
	at sun.reflect.generics.parser.SignatureParser.parseClassSignature(SignatureParser.java:214)
	at sun.reflect.generics.parser.SignatureParser.parseClassSig(SignatureParser.java:158)
	at sun.reflect.generics.repository.ClassRepository.parse(ClassRepository.java:52)
	at sun.reflect.generics.repository.ClassRepository.parse(ClassRepository.java:41)
	at sun.reflect.generics.repository.AbstractRepository.<init>(AbstractRepository.java:74)
	at sun.reflect.generics.repository.GenericDeclRepository.<init>(GenericDeclRepository.java:48)
	at sun.reflect.generics.repository.ClassRepository.<init>(ClassRepository.java:48)
	at sun.reflect.generics.repository.ClassRepository.make(ClassRepository.java:65)
	at java.lang.Class.getGenericInfo(Class.java:2360)
	at java.lang.Class.getTypeParameters(Class.java:640)
	at foo.Foo.m(foo.java:7)
	at foo.Foo.main(foo.java:10)

The bug is due to com.sun.tools.javac.jvm.ClassWriter.assembleClassSig():

...
        if (outer.allparams().nonEmpty()) {
            boolean rawOuter =
                c.owner.kind == MTH || // either a local class
                c.name == names.empty; // or anonymous
            assembleClassSig(rawOuter
                             ? types.erasure(outer)
                             : outer);
            sigbuf.appendByte('.'); // BUG HERE
            Assert.check(c.flatname.startsWith(c.owner.enclClass().flatname));
            sigbuf.appendName(rawOuter
                              ? c.flatname.subName(c.owner.enclClass().flatname.getByteLength()+1,c.flatname.getByteLength())
                              : c.name);
        } else {
...

If we replace the line with "BUG HERE" with:

            sigbuf.appendByte(rawOuter ? '$' : '.');

Then it works.

The same bug seems to be present in OpenJDK 8 but the code has moved to Types.java: http://hg.openjdk.java.net/jdk8/jdk8/langtools/file/1ff9d5118aae/src/share/classes/com/sun/tools/javac/code/Types.java#l4652


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile the sample Java code, run foo.Foo

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Expected to see "Test passed"
ACTUAL -
Exception in thread "main" java.lang.reflect.GenericSignatureFormatError
	at sun.reflect.generics.parser.SignatureParser.error(SignatureParser.java:126)
	at sun.reflect.generics.parser.SignatureParser.parsePackageNameAndSimpleClassTypeSignature(SignatureParser.java:350)
	at sun.reflect.generics.parser.SignatureParser.parseClassTypeSignature(SignatureParser.java:312)
	at sun.reflect.generics.parser.SignatureParser.parseClassSignature(SignatureParser.java:214)
	at sun.reflect.generics.parser.SignatureParser.parseClassSig(SignatureParser.java:158)
	at sun.reflect.generics.repository.ClassRepository.parse(ClassRepository.java:52)
	at sun.reflect.generics.repository.ClassRepository.parse(ClassRepository.java:41)
	at sun.reflect.generics.repository.AbstractRepository.<init>(AbstractRepository.java:74)
	at sun.reflect.generics.repository.GenericDeclRepository.<init>(GenericDeclRepository.java:48)
	at sun.reflect.generics.repository.ClassRepository.<init>(ClassRepository.java:48)
	at sun.reflect.generics.repository.ClassRepository.make(ClassRepository.java:65)
	at java.lang.Class.getGenericInfo(Class.java:2360)
	at java.lang.Class.getTypeParameters(Class.java:640)
	at foo.Foo.m(foo.java:7)
	at foo.Foo.main(foo.java:10)


ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "main" java.lang.reflect.GenericSignatureFormatError
	at sun.reflect.generics.parser.SignatureParser.error(SignatureParser.java:126)
	at sun.reflect.generics.parser.SignatureParser.parsePackageNameAndSimpleClassTypeSignature(SignatureParser.java:350)
	at sun.reflect.generics.parser.SignatureParser.parseClassTypeSignature(SignatureParser.java:312)
	at sun.reflect.generics.parser.SignatureParser.parseClassSignature(SignatureParser.java:214)
	at sun.reflect.generics.parser.SignatureParser.parseClassSig(SignatureParser.java:158)
	at sun.reflect.generics.repository.ClassRepository.parse(ClassRepository.java:52)
	at sun.reflect.generics.repository.ClassRepository.parse(ClassRepository.java:41)
	at sun.reflect.generics.repository.AbstractRepository.<init>(AbstractRepository.java:74)
	at sun.reflect.generics.repository.GenericDeclRepository.<init>(GenericDeclRepository.java:48)
	at sun.reflect.generics.repository.ClassRepository.<init>(ClassRepository.java:48)
	at sun.reflect.generics.repository.ClassRepository.make(ClassRepository.java:65)
	at java.lang.Class.getGenericInfo(Class.java:2360)
	at java.lang.Class.getTypeParameters(Class.java:640)
	at foo.Foo.m(foo.java:7)
	at foo.Foo.main(foo.java:10)


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
class Foo<Outer> {
    void m(){
        class Local1{}
        class Local2 extends Local1{}
        Local2.class.getTypeParameters();
    }
    public static void main(String[] args) {
        new Foo().m();
        System.err.println("Test passed");
    }
}

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

CUSTOMER SUBMITTED WORKAROUND :
For some reason, if the Foo outer class does not take type parameters, the bug will not trigger at runtime.


Comments
It turns out that Core Reflection rejects the signature "Foo.1Local1" on the grounds that Foo is generic and therefore must be given type arguments in the signature. This is surprisingly fussy for Core Reflection. A compiler could conform by generating "Foo<Outer>.1Local1" or "Foo<DummyTypeArg>.1Local1" but neither is appealing, mainly because the local class Local1 isn't technically a member of the generic class Foo<Outer>. To circumvent this fussiness, compilers generate "Foo$1Local1" which is syntactically legal (per JVMS8 4.7.9.1) and semantically acceptable.
18-08-2014

This bug is wrong to claim that "Lfoo/Foo.1Local1;" should be "Lfoo/Foo$1Local1;" in a Signature attribute. $ has never been allowed in a Signature attribute. Signatures are not descriptors. See JVMS8 4.7.9.1 "Signatures" (formerly JVMS7 4.3.4). If javac has been changed to emit $ rather than . then it should be changed back. There may be bugs in the Signature parsing code w.r.t. nested types, but that is a separate issue.
16-07-2014

I think that in 8 (9) the problem should not be fixed in Types but during class writing, at jvm.ClassWriter.
31-03-2014

reproducible in versions 8 and 9
31-03-2014