summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukas Rytz <lukas.rytz@gmail.com>2014-12-16 10:50:10 +0100
committerLukas Rytz <lukas.rytz@gmail.com>2015-01-16 23:18:12 +0100
commitafebceee78155c66564921eab7dd2170f820fdbf (patch)
tree65fba50f00339b69e4b0713f4120722115cf9a9c
parent7c1983d153a81ba43858a1bb5f0b59c5708bbfcf (diff)
downloadscala-afebceee78155c66564921eab7dd2170f820fdbf.tar.gz
scala-afebceee78155c66564921eab7dd2170f820fdbf.tar.bz2
scala-afebceee78155c66564921eab7dd2170f820fdbf.zip
Construct ClassBTypes from parsed classfiles
This infrastructure is required for the inliner: when inlining code from a classfile, the corresponding ClassBType is needed for various things (eg access checks, InnerClass attribute). The test creates two ClassBTypes for the same class: once using the (unpickled) Symbol, once using the parsed ASM ClassNode, and verifies that the two are the same. There's a cleanup to the InnerClass attribute: object T { class Member; def foo = { class Local } } class T For Java compatibility the InnerClass entry for Member says the class is nested in T (not in the module class T$). We now make sure to add that entry only to T, not to T$ (unless Member is actually referenced in the classfile T$, in that case it will be added, as required).
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala22
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala2
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala116
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala110
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala20
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala2
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/CodeRepository.scala93
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala21
-rw-r--r--test/files/jvm/innerClassAttribute/Test.scala29
-rw-r--r--test/files/jvm/t8582.check6
-rw-r--r--test/files/jvm/t8582.scala4
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala2
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala95
13 files changed, 421 insertions, 101 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
index 806d4b277c..8d1c37532e 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
@@ -791,32 +791,28 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
assert(moduleClass.companionClass == NoSymbol, moduleClass)
innerClassBufferASM.clear()
this.cunit = cunit
- val moduleName = internalName(moduleClass) // + "$"
- val mirrorName = moduleName.substring(0, moduleName.length() - 1)
- val flags = (asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL)
+ val bType = mirrorClassClassBType(moduleClass)
val mirrorClass = new asm.tree.ClassNode
mirrorClass.visit(
classfileVersion,
- flags,
- mirrorName,
+ bType.info.flags,
+ bType.internalName,
null /* no java-generic-signature */,
ObjectReference.internalName,
EMPTY_STRING_ARRAY
)
- if (emitSource) {
- mirrorClass.visitSource("" + cunit.source,
- null /* SourceDebugExtension */)
- }
+ if (emitSource)
+ mirrorClass.visitSource("" + cunit.source, null /* SourceDebugExtension */)
- val ssa = getAnnotPickle(mirrorName, moduleClass.companionSymbol)
+ val ssa = getAnnotPickle(bType.internalName, moduleClass.companionSymbol)
mirrorClass.visitAttribute(if (ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign)
emitAnnotations(mirrorClass, moduleClass.annotations ++ ssa)
- addForwarders(isRemote(moduleClass), mirrorClass, mirrorName, moduleClass)
+ addForwarders(isRemote(moduleClass), mirrorClass, bType.internalName, moduleClass)
- innerClassBufferASM ++= classBTypeFromSymbol(moduleClass).info.memberClasses
+ innerClassBufferASM ++= bType.info.nestedClasses
addInnerClassesASM(mirrorClass, innerClassBufferASM.toList)
mirrorClass.visitEnd()
@@ -932,7 +928,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments
constructor.visitEnd()
- innerClassBufferASM ++= classBTypeFromSymbol(cls).info.memberClasses
+ innerClassBufferASM ++= classBTypeFromSymbol(cls).info.nestedClasses
addInnerClassesASM(beanInfoClass, innerClassBufferASM.toList)
beanInfoClass.visitEnd()
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
index 03bc32061b..142c901c21 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
@@ -118,7 +118,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
addClassFields()
- innerClassBufferASM ++= classBTypeFromSymbol(claszSymbol).info.memberClasses
+ innerClassBufferASM ++= classBTypeFromSymbol(claszSymbol).info.nestedClasses
gen(cd.impl)
addInnerClassesASM(cnode, innerClassBufferASM.toList)
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
index e1ca13cba9..11ee0d127b 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
@@ -8,6 +8,9 @@ package backend.jvm
import scala.tools.asm
import asm.Opcodes
+import scala.tools.asm.tree.{InnerClassNode, ClassNode}
+import opt.CodeRepository
+import scala.collection.convert.decorateAsScala._
/**
* The BTypes component defines The BType class hierarchy. BTypes encapsulate all type information
@@ -20,6 +23,17 @@ import asm.Opcodes
abstract class BTypes {
import BTypes.InternalName
+ // Some core BTypes are required here, in class BType, where no Global instance is available.
+ // The Global is only available in the subclass BTypesFromSymbols. We cannot depend on the actual
+ // implementation (CoreBTypesProxy) here because it has members that refer to global.Symbol.
+ val coreBTypes: CoreBTypesProxyGlobalIndependent[this.type]
+ import coreBTypes._
+
+ /**
+ * Tools for parsing classfiles, used by the inliner.
+ */
+ val codeRepository: CodeRepository
+
/**
* A map from internal names to ClassBTypes. Every ClassBType is added to this map on its
* construction.
@@ -31,18 +45,77 @@ abstract class BTypes {
* Concurrent because stack map frames are computed when in the class writer, which might run
* on multiple classes concurrently.
*/
- protected val classBTypeFromInternalNameMap: collection.concurrent.Map[InternalName, ClassBType]
+ val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType]
/**
- * Obtain a previously constructed ClassBType for a given internal name.
+ * Parse the classfile for `internalName` and construct the [[ClassBType]].
*/
- def classBTypeFromInternalName(internalName: InternalName) = classBTypeFromInternalNameMap(internalName)
+ def classBTypeFromParsedClassfile(internalName: InternalName): ClassBType = {
+ classBTypeFromClassNode(codeRepository.classNode(internalName))
+ }
- // Some core BTypes are required here, in class BType, where no Global instance is available.
- // The Global is only available in the subclass BTypesFromSymbols. We cannot depend on the actual
- // implementation (CoreBTypesProxy) here because it has members that refer to global.Symbol.
- val coreBTypes: CoreBTypesProxyGlobalIndependent[this.type]
- import coreBTypes._
+ /**
+ * Construct the [[ClassBType]] for a parsed classfile.
+ */
+ def classBTypeFromClassNode(classNode: ClassNode): ClassBType = {
+ classBTypeFromInternalName.getOrElse(classNode.name, {
+ setClassInfo(classNode, ClassBType(classNode.name))
+ })
+ }
+
+ private def setClassInfo(classNode: ClassNode, classBType: ClassBType): ClassBType = {
+ val superClass = classNode.superName match {
+ case null =>
+ assert(classNode.name == ObjectReference.internalName, s"class with missing super type: ${classNode.name}")
+ None
+ case superName =>
+ Some(classBTypeFromParsedClassfile(superName))
+ }
+
+ val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut)
+
+ val flags = classNode.access
+
+ /**
+ * Find all nested classes of classNode. The innerClasses attribute contains all nested classes
+ * that are declared inside classNode or used in the bytecode of classNode. So some of them are
+ * nested in some other class than classNode, and we need to filter them.
+ *
+ * For member classes, innerClassNode.outerName is defined, so we compare that to classNode.name.
+ *
+ * For local and anonymous classes, innerClassNode.outerName is null. Such classes are required
+ * to have an EnclosingMethod attribute declaring the outer class. So we keep those local and
+ * anonymous classes whose outerClass is classNode.name.
+ *
+ */
+ def nestedInCurrentClass(innerClassNode: InnerClassNode): Boolean = {
+ (innerClassNode.outerName != null && innerClassNode.outerName == classNode.name) ||
+ (innerClassNode.outerName == null && codeRepository.classNode(innerClassNode.name).outerClass == classNode.name)
+ }
+
+ val nestedClasses: List[ClassBType] = classNode.innerClasses.asScala.collect({
+ case i if nestedInCurrentClass(i) => classBTypeFromParsedClassfile(i.name)
+ })(collection.breakOut)
+
+ // if classNode is a nested class, it has an innerClass attribute for itself. in this
+ // case we build the NestedInfo.
+ val nestedInfo = classNode.innerClasses.asScala.find(_.name == classNode.name) map {
+ case innerEntry =>
+ val enclosingClass =
+ if (innerEntry.outerName != null) {
+ // if classNode is a member class, the outerName is non-null
+ classBTypeFromParsedClassfile(innerEntry.outerName)
+ } else {
+ // for anonymous or local classes, the outerName is null, but the enclosing class is
+ // stored in the EnclosingMethod attribute (which ASM encodes in classNode.outerClass).
+ classBTypeFromParsedClassfile(classNode.outerClass)
+ }
+ val staticFlag = (innerEntry.access & Opcodes.ACC_STATIC) != 0
+ NestedInfo(enclosingClass, Option(innerEntry.outerName), Option(innerEntry.innerName), staticFlag)
+ }
+ classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo)
+ classBType
+ }
/**
* A BType is either a primitive type, a ClassBType, an ArrayBType of one of these, or a MethodType
@@ -574,7 +647,7 @@ abstract class BTypes {
* nested classes. Example: for the definition `class A { class B }` we have
*
* B.info.nestedInfo.outerClass == A
- * A.info.memberClasses contains B
+ * A.info.nestedClasses contains B
*/
private var _info: ClassInfo = null
@@ -589,7 +662,7 @@ abstract class BTypes {
checkInfoConsistency()
}
- classBTypeFromInternalNameMap(internalName) = this
+ classBTypeFromInternalName(internalName) = this
private def checkInfoConsistency(): Unit = {
// we assert some properties. however, some of the linked ClassBType (members, superClass,
@@ -597,7 +670,7 @@ abstract class BTypes {
// best-effort verification.
def ifInit(c: ClassBType)(p: ClassBType => Boolean): Boolean = c._info == null || p(c)
- def isJLO(t: ClassBType) = t.internalName == "java/lang/Object"
+ def isJLO(t: ClassBType) = t.internalName == ObjectReference.internalName
assert(!ClassBType.isInternalPhantomType(internalName), s"Cannot create ClassBType for phantom type $this")
@@ -612,7 +685,7 @@ abstract class BTypes {
s"Invalid interfaces in $this: ${info.interfaces}"
)
- assert(info.memberClasses.forall(c => ifInit(c)(_.isNestedClass)), info.memberClasses)
+ assert(info.nestedClasses.forall(c => ifInit(c)(_.isNestedClass)), info.nestedClasses)
}
/**
@@ -640,8 +713,9 @@ abstract class BTypes {
outerName.orNull,
innerName.orNull,
GenBCode.mkFlags(
- info.flags,
- if (isStaticNestedClass) asm.Opcodes.ACC_STATIC else 0
+ // the static flag in the InnerClass table has a special meaning, see InnerClass comment
+ info.flags & ~Opcodes.ACC_STATIC,
+ if (isStaticNestedClass) Opcodes.ACC_STATIC else 0
) & ClassBType.INNER_CLASSES_FLAGS
)
}
@@ -757,12 +831,12 @@ abstract class BTypes {
* through the superclass.
* @param flags The java flags, obtained through `javaFlags`. Used also to derive
* the flags for InnerClass entries.
- * @param memberClasses Classes nested in this class. Those need to be added to the
+ * @param nestedClasses Classes nested in this class. Those need to be added to the
* InnerClass table, see the InnerClass spec summary above.
* @param nestedInfo If this describes a nested class, information for the InnerClass table.
*/
- case class ClassInfo(superClass: Option[ClassBType], interfaces: List[ClassBType], flags: Int,
- memberClasses: List[ClassBType], nestedInfo: Option[NestedInfo])
+ final case class ClassInfo(superClass: Option[ClassBType], interfaces: List[ClassBType], flags: Int,
+ nestedClasses: List[ClassBType], nestedInfo: Option[NestedInfo])
/**
* Information required to add a class to an InnerClass table.
@@ -779,10 +853,10 @@ abstract class BTypes {
* a source-level property: if the class is in a static context (does not have an outer pointer).
* This is checked when building the NestedInfo.
*/
- case class NestedInfo(enclosingClass: ClassBType,
- outerName: Option[String],
- innerName: Option[String],
- isStaticNestedClass: Boolean)
+ final case class NestedInfo(enclosingClass: ClassBType,
+ outerName: Option[String],
+ innerName: Option[String],
+ isStaticNestedClass: Boolean)
/**
* This class holds the data for an entry in the InnerClass table. See the InnerClass summary
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
index 0582088710..9b8ac82340 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
@@ -7,6 +7,7 @@ package scala.tools.nsc
package backend.jvm
import scala.tools.asm
+import opt.CodeRepository
import BTypes.InternalName
/**
@@ -33,11 +34,13 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
val coreBTypes = new CoreBTypesProxy[this.type](this)
import coreBTypes._
- final def intializeCoreBTypes(): Unit = {
+ val codeRepository = new CodeRepository(global.classPath)
+
+ final def initializeCoreBTypes(): Unit = {
coreBTypes.setBTypes(new CoreBTypes[this.type](this))
}
- protected val classBTypeFromInternalNameMap = {
+ val classBTypeFromInternalName = {
global.perRunCaches.recordCache(collection.concurrent.TrieMap.empty[InternalName, ClassBType])
}
@@ -84,7 +87,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
s"Cannot create ClassBType for special class symbol ${classSym.fullName}")
val internalName = classSym.javaBinaryName.toString
- classBTypeFromInternalNameMap.getOrElse(internalName, {
+ classBTypeFromInternalName.getOrElse(internalName, {
// The new ClassBType is added to the map in its constructor, before we set its info. This
// allows initializing cyclic dependencies, see the comment on variable ClassBType._info.
setClassInfo(classSym, ClassBType(internalName))
@@ -118,25 +121,35 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
* code generation, but those duplicates will be eliminated when emitting the InnerClass
* attribute.
*
- * Why doe we need to collect classes into innerClassBufferASM at all? To collect references to
+ * Why do we need to collect classes into innerClassBufferASM at all? To collect references to
* nested classes, but NOT nested in C, that are used within C.
*/
val nestedClassSymbols = {
// The lambdalift phase lifts all nested classes to the enclosing class, so if we collect
// member classes right after lambdalift, we obtain all nested classes, including local and
// anonymous ones.
- val nestedClasses = exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(classSym))
+ val nestedClasses = {
+ val nested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(classSym))
+ if (isTopLevelModuleClass(classSym)) {
+ // For Java compatibility, member classes of top-level objects are treated as members of
+ // the top-level companion class, see comment below.
+ val members = exitingPickler(memberClassesOf(classSym))
+ nested diff members
+ } else {
+ nested
+ }
+ }
- // If this is a top-level class, and it has a companion object, the member classes of the
- // companion are added as members of the class. For example:
+ // If this is a top-level class, the member classes of the companion object are added as
+ // members of the class. For example:
// class C { }
// object C {
// class D
// def f = { class E }
// }
- // The class D is added as a member of class C. The reason is that the InnerClass attribute
- // for D will containt class "C" and NOT the module class "C$" as the outer class of D.
- // This is done by buildNestedInfo, the reason is Java compatibility, see comment in BTypes.
+ // The class D is added as a member of class C. The reason is: for Java compatibility, the
+ // InnerClass attribute for D has "C" (NOT the module class "C$") as the outer class of D
+ // (done by buildNestedInfo). See comment in BTypes.
// For consistency, the InnerClass entry for D needs to be present in C - to Java it looks
// like D is a member of C, not C$.
val linkedClass = exitingPickler(classSym.linkedClassOfClass) // linkedCoC does not work properly in late phases
@@ -166,53 +179,51 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
} else true
})
- val memberClasses = nestedClassSymbolsNoJavaModuleClasses.map(classBTypeFromSymbol)
+ val nestedClasses = nestedClassSymbolsNoJavaModuleClasses.map(classBTypeFromSymbol)
val nestedInfo = buildNestedInfo(classSym)
- classBType.info = ClassInfo(superClass, interfaces, flags, memberClasses, nestedInfo)
+ classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo)
classBType
}
private def buildNestedInfo(innerClassSym: Symbol): Option[NestedInfo] = {
assert(innerClassSym.isClass, s"Cannot build NestedInfo for non-class symbol $innerClassSym")
- val isNested = !innerClassSym.rawowner.isPackageClass
- if (!isNested) None
+ val isTopLevel = innerClassSym.rawowner.isPackageClass
+ if (isTopLevel) None
else {
// See comment in BTypes, when is a class marked static in the InnerClass table.
val isStaticNestedClass = isOriginallyStaticOwner(innerClassSym.originalOwner)
// After lambdalift (which is where we are), the rawowoner field contains the enclosing class.
- val enclosingClassSym = {
- if (innerClassSym.isJavaDefined && innerClassSym.rawowner.isModuleClass) {
- // Example java source: class C { static class D { } }
- // The Scala compiler creates a class and a module symbol for C. Because D is a static
- // nested class, the symbol for D is nested in the module class C (not in the class C).
- // For the InnerClass attribute, we use the class symbol C, which represents the situation
- // in the source code.
-
- // Cannot use innerClassSym.isStatic: this method looks at the owner, which is a package
- // at this pahse (after lambdalift, flatten).
- assert(isOriginallyStaticOwner(innerClassSym.originalOwner), innerClassSym.originalOwner)
-
+ val enclosingClass = {
+ // (1) Example java source: class C { static class D { } }
+ // The Scala compiler creates a class and a module symbol for C. Because D is a static
+ // nested class, the symbol for D is nested in the module class C (not in the class C).
+ // For the InnerClass attribute, we use the class symbol C, which represents the situation
+ // in the source code.
+
+ // (2) Java compatibility. See the big comment in BTypes that summarizes the InnerClass spec.
+ if ((innerClassSym.isJavaDefined && innerClassSym.rawowner.isModuleClass) || // (1)
+ (!isAnonymousOrLocalClass(innerClassSym) && isTopLevelModuleClass(innerClassSym.rawowner))) { // (2)
// phase travel for linkedCoC - does not always work in late phases
- exitingPickler(innerClassSym.rawowner.linkedClassOfClass)
+ exitingPickler(innerClassSym.rawowner.linkedClassOfClass) match {
+ case NoSymbol =>
+ // For top-level modules without a companion class, see doc of mirrorClassClassBType.
+ mirrorClassClassBType(exitingPickler(innerClassSym.rawowner))
+
+ case companionClass =>
+ classBTypeFromSymbol(companionClass)
+ }
+ } else {
+ classBTypeFromSymbol(innerClassSym.rawowner)
}
- else innerClassSym.rawowner
}
- val enclosingClass: ClassBType = classBTypeFromSymbol(enclosingClassSym)
val outerName: Option[String] = {
- if (isAnonymousOrLocalClass(innerClassSym)) {
- None
- } else {
- val outerName = innerClassSym.rawowner.javaBinaryName
- // Java compatibility. See the big comment in BTypes that summarizes the InnerClass spec.
- val outerNameModule = if (isTopLevelModuleClass(innerClassSym.rawowner)) outerName.dropModule
- else outerName
- Some(outerNameModule.toString)
- }
+ if (isAnonymousOrLocalClass(innerClassSym)) None
+ else Some(enclosingClass.internalName)
}
val innerName: Option[String] = {
@@ -225,6 +236,29 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
}
/**
+ * For top-level objects without a companion class, the compilere generates a mirror class with
+ * static forwarders (Java compat). There's no symbol for the mirror class, but we still need a
+ * ClassBType (its info.nestedClasses will hold the InnerClass entries, see comment in BTypes).
+ */
+ def mirrorClassClassBType(moduleClassSym: Symbol): ClassBType = {
+ assert(isTopLevelModuleClass(moduleClassSym), s"not a top-level module class: $moduleClassSym")
+ val internalName = moduleClassSym.javaBinaryName.dropModule.toString
+ classBTypeFromInternalName.getOrElse(internalName, {
+ val c = ClassBType(internalName)
+ // class info consistent with BCodeHelpers.genMirrorClass
+ val nested = exitingPickler(memberClassesOf(moduleClassSym)) map classBTypeFromSymbol
+ c.info = ClassInfo(
+ superClass = Some(ObjectReference),
+ interfaces = Nil,
+ flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL,
+ nestedClasses = nested,
+ nestedInfo = None
+ )
+ c
+ })
+ }
+
+ /**
* True for module classes of package level objects. The backend will generate a mirror class for
* such objects.
*/
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
index de468c0a6d..abe3bc512c 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
@@ -677,7 +677,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self =>
def isDeprecated(sym: Symbol): Boolean = { sym.annotations exists (_ matches definitions.DeprecatedAttr) }
- def addInnerClasses(csym: Symbol, jclass: asm.ClassVisitor) {
+ def addInnerClasses(csym: Symbol, jclass: asm.ClassVisitor, isMirror: Boolean = false) {
/* The outer name for this inner class. Note that it returns null
* when the inner class should not get an index in the constant pool.
* That means non-member classes (anonymous). See Section 4.7.5 in the JVMS.
@@ -698,11 +698,19 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self =>
else
innerSym.rawname + innerSym.moduleSuffix
- // This collects all inner classes of csym, including local and anonymous: lambdalift makes
- // them members of their enclosing class.
- innerClassBuffer ++= exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(csym))
+ innerClassBuffer ++= {
+ val members = exitingPickler(memberClassesOf(csym))
+ // lambdalift makes all classes (also local, anonymous) members of their enclosing class
+ val allNested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(csym))
- // Add members of the companion object (if top-level). why, see comment in BTypes.scala.
+ // for the mirror class, we take the members of the companion module class (Java compat,
+ // see doc in BTypes.scala). for module classes, we filter out those members.
+ if (isMirror) members
+ else if (isTopLevelModule(csym)) allNested diff members
+ else allNested
+ }
+
+ // If this is a top-level class, add members of the companion object.
val linkedClass = exitingPickler(csym.linkedClassOfClass) // linkedCoC does not work properly in late phases
if (isTopLevelModule(linkedClass)) {
// phase travel to exitingPickler: this makes sure that memberClassesOf only sees member classes,
@@ -2796,7 +2804,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self =>
addForwarders(isRemote(modsym), mirrorClass, mirrorName, modsym)
- addInnerClasses(modsym, mirrorClass)
+ addInnerClasses(modsym, mirrorClass, isMirror = true)
mirrorClass.visitEnd()
writeIfNotTooBig("" + modsym.name, mirrorName, mirrorClass, modsym)
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
index a45f586666..d5e95c47cf 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
@@ -286,7 +286,7 @@ abstract class GenBCode extends BCodeSyncAndTry {
val initStart = Statistics.startTimer(BackendStats.bcodeInitTimer)
arrivalPos = 0 // just in case
scalaPrimitives.init()
- bTypes.intializeCoreBTypes()
+ bTypes.initializeCoreBTypes()
Statistics.stopTimer(BackendStats.bcodeInitTimer, initStart)
// initBytecodeWriter invokes fullName, thus we have to run it before the typer-dependent thread is activated.
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CodeRepository.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CodeRepository.scala
new file mode 100644
index 0000000000..285855d083
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CodeRepository.scala
@@ -0,0 +1,93 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2014 LAMP/EPFL
+ * @author Martin Odersky
+ */
+
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import scala.tools.asm
+import asm.tree._
+import scala.collection.convert.decorateAsScala._
+import scala.tools.nsc.io.AbstractFile
+import scala.tools.nsc.util.ClassFileLookup
+import OptimizerReporting._
+
+class CodeRepository(val classPath: ClassFileLookup[AbstractFile]) {
+ import BTypes.InternalName
+
+ /**
+ * Cache for parsed ClassNodes.
+ */
+ val classes: collection.concurrent.Map[InternalName, ClassNode] = collection.concurrent.TrieMap.empty[InternalName, ClassNode]
+
+ /**
+ * The class node for an internal name. If the class node is not yet available, it is parsed from
+ * the classfile on the compile classpath.
+ */
+ def classNode(internalName: InternalName): ClassNode = {
+ classes.getOrElseUpdate(internalName, parseClass(internalName))
+ }
+
+ /**
+ * The field node for a field matching `name` and `descriptor`, accessed in class `classInternalName`.
+ * The declaration of the field may be in one of the superclasses.
+ *
+ * @return The [[FieldNode]] of the requested field and the [[InternalName]] of its declaring class.
+ */
+ def fieldNode(classInternalName: InternalName, name: String, descriptor: String): Option[(FieldNode, InternalName)] = {
+ val c = classNode(classInternalName)
+ c.fields.asScala.find(f => f.name == name && f.desc == descriptor).map((_, classInternalName)) orElse {
+ Option(c.superName).flatMap(n => fieldNode(n, name, descriptor))
+ }
+ }
+
+ /**
+ * The method node for a method matching `name` and `descriptor`, accessed in class `classInternalName`.
+ * The declaration of the method may be in one of the parents.
+ *
+ * @return The [[MethodNode]] of the requested method and the [[InternalName]] of its declaring class.
+ */
+ def methodNode(classInternalName: InternalName, name: String, descriptor: String): Option[(MethodNode, InternalName)] = {
+ val c = classNode(classInternalName)
+ c.methods.asScala.find(m => m.name == name && m.desc == descriptor).map((_, classInternalName)) orElse {
+ val parents = Option(c.superName) ++ c.interfaces.asScala
+ // `view` to stop at the first result
+ parents.view.flatMap(methodNode(_, name, descriptor)).headOption
+ }
+ }
+
+ private def parseClass(internalName: InternalName): ClassNode = {
+ val fullName = internalName.replace('/', '.')
+ classPath.findClassFile(fullName) map { classFile =>
+ val classNode = new asm.tree.ClassNode()
+ val classReader = new asm.ClassReader(classFile.toByteArray)
+ // We don't need frames when inlining, but we want to keep the local variable table, so we
+ // don't use SKIP_DEBUG.
+ classReader.accept(classNode, asm.ClassReader.SKIP_FRAMES)
+ // SKIP_FRAMES leaves line number nodes. Remove them because they are not correct after
+ // inlining.
+ // TODO: we need to remove them also for classes that are not parsed from classfiles, why not simplify and do it once when inlining?
+ // OR: instead of skipping line numbers for inlined code, use write a SourceDebugExtension
+ // attribute that contains JSR-45 data that encodes debugging info.
+ // http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.11
+ // https://jcp.org/aboutJava/communityprocess/final/jsr045/index.html
+ removeLineNumberNodes(classNode)
+ classes(internalName) = classNode
+ classNode
+ } getOrElse {
+ inlineFailure(s"Class file for class $fullName not found.")
+ }
+ }
+
+ private def removeLineNumberNodes(classNode: ClassNode): Unit = {
+ for (method <- classNode.methods.asScala) {
+ val iter = method.instructions.iterator()
+ while (iter.hasNext) iter.next() match {
+ case _: LineNumberNode => iter.remove()
+ case _ =>
+ }
+ }
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala
new file mode 100644
index 0000000000..c07fda2bc5
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala
@@ -0,0 +1,21 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2014 LAMP/EPFL
+ * @author Martin Odersky
+ */
+
+package scala.tools.nsc
+package backend.jvm
+
+import scala.tools.asm
+import asm.tree._
+
+object OptimizerReporting {
+ def methodSignature(className: String, methodName: String, methodDescriptor: String): String = {
+ className + "::" + methodName + methodDescriptor
+ }
+
+ def methodSignature(className: String, method: MethodNode): String = methodSignature(className, method.name, method.desc)
+
+ def inlineFailure(reason: String): Nothing = MissingRequirementError.signal(reason)
+ def assertionError(message: String): Nothing = throw new AssertionError(message)
+}
diff --git a/test/files/jvm/innerClassAttribute/Test.scala b/test/files/jvm/innerClassAttribute/Test.scala
index 1c1a0522e4..3820048cb4 100644
--- a/test/files/jvm/innerClassAttribute/Test.scala
+++ b/test/files/jvm/innerClassAttribute/Test.scala
@@ -84,17 +84,15 @@ object Test extends BytecodeTest {
}
def testA3() = {
- def t(c: String) = {
- val List(b1, b2) = innerClassNodes(c)
- // the outer class for classes nested inside top-level modules is not the module class, but the mirror class.
- // this is a hack for java interop, handled in the backend. see BTypes.scala, comment on "Java Compatibility".
- assertMember(b1, "A3", "B1", flags = publicStatic)
- assertMember(b2, "A3", "B2$", flags = publicStatic)
- }
- t("A3$")
- // the mirror class has the same inner class attributes as the module
- // class (added when the mirror is created in the backend)
- t("A3")
+ // the inner class entries for top-level object members are in the companion class, so nothing in the module class
+ val List() = innerClassNodes("A3$")
+
+ // inner class entries in the companion class (a backend-generated mirror class in this case)
+ val List(b1, b2) = innerClassNodes("A3")
+ // the outer class for classes nested inside top-level modules is not the module class, but the mirror class.
+ // this is a hack for java interop, handled in the backend. see BTypes.scala, comment on "Java Compatibility".
+ assertMember(b1, "A3", "B1", flags = publicStatic)
+ assertMember(b2, "A3", "B2$", flags = publicStatic)
}
def testA4() = {
@@ -164,7 +162,10 @@ object Test extends BytecodeTest {
}
def testA15() = {
- val List(b) = innerClassNodes("A15")
+ // no member classes, only anonymous / local. these are nested in the module class, not the companion.
+ val List() = innerClassNodes("A15")
+
+ val List(b) = innerClassNodes("A15$")
assertLocal(b, "A15$B$3", "B$3")
val List(_, c) = innerClassNodes("A15$B$3")
@@ -283,9 +284,7 @@ object Test extends BytecodeTest {
assertMember(i3c, "A21", "I3$", flags = publicStatic)
assertLocal(j1, "A21$J1$1", "J1$1")
- val List(i2m, i3m, j3, j4, j5) = innerClassNodes("A21$")
- assertMember(i2m, "A21", "I2", flags = publicStatic)
- assertMember(i3m, "A21", "I3$", flags = publicStatic)
+ val List(j3, j4, j5) = innerClassNodes("A21$")
assertLocal(j3, "A21$J3$1", "J3$1")
assertLocal(j4, "A21$J4$1", "J4$1")
assertLocal(j5, "A21$J5$1", "J5$1") // non-static!
diff --git a/test/files/jvm/t8582.check b/test/files/jvm/t8582.check
index 564f482ff8..e388366270 100644
--- a/test/files/jvm/t8582.check
+++ b/test/files/jvm/t8582.check
@@ -14,10 +14,10 @@ Reflection can find direct nested classes (A2-B2-C2)
A2$B2: List(class A2$B2$C2)
A2$B2$C2: List()
-Mirror classes have the same InnerClass attributes as the corresponding module class:
- className[p1/p2/Singleton$Singleton$] outerClassName[p1/p2/Singleton] innerName[Singleton$] access[9]
-Module class
+The InnerClass attribute of a mirror class contains the members of the module class:
className[p1/p2/Singleton$Singleton$] outerClassName[p1/p2/Singleton] innerName[Singleton$] access[9]
+The module members are not in the InnerClass table of the module class (unless referenced):
+
An outer class has a InnerClass attribute for direct nested classes
className[A1$B1] outerClassName[A1] innerName[B1] access[1]
diff --git a/test/files/jvm/t8582.scala b/test/files/jvm/t8582.scala
index 8a57ef7952..e9a01f9016 100644
--- a/test/files/jvm/t8582.scala
+++ b/test/files/jvm/t8582.scala
@@ -55,9 +55,9 @@ object Test extends BytecodeTest {
println(nested(classOf[A2#B2]))
println(nested(classOf[A2#B2#C2]))
- nprintln("Mirror classes have the same InnerClass attributes as the corresponding module class:")
+ nprintln("The InnerClass attribute of a mirror class contains the members of the module class:")
printInner("p1.p2.Singleton") // mirror class
- println("Module class")
+ println("The module members are not in the InnerClass table of the module class (unless referenced):")
printInner("p1.p2.Singleton$")
nprintln("An outer class has a InnerClass attribute for direct nested classes")
diff --git a/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala b/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala
index 221aad6536..2347e8288e 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala
@@ -19,7 +19,7 @@ class BTypesTest {
val btypes = new BTypesFromSymbols[g.type](g)
import btypes._
- duringBackend(btypes.intializeCoreBTypes())
+ duringBackend(btypes.initializeCoreBTypes())
def classBTypeFromSymbol(sym: Symbol) = duringBackend(btypes.classBTypeFromSymbol(sym))
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala
new file mode 100644
index 0000000000..2975bd060d
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala
@@ -0,0 +1,95 @@
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+import scala.tools.asm.Opcodes._
+import org.junit.Assert._
+
+import scala.tools.nsc.backend.jvm.BTypes.InternalName
+import scala.tools.testing.AssertUtil._
+
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+
+import scala.collection.convert.decorateAsScala._
+
+@RunWith(classOf[JUnit4])
+class BTypesFromClassfileTest {
+ val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode")
+
+ import compiler._
+ import definitions._
+ import genBCode.bTypes
+ import bTypes._
+
+ def duringBackend[T](f: => T) = compiler.exitingDelambdafy(f)
+
+ val run = new compiler.Run() // initializes some of the compiler
+ duringBackend(bTypes.initializeCoreBTypes())
+
+ def clearCache() = bTypes.classBTypeFromInternalName.clear()
+
+ def sameBType(fromSym: ClassBType, fromClassfile: ClassBType, checked: Set[InternalName] = Set.empty): Set[InternalName] = {
+ if (checked(fromSym.internalName)) checked
+ else {
+ assert(fromSym == fromClassfile, s"$fromSym != $fromClassfile")
+ sameInfo(fromSym.info, fromClassfile.info, checked + fromSym.internalName)
+ }
+ }
+
+ def sameBTypes(fromSyms: Iterable[ClassBType], fromClassfiles: Iterable[ClassBType], checked: Set[InternalName]): Set[InternalName] = {
+ assert(fromSyms.size == fromClassfiles.size, s"\n$fromSyms\n$fromClassfiles")
+ (fromSyms, fromClassfiles).zipped.foldLeft(checked) {
+ case (chk, (fromSym, fromClassfile)) => sameBType(fromSym, fromClassfile, chk)
+ }
+ }
+
+ def sameInfo(fromSym: ClassInfo, fromClassfile: ClassInfo, checked: Set[InternalName]): Set[InternalName] = {
+ assert({
+ // Nested class symbols can undergo makeNotPrivate (ExplicitOuter). But this is only applied
+ // for symbols of class symbols that are being compiled, not those read from a pickle.
+ // So a class may be public in bytecode, but the symbol still says private.
+ if (fromSym.nestedInfo.isEmpty) fromSym.flags == fromClassfile.flags
+ else (fromSym.flags | ACC_PRIVATE | ACC_PUBLIC) == (fromClassfile.flags | ACC_PRIVATE | ACC_PUBLIC)
+ }, s"class flags differ\n$fromSym\n$fromClassfile")
+
+ val chk1 = sameBTypes(fromSym.superClass, fromClassfile.superClass, checked)
+
+ val chk2 = sameBTypes(fromSym.interfaces, fromClassfile.interfaces, chk1)
+
+ // The fromSym info has only member classes, no local or anonymous. The symbol is read from the
+ // Scala pickle data and only member classes are created / entered.
+ // (This is different for symbols that are being compiled, there flatten will enter all local
+ // and anonymous classes as members of the outer class. But not for unpickled symbols).
+ // The fromClassfile info has all nested classes, including anonymous and local. So we filter
+ // them out: member classes are identified by having the `outerName` defined.
+ val memberClassesFromClassfile = fromClassfile.nestedClasses.filter(_.info.nestedInfo.get.outerName.isDefined)
+ // Sorting is required: the backend sorts all InnerClass entries by internalName before writing
+ // them to the classfile (to make it deterministic: the entries are collected in a Set during
+ // code generation).
+ val chk3 = sameBTypes(fromSym.nestedClasses.sortBy(_.internalName), memberClassesFromClassfile.sortBy(_.internalName), chk2)
+ sameBTypes(fromSym.nestedInfo.map(_.enclosingClass), fromClassfile.nestedInfo.map(_.enclosingClass), chk3)
+ }
+
+ def check(classSym: Symbol): Unit = duringBackend {
+ clearCache()
+ val fromSymbol = classBTypeFromSymbol(classSym)
+ clearCache()
+ val fromClassfile = bTypes.classBTypeFromParsedClassfile(fromSymbol.internalName)
+ sameBType(fromSymbol, fromClassfile)
+ }
+
+ @Test
+ def compareClassBTypes(): Unit = {
+ // Note that not only these classes are tested, but also all their parents and all nested
+ // classes in their InnerClass attributes.
+ check(ObjectClass)
+ check(JavaNumberClass)
+ check(ConsClass)
+ check(ListModule.moduleClass)
+ }
+}