summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2012-04-26 07:02:55 -0700
committerPaul Phillips <paulp@improving.org>2012-04-26 10:04:33 -0700
commit2aa44c230c96b0ceb8e2b84c8095620a1be5a77f (patch)
tree2b0d6e52c9d99bb8a90e9f7498e6cd6aee04f8ea
parent99388a81d42cde0cb0e8ecd57f290f6df25b9bdd (diff)
downloadscala-2aa44c230c96b0ceb8e2b84c8095620a1be5a77f.tar.gz
scala-2aa44c230c96b0ceb8e2b84c8095620a1be5a77f.tar.bz2
scala-2aa44c230c96b0ceb8e2b84c8095620a1be5a77f.zip
A brand new, fast classfile parser.
Try it: ./tools/dump-class ./build/quick/classes The output is intended to be easy to filter on the command line. This is a starting point for lots of interesting bytecode analysis for which we have waited too long. Example. All generic signatures we produce. // almost 20K classfiles % find build/quick/classes -name '*.class' |wc -l 18519 // fully parsed in 6 seconds tools/dump-class build/quick/classes |grep "^ signature" | wc -l 50802 real 0m6.230s It's designed to be easy to make faster if you don't care about particular classfile bits; you can override those methods to jump forward in the input stream rather than building a structure. For just a little sampling, here are our most frequently repeated name/signature combinations. 194 signature <init> ()V // this one is weird, wonder why there's a generic signature 115 signature $div$colon$bslash <A1:Ljava/lang/Object;>(TA1;Lscala/Function2<TA1;TA1;TA1;>;)TA1; 105 signature applyOrElse <A1:Ljava/lang/Object;B1:Ljava/lang/Object;>(TA1;Lscala/Function1<TA1;TB1;>;)TB1; 103 signature view ()Ljava/lang/Object; 101 signature toSet <B:Ljava/lang/Object;>()Lscala/collection/immutable/Set<TB;>; And the top five name/descriptor combinations. 11170 descriptor <clinit> ()V 10155 descriptor serialVersionUID J 7130 descriptor apply (Ljava/lang/Object;)Ljava/lang/Object; 3028 descriptor apply ()Ljava/lang/Object; 2426 descriptor <init> ()V
-rw-r--r--src/compiler/scala/reflect/internal/JvmClassInfo.scala440
-rw-r--r--src/compiler/scala/tools/cmd/program/DumpClass.scala40
-rw-r--r--src/compiler/scala/tools/util/StringOps.scala10
-rw-r--r--test/files/run/inner-parse.check86
-rw-r--r--test/files/run/inner-parse/J_1.java9
-rw-r--r--test/files/run/inner-parse/S_2.scala9
-rw-r--r--test/files/run/inner-parse/S_3.scala12
-rwxr-xr-xtools/dump-class6
8 files changed, 612 insertions, 0 deletions
diff --git a/src/compiler/scala/reflect/internal/JvmClassInfo.scala b/src/compiler/scala/reflect/internal/JvmClassInfo.scala
new file mode 100644
index 0000000000..d47f51e512
--- /dev/null
+++ b/src/compiler/scala/reflect/internal/JvmClassInfo.scala
@@ -0,0 +1,440 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2011 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.reflect.internal
+
+import java.io.{ DataInput, InputStream, DataInputStream, ByteArrayInputStream, BufferedInputStream, FileInputStream }
+import scala.tools.nsc.io.{ Directory }
+import scala.reflect.NameTransformer.decode
+import scala.tools.util.StringOps.trimTrailingSpace
+import ConstantPool._
+
+final case class JvmVersion(minorVersion: Int, majorVersion: Int)
+
+trait ClassfileModel {
+ type Result
+ type Entry
+ type InterfaceInfo
+ type MemberInfo
+ type AttributeInfo
+ type InnerClassInfo
+
+ protected implicit def EntryArrayTag: ArrayTag[Entry]
+ protected implicit def InterfaceInfoArrayTag: ArrayTag[InterfaceInfo]
+ protected implicit def MemberInfoArrayTag: ArrayTag[MemberInfo]
+ protected implicit def AttributeInfoArrayTag: ArrayTag[AttributeInfo]
+ protected implicit def InnerClassInfoArrayTag: ArrayTag[InnerClassInfo]
+
+ // These could be implemented to jump forward in the stream if the
+ // result is not wanted.
+ def readConstantPoolEntry(): Entry
+ def readInterface(): InterfaceInfo
+ def readMember(): MemberInfo
+ def readAttribute(): AttributeInfo
+ def readInnerClass(): InnerClassInfo
+
+ def createInfo(
+ version: JvmVersion,
+ entries: Array[Entry],
+ flags: Int,
+ name: String,
+ superName: String,
+ interfaces: Array[InterfaceInfo],
+ fields: Array[MemberInfo],
+ methods: Array[MemberInfo],
+ attributes: Array[AttributeInfo]
+ ): Result
+}
+
+abstract class StreamingClassfileModel extends ClassfileModel {
+ protected[this] val in: DataInput
+ private[this] var name: String = _
+ private[this] var entries: Array[PoolEntry] = _
+
+ type Entry = PoolEntry
+
+ // These translate null into "", it's less troublesome.
+ protected def nameAt(idx: Int) = entries(idx) match {
+ case x: Name_Info => stringAt(x.name_index).replace('/', '.')
+ case _ => ""
+ }
+ protected def stringAt(idx: Int) = entries(idx) match {
+ case x: Utf8_info => x.stringValue
+ case _ => ""
+ }
+
+ protected def u4 = in.readInt
+ protected def u2 = in.readUnsignedShort.toChar
+ protected def u1 = in.readUnsignedByte
+
+ // The constant_pool table is indexed from 1 to constant_pool_count−1.
+ protected def readConstantPool(): Array[Entry] = {
+ val count = u2
+ val entries = new Array[Entry](count)
+ var i = 1
+ while (i < count) {
+ val entry = readConstantPoolEntry()
+ entries(i) = entry
+ i += entry.width
+ }
+ entries
+ }
+ protected def readInterfaces() = {
+ val count = u2
+ val interfaces = new Array[InterfaceInfo](count)
+ var i = 0
+ while (i < count) {
+ interfaces(i) = readInterface()
+ i += 1
+ }
+ interfaces
+ }
+ protected def readMembers() = {
+ val count = u2
+ val arr = new Array[MemberInfo](count)
+ var i = 0
+ while (i < count) {
+ arr(i) = readMember()
+ i += 1
+ }
+ arr
+ }
+ protected def readAttributes(): Array[AttributeInfo] = {
+ val count = u2
+ val arr = new Array[AttributeInfo](count)
+ var i = 0
+ while (i < count) {
+ arr(i) = readAttribute()
+ i += 1
+ }
+ arr
+ }
+ protected def readInnerClasses() = {
+ val count = u2
+ val arr = new Array[InnerClassInfo](count)
+ var i = 0
+ while (i < count) {
+ arr(i) = readInnerClass()
+ i += 1
+ }
+ arr
+ }
+ protected def thisClass = name
+
+ def parse() = {
+ assert(u4 == 0xCAFEBABE, "Bad magic number")
+ val version = JvmVersion(u2, u2)
+ this.entries = readConstantPool()
+ val flags = u2.toShort
+ this.name = nameAt(u2)
+ val superName = nameAt(u2)
+ val interfaces = readInterfaces()
+ val fields = readMembers()
+ val methods = readMembers()
+ val attributes = readAttributes()
+
+ try createInfo(version, entries, flags, name, superName, interfaces, fields, methods, attributes)
+ finally entries = null
+ }
+}
+
+abstract class ScalacClassfileModel extends StreamingClassfileModel {
+ type Result = JvmClassInfo
+ type InterfaceInfo = String
+ type MemberInfo = JvmMemberInfo
+ type AttributeInfo = JvmAttributeInfo
+ type InnerClassInfo = JvmInnerClassInfo
+
+ protected implicit def EntryArrayTag = arrayTag[PoolEntry]
+ protected implicit def InterfaceInfoArrayTag = arrayTag[InterfaceInfo]
+ protected implicit def MemberInfoArrayTag = arrayTag[MemberInfo]
+ protected implicit def AttributeInfoArrayTag = arrayTag[AttributeInfo]
+ protected implicit def InnerClassInfoArrayTag = arrayTag[InnerClassInfo]
+
+ def readConstantPoolEntry(): PoolEntry
+ def readInterface(): String
+ def readMember(): JvmMemberInfo
+ def readAttribute(): JvmAttributeInfo
+ def readInnerClass(): JvmInnerClassInfo
+
+ def createInfo(
+ version: JvmVersion,
+ entries: Array[PoolEntry],
+ flags: Int,
+ name: String,
+ superName: String,
+ interfaces: Array[String],
+ fields: Array[JvmMemberInfo],
+ methods: Array[JvmMemberInfo],
+ attributes: Array[JvmAttributeInfo]
+ ): JvmClassInfo = new JvmClassInfo(name, superName, interfaces, fields, methods, attributes)
+}
+
+class JvmClassInfoBuilder(protected[this] val in: DataInput) extends ScalacClassfileModel {
+ def readInterface(): InterfaceInfo = nameAt(u2)
+ def readMember(): JvmMemberInfo = new JvmMemberInfo(u2.toShort, stringAt(u2), stringAt(u2), readAttributes())
+ def readInnerClass(): JvmInnerClassInfo = new JvmInnerClassInfo(thisClass, nameAt(u2), nameAt(u2), stringAt(u2), u2.toShort)
+
+ def readConstantPoolEntry(): Entry = (u1: @annotation.switch) match {
+ case CONSTANT_Utf8 => new Utf8_info(in.readUTF)
+ case CONSTANT_Integer => new Integer_info(in.readInt)
+ case CONSTANT_Float => new Float_info(in.readFloat)
+ case CONSTANT_Long => new Long_info(in.readLong)
+ case CONSTANT_Double => new Double_info(in.readDouble)
+ case CONSTANT_Class => new Class_info(u2)
+ case CONSTANT_String => new String_info(u2)
+ case CONSTANT_Fieldref => new Fieldref_info(u2, u2)
+ case CONSTANT_Methodref => new Methodref_info(u2, u2)
+ case CONSTANT_InterfaceMethodref => new InterfaceMethodref_info(u2, u2)
+ case CONSTANT_NameAndType => new NameAndType_info(u2, u2)
+ }
+
+ // field_info attributes:
+ // ConstantValue (§4.7.2), Synthetic (§4.7.8), Signature (§4.7.9), Deprecated (§4.7.15),
+ // RuntimeVisibleAnnotations (§4.7.16) and RuntimeInvisibleAnnotations (§4.7.17).
+ //
+ // method_info attributes:
+ // Code (§4.7.3), Exceptions (§4.7.5), Synthetic (§4.7.8), Signature (§4.7.9), Deprecated (§4.7.15),
+ // RuntimeVisibleAnnotations (§4.7.16), RuntimeInvisibleAnnotations (§4.7.17), RuntimeVisibleParameterAnnotations (§4.7.18),
+ // RuntimeInvisibleParameterAnnotations (§4.7.19) and AnnotationDefault (§4.7.20).
+
+ def readAttribute(): AttributeInfo = stringAt(u2) match {
+ case "Signature" => u4 ; new SignatureAttr(stringAt(u2))
+ case "InnerClasses" => u4 ; new InnerClassesAttr(readInnerClasses())
+ case name => val bytes = new Array[Byte](u4) ; in.readFully(bytes) ; new GenericAttr(name, bytes)
+ }
+}
+
+object Classify {
+
+ /*
+
+
+ 4.2.2 Unqualified Names
+
+Names of methods, fields and local variables are stored as unqualified
+names. Unqualified names must not contain the characters '.', ';', '['
+or '/'. Method names are further constrained so that, with the exception
+of the special method names <init> and <clinit> (§3.9), they must not
+contain the characters '<' or '>'.
+
+ 4.3 Descriptors and Signatures
+
+A descriptor is a string representing the type of a field or method.
+Descriptors are represented in the class file format using modified
+UTF-8 strings (§4.4.7) and thus may be drawn, where not further
+constrained, from the entire Unicode character set. A signature is a
+string representing the generic type of a field or method, or generic
+type information for a class declaration.
+*/
+
+}
+
+object ConstantPool {
+ type UShort = Char
+
+ final val CONSTANT_Utf8 = 1
+ final val CONSTANT_Integer = 3
+ final val CONSTANT_Float = 4
+ final val CONSTANT_Long = 5
+ final val CONSTANT_Double = 6
+ final val CONSTANT_Class = 7
+ final val CONSTANT_String = 8
+ final val CONSTANT_Fieldref = 9
+ final val CONSTANT_Methodref = 10
+ final val CONSTANT_InterfaceMethodref = 11
+ final val CONSTANT_NameAndType = 12
+
+ abstract class Name_Info(tag: Byte) extends PoolEntry(tag) {
+ def name_index: UShort
+ }
+ abstract class Ref_Info(tag: Byte) extends PoolEntry(tag) {
+ def class_index: UShort
+ def name_and_type_index: UShort
+ }
+ class Class_info(val name_index: UShort) extends Name_Info(CONSTANT_Class) { }
+ class Double_info(val value: Double) extends PoolEntry(CONSTANT_Double) {
+ override def width = 2
+ }
+ class Fieldref_info(val class_index: UShort, val name_and_type_index: UShort) extends Ref_Info(CONSTANT_Fieldref)
+ class Float_info(val value: Float) extends PoolEntry(CONSTANT_Float)
+ class Integer_info(val value: Int) extends PoolEntry(CONSTANT_Integer)
+ class InterfaceMethodref_info(val class_index: UShort, val name_and_type_index: UShort) extends Ref_Info(CONSTANT_InterfaceMethodref)
+ class Long_info(val value: Long) extends PoolEntry(CONSTANT_Long) {
+ override def width = 2
+ }
+ class Methodref_info(val class_index: UShort, val name_and_type_index: UShort) extends Ref_Info(CONSTANT_Methodref)
+ class NameAndType_info(val name_index: UShort, val descriptor_index: UShort) extends Name_Info(CONSTANT_NameAndType) {
+ override def toString = "NameAndType #%s:#%s;".format(name_index, descriptor_index)
+ }
+ class String_info(val string_index: UShort) extends PoolEntry(CONSTANT_String) { }
+ class Utf8_info(override val stringValue: String) extends PoolEntry(CONSTANT_Utf8) {
+ override def toString = ("Asciz " + stringValue).trim
+ }
+
+ abstract class PoolEntry(tag: Byte) {
+ def width = 1
+ def stringValue: String = sys.error("Not a String-valued constant pool entry: " + this)
+ override def toString = (
+ getClass.getName.split("[.$]").last + "/" + tag
+ )
+ }
+ object NoEntry extends PoolEntry(-1) { }
+}
+
+abstract class JvmInfo(attributes: Array[JvmAttributeInfo]) {
+ // def flags: Short
+ def name: String
+
+ val signature = attributes collectFirst { case x: SignatureAttr => x.value } getOrElse ""
+ val innerClasses = attributes collectFirst { case x: InnerClassesAttr => x.value } getOrElse Array()
+}
+
+
+class JvmClassInfo(
+ val name: String,
+ val superName: String,
+ val interfaces: Array[String],
+ val fields: Array[JvmMemberInfo],
+ val methods: Array[JvmMemberInfo],
+ attributes: Array[JvmAttributeInfo]
+) extends JvmInfo(attributes) {
+
+ def members = fields ++ methods sortBy (_.decodedName)
+ def memberDescriptors = members map (_.toErasedString)
+ def memberSignatures = members filter (_.hasSignature) map (_.toGenericString)
+ def descriptorsString = if (memberDescriptors.nonEmpty) memberDescriptors.mkString("\n-- Member Descriptors --\n", "\n", "\n") else ""
+ def signaturesString = if (memberSignatures.nonEmpty) memberSignatures.mkString("\n-- Member Signatures --\n", "\n", "\n") else ""
+ def innersString = if (innerClasses.isEmpty) "" else innerClasses.mkString("\n-- Inner Classes --\n", "\n", "\n")
+ def membersString = descriptorsString + signaturesString
+ def extendsString = if (superName == "") "" else " extends " + superName
+ def implementsString = if (interfaces.isEmpty) "" else interfaces.mkString("Implements: ", ", ", "")
+
+ private def group(label: String, xs: Traversable[(String, String)]) =
+ xs map { case (name, value) => line(label, name, value) } mkString "\n"
+
+ private def line(label: String, name: String, data: String) =
+ trimTrailingSpace(" %-15s %30s %s".format(label, name, data))
+
+ override def toString = (
+ List(
+ "class " + name + extendsString,
+ if (signature == "") "" else line("class sig", "", signature),
+ group("interface", interfaces map (x => (("", x)))),
+ (innerClasses map (ic => line(ic.kind, ic.innerName, ic.nestString))).sorted.mkString("\n"),
+ group("descriptor", members map (x => (x.name, x.descriptor))),
+ group("signature", members filter (_.hasSignature) map (x => (x.name, x.signature)))
+ ) map trimTrailingSpace filterNot (_ == "") mkString ("", "\n", "\n")
+ )
+}
+
+// method_info or field_info {
+// u2 access_flags;
+// u2 name_index;
+// u2 descriptor_index;
+// u2 attributes_count;
+// attribute_info attributes[attributes_count];
+// }
+class JvmMemberInfo(
+ val flags: Short,
+ val name: String,
+ val descriptor: String,
+ attributes: Array[JvmAttributeInfo]
+) extends JvmInfo(attributes) {
+ def decodedName = decode(name)
+ def hasSignature = signature != ""
+ def toErasedString = "%-30s %s".format(decodedName, descriptor)
+ def toGenericString = "%-30s %s".format(decodedName, signature)
+
+ override def toString = (
+ if (hasSignature) toGenericString else toErasedString
+ )
+}
+
+abstract class JvmAttributeInfo {
+ def name: String
+ def value: Any
+}
+class GenericAttr(val name: String, val value: Array[Byte]) extends JvmAttributeInfo {
+ // attribute_info {
+ // u2 attribute_name_index;
+ // u4 attribute_length;
+ // u1 info[attribute_length];
+ // }
+}
+class SignatureAttr(val value: String) extends JvmAttributeInfo {
+ def name = "Signature"
+}
+class InnerClassesAttr(val value: Array[JvmInnerClassInfo]) extends JvmAttributeInfo {
+ def name = "InnerClasses"
+}
+
+// package foo { class Foo { class Bar } }
+//
+// javap would say
+// Bar = class foo.Foo$Bar of class foo.Foo
+// which is translated as
+// innerClass = foo.Foo$Bar
+// outerClass = foo.Foo
+// innerName = Bar
+
+class JvmInnerClassInfo(
+ thisClass: String, // classfile which is being parsed
+ val innerClass: String, // the full name of the inner/nested class
+ val outerClass: String, // the full name of the outer class - must be a prefix of innerClass
+ val innerName: String, // the simple name of the inner class - should (?) be a suffix of innerClass
+ val flags: Short // flags
+) {
+ val isEntryOfEnclosingClass = !isAnonymousClass && (innerClass == thisClass)
+ val isEntryOfNestedClass = !isAnonymousClass && (outerClass == thisClass)
+
+ def isTopLevelClass = outerClass == ""
+ def isAnonymousClass = innerName == ""
+ def isMemberClass = !isTopLevelClass
+
+ def kind = (
+ if (isEntryOfEnclosingClass) "inner/enclosing"
+ else if (isEntryOfNestedClass) "inner/nested"
+ else if (isAnonymousClass) "inner/anon"
+ else "inner"
+ )
+ def nestString = (
+ if (isEntryOfEnclosingClass) "enclosing class: " + outerClass
+ else if (isEntryOfNestedClass) "member class: " + innerClass
+ else if (isAnonymousClass) "anonymous class: " + innerClass
+ else innerClass + " in " + outerClass
+ )
+ override def toString = innerName + "=" + nestString
+}
+
+object JvmClassInfo {
+ private def classFiles(path: String) =
+ Directory(path).deepFiles filter (_ hasExtension "class")
+
+ def classInfoMap(path: String): Map[String, JvmClassInfo] = {
+ classFiles(path) map (f => (f.path, JvmClassInfo fromFile f.jfile)) toMap
+ }
+ def classInfoList(path: String): List[(String, JvmClassInfo)] = {
+ classInfoMap(path).toList sortBy (_._1)
+ }
+
+ def fromFile(file: java.io.File) =
+ fromStream(new BufferedInputStream(new FileInputStream(file)))
+
+ def fromBytes(bytes: Array[Byte]) =
+ fromStream(new ByteArrayInputStream(bytes))
+
+ def fromPath(path: String) =
+ fromStream(new BufferedInputStream(new FileInputStream(path)))
+
+ def fromStream(in0: InputStream) = {
+ val in = new DataInputStream(in0)
+ try fromDataInput(in) finally in.close()
+ }
+
+ def fromDataInput(in: DataInput): JvmClassInfo = {
+ new JvmClassInfoBuilder(in) parse
+ }
+}
diff --git a/src/compiler/scala/tools/cmd/program/DumpClass.scala b/src/compiler/scala/tools/cmd/program/DumpClass.scala
new file mode 100644
index 0000000000..a583f1d3ea
--- /dev/null
+++ b/src/compiler/scala/tools/cmd/program/DumpClass.scala
@@ -0,0 +1,40 @@
+/* NEST (New Scala Test)
+ * Copyright 2007-2011 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools
+package cmd
+package program
+
+import scala.reflect.internal.JvmClassInfo
+import scala.tools.nsc.io.Directory
+
+object DumpClass {
+ private val usage = """
+ |Usage: dump-class [options] <path> <path> ...
+ |
+ |Parses and dumps the bytecode of all classes found at the given paths.
+ |""".stripMargin
+
+ private val unaryOps = List(
+ "signatures" -> "dump signatures"
+ )
+ private val info = Simple.scalaProgramInfo("dump-class", usage)
+ private val spec = Simple(info, unaryOps, Nil, null)
+
+ def deepInfos(dir: String) = {
+ val files = Directory(dir).deepFiles.toList filter (_ hasExtension "class")
+ files.sortBy(_.path) map (f => (f.path, JvmClassInfo fromPath f.path))
+ }
+
+ def main(args: Array[String]): Unit = {
+ val runner = spec instance args
+ import runner._
+
+ if (args.isEmpty)
+ println(usage)
+ else
+ (residualArgs flatMap deepInfos) sortBy (_._1) map (_._2) foreach println
+ }
+}
diff --git a/src/compiler/scala/tools/util/StringOps.scala b/src/compiler/scala/tools/util/StringOps.scala
index 02eb364abe..725e0afb79 100644
--- a/src/compiler/scala/tools/util/StringOps.scala
+++ b/src/compiler/scala/tools/util/StringOps.scala
@@ -25,6 +25,16 @@ trait StringOps {
val ys = oempty(xs: _*)
if (ys.isEmpty) orElse else ys mkString sep
}
+ def trimTrailingSpace(s: String) = {
+ if (s.length == 0 || !s.charAt(s.length - 1).isWhitespace) s
+ else {
+ var idx = s.length - 1
+ while (idx >= 0 && s.charAt(idx).isWhitespace)
+ idx -= 1
+
+ s.substring(0, idx + 1)
+ }
+ }
def decompose(str: String, sep: Char): List[String] = {
def ws(start: Int): List[String] =
diff --git a/test/files/run/inner-parse.check b/test/files/run/inner-parse.check
new file mode 100644
index 0000000000..87ea9ddeb5
--- /dev/null
+++ b/test/files/run/inner-parse.check
@@ -0,0 +1,86 @@
+file Test$$anonfun$main$1.class
+class Test$$anonfun$main$1 extends scala.runtime.AbstractFunction1$mcVL$sp
+ interface scala.Serializable
+ inner/anon anonymous class: Test$$anonfun$main$1
+ descriptor <clinit> ()V
+ descriptor apply (Lscala/Tuple2;)V
+ descriptor apply (Ljava/lang/Object;)Ljava/lang/Object;
+ descriptor cwd$1 Ljava/lang/String;
+ descriptor serialVersionUID J
+ descriptor <init> (Ljava/lang/String;)V
+ signature apply (Lscala/Tuple2<Ljava/lang/String;Lscala/reflect/internal/JvmClassInfo;>;)V
+
+file Test$.class
+class Test$ extends java.lang.Object
+ inner/anon anonymous class: Test$$anonfun$main$1
+ descriptor <clinit> ()V
+ descriptor MODULE$ LTest$;
+ descriptor main ([Ljava/lang/String;)V
+ descriptor <init> ()V
+
+file Test.class
+class Test extends java.lang.Object
+ inner/anon anonymous class: Test$$anonfun$main$1
+ descriptor main ([Ljava/lang/String;)V
+
+file j/J_1$B$C$D.class
+class j.J_1$B$C$D extends java.lang.Object
+ inner B j.J_1$B in j.J_1
+ inner C j.J_1$B$C in j.J_1$B
+ inner/enclosing D enclosing class: j.J_1$B$C
+ descriptor <init> (Lj/J_1$B$C;)V
+ descriptor this$2 Lj/J_1$B$C;
+
+file j/J_1$B$C.class
+class j.J_1$B$C extends java.lang.Object
+ inner B j.J_1$B in j.J_1
+ inner/enclosing C enclosing class: j.J_1$B
+ inner/nested D member class: j.J_1$B$C$D
+ descriptor <init> (Lj/J_1$B;)V
+ descriptor this$1 Lj/J_1$B;
+
+file j/J_1$B.class
+class j.J_1$B extends java.lang.Object
+ inner/enclosing B enclosing class: j.J_1
+ inner/nested C member class: j.J_1$B$C
+ descriptor <init> (Lj/J_1;)V
+ descriptor this$0 Lj/J_1;
+
+file j/J_1.class
+class j.J_1 extends java.lang.Object
+ interface java.util.RandomAccess
+ inner/nested B member class: j.J_1$B
+ descriptor <init> ()V
+
+file s/J_1$B$C$D.class
+class s.J_1$B$C$D extends java.lang.Object
+ inner B s.J_1$B in s.J_1
+ inner C s.J_1$B$C in s.J_1$B
+ inner/enclosing D enclosing class: s.J_1$B$C
+ descriptor $outer Ls/J_1$B$C;
+ descriptor s$J_1$B$C$D$$$outer ()Ls/J_1$B$C;
+ descriptor <init> (Ls/J_1$B$C;)V
+
+file s/J_1$B$C.class
+class s.J_1$B$C extends java.lang.Object
+ inner B s.J_1$B in s.J_1
+ inner/enclosing C enclosing class: s.J_1$B
+ inner/nested D member class: s.J_1$B$C$D
+ descriptor $outer Ls/J_1$B;
+ descriptor s$J_1$B$C$$$outer ()Ls/J_1$B;
+ descriptor <init> (Ls/J_1$B;)V
+
+file s/J_1$B.class
+class s.J_1$B extends java.lang.Object
+ inner/enclosing B enclosing class: s.J_1
+ inner/nested C member class: s.J_1$B$C
+ descriptor $outer Ls/J_1;
+ descriptor s$J_1$B$$$outer ()Ls/J_1;
+ descriptor <init> (Ls/J_1;)V
+
+file s/J_1.class
+class s.J_1 extends java.lang.Object
+ interface java.util.RandomAccess
+ inner/nested B member class: s.J_1$B
+ descriptor <init> ()V
+
diff --git a/test/files/run/inner-parse/J_1.java b/test/files/run/inner-parse/J_1.java
new file mode 100644
index 0000000000..920ab951ab
--- /dev/null
+++ b/test/files/run/inner-parse/J_1.java
@@ -0,0 +1,9 @@
+package j;
+
+public class J_1 implements java.util.RandomAccess { // "random" marker interface
+ class B {
+ class C {
+ class D { }
+ }
+ }
+}
diff --git a/test/files/run/inner-parse/S_2.scala b/test/files/run/inner-parse/S_2.scala
new file mode 100644
index 0000000000..fd144a40b7
--- /dev/null
+++ b/test/files/run/inner-parse/S_2.scala
@@ -0,0 +1,9 @@
+package s;
+
+class J_1 extends java.util.RandomAccess {
+ class B {
+ class C {
+ class D { }
+ }
+ }
+}
diff --git a/test/files/run/inner-parse/S_3.scala b/test/files/run/inner-parse/S_3.scala
new file mode 100644
index 0000000000..296a651460
--- /dev/null
+++ b/test/files/run/inner-parse/S_3.scala
@@ -0,0 +1,12 @@
+import scala.reflect.internal.JvmClassInfo
+
+object Test {
+ def main(args: Array[String]): Unit = {
+ val cwd = sys.props("partest.output")
+
+ for ((f, info) <- JvmClassInfo.classInfoList(cwd)) {
+ println("file " + f.stripPrefix(cwd + "/"))
+ println(info)
+ }
+ }
+}
diff --git a/tools/dump-class b/tools/dump-class
new file mode 100755
index 0000000000..0b4f2a73fa
--- /dev/null
+++ b/tools/dump-class
@@ -0,0 +1,6 @@
+#!/bin/sh
+#
+
+classpath=$($(dirname $BASH_SOURCE)/quickcp)
+
+java -cp "$classpath" scala.tools.nsc.MainGenericRunner -usejavacp scala.tools.cmd.program.DumpClass "$@" \ No newline at end of file