diff options
author | Paul Phillips <paulp@improving.org> | 2012-04-26 07:02:55 -0700 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2012-04-26 10:04:33 -0700 |
commit | 2aa44c230c96b0ceb8e2b84c8095620a1be5a77f (patch) | |
tree | 2b0d6e52c9d99bb8a90e9f7498e6cd6aee04f8ea | |
parent | 99388a81d42cde0cb0e8ecd57f290f6df25b9bdd (diff) | |
download | scala-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.scala | 440 | ||||
-rw-r--r-- | src/compiler/scala/tools/cmd/program/DumpClass.scala | 40 | ||||
-rw-r--r-- | src/compiler/scala/tools/util/StringOps.scala | 10 | ||||
-rw-r--r-- | test/files/run/inner-parse.check | 86 | ||||
-rw-r--r-- | test/files/run/inner-parse/J_1.java | 9 | ||||
-rw-r--r-- | test/files/run/inner-parse/S_2.scala | 9 | ||||
-rw-r--r-- | test/files/run/inner-parse/S_3.scala | 12 | ||||
-rwxr-xr-x | tools/dump-class | 6 |
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 |