JDK-8057919 : Class.getSimpleName() should work for non-JLS compliant class names
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang.invoke
  • Affected Version: 8u60,9
  • Priority: P2
  • Status: Closed
  • Resolution: Fixed
  • Submitted: 2014-09-09
  • Updated: 2017-05-17
  • Resolved: 2015-04-14
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.
9 b64Fixed
Related Reports
Relates :  
Sub Tasks
JDK-8177366 :  

On 9/2/14, 12:59 PM, Jochen Theodorou wrote:
I just wanted to ask, why Class#getSimpleName() is used in MethodType#toString(). People knowing this method from Class will also know that getSimpleName makes assumption about the inner class names, that are based on the JLS, which allows only a fraction of the names the JVM allows. Resulting in things like this:

> java.lang.InternalError: Malformed class name
> at java.lang.Class.getSimpleName(Class.java:1317)
> at java.lang.invoke.MethodType.toString(MethodType.java:724)
> at java.lang.String.valueOf(String.java:2979)
> at java.lang.StringBuilder.append(StringBuilder.java:131)
> at org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector.<init>(Selector.java:477)

bye Jochen

release-note=yes Class.getSimpleName() returns a name recorded in InnerClasses attribute from the class file instead of parsing Class.getName() and pruning enclosing class name from it. It doesn't affect javac-generated bytecode, but it causes observable change in behavior, if an application generates custom bytecode with incomplete or incorrect info recorded in InnerClasses attribute.

To answer Jochen's question directly, MethodType.toString returns a summarizing string of the type. The API does not attempt to produce a string that unambiguously encodes the full type using Class.getName because (a) the class loader information would still be missing, and (b) there would still be an ambiguity between primitive types ("int") and classes in the unnamed package ("Ant"). The method toMethodDescriptorString is more suitable if unambiguous spellings are required. Meanwhile, if we were to use Class.getName to build MethodType.toString, the method type strings would often be unreadably lengthy, defeating the main purpose of this toString method, which is debugging.

The problem is that Class.getSimpleName is cranky and throws exceptions (undocumented ones) if it sees a class name that surprises it. The existing implementation of Class.getSimpleName ignores the InnerClasses attribute which language processors use to record simple names, and instead relies on a combination of the outer-class name and some dead-reckoning. We should fix Class.getSimpleName to go into the VM and pick out the simple name put there by the bytecode generator (javac, scalac, nashorn, whatever). The JVM logic of InstanceKlass::compute_enclosing_class_impl should be adapted to pick up the inner class name (at 'noff') and return it. JVMS 4.7.6 clearly documents that the 'inner_name_index' for a class's InnerClasses record gives a UTF8 index for the string "that represents the original simple name of C, as given in the source code from which this class file was compiled.". That is, it gives the true value of Class.getSimpleName. Trying to cobble up the same thing in Java code is doomed to fail. Failing that correct fix, the patch given in the following comment would improve matters; it would not throw exceptions. Scala has observed this problem; see their bug report and the mail copied below. https://issues.scala-lang.org/browse/SI-2034 Date: March 8, 2013 at 4:52:32 PM PST Subject: Re: questions about name mangling ... Hi, The problem with '$' and reflection can be easily reproduced by compiling and running the following code: // Outer.scala object Outer { object Inner { class MoreInner } def main(args: Array[String]) { println(classOf[Inner.MoreInner].getSimpleName) } } scalac Outer.scala -d sandbox scala -cp sandbox Outer java.lang.InternalError: Malformed class name at java.lang.Class.getSimpleName(Class.java:1135) at Outer$.main(Nested.scala:6) at Outer.main(Nested.scala) [...] The reason it blows up is missing '$' for in the name 'Outer$Inner$MoreInner'. If you look at javap output: javap sandbox/Outer\$Inner\$MoreInner -verbose Compiled from "Nested.scala" public class Outer$Inner$MoreInner extends java.lang.Object SourceFile: "Nested.scala" InnerClass: public #16= #13 of #15; //Inner$=class Outer$Inner$ of class Outer public #17= #2 of #13; //MoreInner=class Outer$Inner$MoreInner of class Outer$Inner$ Scala: length = 0x You'll see that Inner has name 'Inner$' and then we get an inner class named 'Inner$MoreInner' but Java reflection would expect 'Inner$$MoreInner' where second dollar sign comes as separator. This is discussed here: https://issues.scala-lang.org/browse/SI-5425?focusedCommentId=56001&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-56001

diff --git a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java --- a/src/java.base/share/classes/java/lang/Class.java +++ b/src/java.base/share/classes/java/lang/Class.java @@ -1320,6 +1320,11 @@ simpleName = getName(); return simpleName.substring(simpleName.lastIndexOf('.')+1); // strip the package name } + + // FIXME: The following logic is binding on javac, but not on the JVM. + // Get rid of it, and instead respect the InnerClasses attribute, + // which accurately reports the simple name of all inner classes. + // According to JLS3 "Binary Compatibility" (13.1) the binary // name of non-package classes (not top level) is the binary // name of the immediately enclosing class followed by a '$' followed by: @@ -1336,7 +1341,7 @@ // Remove leading "\$[0-9]*" from the name int length = simpleName.length(); if (length < 1 || simpleName.charAt(0) != '$') - throw new InternalError("Malformed class name"); + return simpleName; // binary name doesn't follow Java source rules int index = 1; while (index < length && isAsciiDigit(simpleName.charAt(index))) index++; @@ -1449,14 +1454,18 @@ * class. */ private String getSimpleBinaryName() { + // FIXME: Call into theJVM for the inner name, not the outer name. + // Then we won't need to do the fallible prefix stripping trick. Class<?> enclosingClass = getEnclosingClass(); if (enclosingClass == null) // top level class return null; // Otherwise, strip the enclosing class' name - try { + if (getName().startsWith(enclosingClass.getName())) { return getName().substring(enclosingClass.getName().length()); - } catch (IndexOutOfBoundsException ex) { - throw new InternalError("Malformed class name", ex); + } else { + // The binary name doesn't follow Java source rules. + // We should ask the JVM, but for now we'll guess. + return getName(); } }

Nils, I'd argue that a bug in MethodType.toString() worth I=H.

ILW=incorrect behavior;impossible in java world, but common situation for other JVM language;none=HLH=>P2