diff options
7 files changed, 214 insertions, 41 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala index 09d78fdf41..fb9804be7f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala @@ -22,6 +22,14 @@ final class BCodeAsmCommon[G <: Global](val global: G) { } /** + * True for classes generated by the Scala compiler that are considered top-level in terms of + * the InnerClass / EnclosingMethod classfile attributes. See comment in BTypes. + */ + def considerAsTopLevelImplementationArtifact(classSym: Symbol) = { + classSym.isImplClass || classSym.isSpecialized + } + + /** * Cache the value of delambdafy == "inline" for each run. We need to query this value many * times, so caching makes sense. */ @@ -148,7 +156,10 @@ final class BCodeAsmCommon[G <: Global](val global: G) { if (sym.isClass) sym else enclosingClass(nextEnclosing(sym)) } - enclosingClass(nextEnclosing(classSym)) + val r = enclosingClass(nextEnclosing(classSym)) + // this should be an assertion, but we are more cautious for now as it was introduced before the 2.11.6 minor release + if (considerAsTopLevelImplementationArtifact(r)) devWarning(s"enclosing class of $classSym should not be an implementation artifact class: $r") + r } final case class EnclosingMethodEntry(owner: String, name: String, methodDescriptor: String) @@ -162,7 +173,8 @@ final class BCodeAsmCommon[G <: Global](val global: G) { * on the implementation of GenASM / GenBCode, so they need to be passed in. */ def enclosingMethodAttribute(classSym: Symbol, classDesc: Symbol => String, methodDesc: Symbol => String): Option[EnclosingMethodEntry] = { - if (isAnonymousOrLocalClass(classSym)) { + // trait impl classes are always top-level, see comment in BTypes + if (isAnonymousOrLocalClass(classSym) && !considerAsTopLevelImplementationArtifact(classSym)) { val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym) debuglog(s"enclosing method for $classSym is $methodOpt (in ${methodOpt.map(_.enclClass)})") Some(EnclosingMethodEntry( @@ -191,11 +203,13 @@ final class BCodeAsmCommon[G <: Global](val global: G) { * The member classes of a class symbol. Note that the result of this method depends on the * current phase, for example, after lambdalift, all local classes become member of the enclosing * class. + * + * Impl classes are always considered top-level, see comment in BTypes. */ - def memberClassesOf(classSymbol: Symbol): List[Symbol] = classSymbol.info.decls.collect({ - case sym if sym.isClass => + def memberClassesForInnerClassTable(classSymbol: Symbol): List[Symbol] = classSymbol.info.decls.collect({ + case sym if sym.isClass && !considerAsTopLevelImplementationArtifact(sym) => sym - case sym if sym.isModule => + case sym if sym.isModule && !considerAsTopLevelImplementationArtifact(sym) => // impl classes get the lateMODULE flag in mixin val r = exitingPickler(sym.moduleClass) assert(r != NoSymbol, sym.fullLocationString) r diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index 8d1c37532e..ccee230191 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -362,8 +362,8 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { else if (sym == definitions.NullClass) RT_NULL else { val r = classBTypeFromSymbol(sym) - if (r.isNestedClass) innerClassBufferASM += r - r + if (r.isNestedClass) innerClassBufferASM += r + r } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index 7814ed858b..a194fe8fe4 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -634,6 +634,28 @@ abstract class BTypes { * } * } * + * + * Traits Members + * -------------- + * + * Some trait methods don't exist in the generated interface, but only in the implementation class + * (private methods in traits for example). Since EnclosingMethod expresses a source-level property, + * but the source-level enclosing method doesn't exist in the classfile, we the enclosing method + * is null (the enclosing class is still emitted). + * See BCodeAsmCommon.considerAsTopLevelImplementationArtifact + * + * + * Implementation Classes, Specialized Classes, Delambdafy:method closure classes + * ------------------------------------------------------------------------------ + * + * Trait implementation classes and specialized classes are always considered top-level. Again, + * the InnerClass / EnclosingMethod attributes describe a source-level properties. The impl + * classes are compilation artifacts. + * + * The same is true for delambdafy:method closure classes. These classes are generated at + * top-level in the delambdafy phase, no special support is required in the backend. + * + * * Mirror Classes * -------------- * diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index bc880e002e..52d5cafc9c 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -129,35 +129,42 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { // member classes right after lambdalift, we obtain all nested classes, including local and // anonymous ones. val nestedClasses = { - val nested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(classSym)) + val nested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(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)) + val members = exitingPickler(memberClassesForInnerClassTable(classSym)) nested diff members } else { nested } } - // 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: 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 - val companionModuleMembers = { - // phase travel to exitingPickler: this makes sure that memberClassesOf only sees member classes, - // not local classes of the companion module (E in the exmaple) that were lifted by lambdalift. - if (isTopLevelModuleClass(linkedClass)) exitingPickler(memberClassesOf(linkedClass)) - else Nil + val companionModuleMembers = if (considerAsTopLevelImplementationArtifact(classSym)) Nil else { + // If this is a top-level non-impl (*) 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: 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$. + // + // (*) We exclude impl classes: if the classfile for the impl class exists on the classpath, + // a linkedClass symbol is found for which isTopLevelModule is true, so we end up searching + // members of that weird impl-class-module-class-symbol. that search probably cannot return + // any classes, but it's better to exclude it. + val linkedClass = exitingPickler(classSym.linkedClassOfClass) // linkedCoC does not work properly in late phases + if (linkedClass != NoSymbol && isTopLevelModuleClass(linkedClass)) + // phase travel to exitingPickler: this makes sure that memberClassesForInnerClassTable only sees member + // classes, not local classes of the companion module (E in the exmaple) that were lifted by lambdalift. + exitingPickler(memberClassesForInnerClassTable(linkedClass)) + else + Nil } nestedClasses ++ companionModuleMembers @@ -191,7 +198,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { assert(innerClassSym.isClass, s"Cannot build NestedInfo for non-class symbol $innerClassSym") val isTopLevel = innerClassSym.rawowner.isPackageClass - if (isTopLevel) None + // impl classes are considered top-level, see comment in BTypes + if (isTopLevel || considerAsTopLevelImplementationArtifact(innerClassSym)) None else { // See comment in BTypes, when is a class marked static in the InnerClass table. val isStaticNestedClass = isOriginallyStaticOwner(innerClassSym.originalOwner) @@ -248,7 +256,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { classBTypeFromInternalName.getOrElse(internalName, { val c = ClassBType(internalName) // class info consistent with BCodeHelpers.genMirrorClass - val nested = exitingPickler(memberClassesOf(moduleClassSym)) map classBTypeFromSymbol + val nested = exitingPickler(memberClassesForInnerClassTable(moduleClassSym)) map classBTypeFromSymbol c.info = ClassInfo( superClass = Some(ObjectReference), interfaces = Nil, diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index c36afd018b..0543929aef 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -595,7 +595,8 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => val x = innerClassSymbolFor(s) if(x ne NoSymbol) { assert(x.isClass, "not an inner-class symbol") - val isInner = !x.rawowner.isPackageClass + // impl classes are considered top-level, see comment in BTypes + val isInner = !considerAsTopLevelImplementationArtifact(s) && !x.rawowner.isPackageClass if (isInner) { innerClassBuffer += x collectInnerClass(x.rawowner) @@ -700,23 +701,29 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => } innerClassBuffer ++= { - val members = exitingPickler(memberClassesOf(csym)) + val members = exitingPickler(memberClassesForInnerClassTable(csym)) // lambdalift makes all classes (also local, anonymous) members of their enclosing class - val allNested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(csym)) + val allNested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(csym)) // 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 + 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, - // not local classes that were lifted by lambdalift. - innerClassBuffer ++= exitingPickler(memberClassesOf(linkedClass)) + if (!considerAsTopLevelImplementationArtifact(csym)) { + // If this is a top-level non-impl class, add members of the companion object. + // We exclude impl classes: if the classfile for the impl class exists on the classpath, a + // linkedClass symbol is found for which isTopLevelModule is true, so we end up searching + // members of that weird impl-class-module-class-symbol. that search probably cannot return + // any classes, but it's better to exclude it. + val linkedClass = exitingPickler(csym.linkedClassOfClass) // linkedCoC does not work properly in late phases + if (linkedClass != NoSymbol && isTopLevelModule(linkedClass)) { + // phase travel to exitingPickler: this makes sure that memberClassesForInnerClassTable only + // sees member classes, not local classes that were lifted by lambdalift. + innerClassBuffer ++= exitingPickler(memberClassesForInnerClassTable(linkedClass)) + } } val allInners: List[Symbol] = innerClassBuffer.toList filterNot deadCode.elidedClosures diff --git a/test/files/jvm/innerClassAttribute/Classes_1.scala b/test/files/jvm/innerClassAttribute/Classes_1.scala index 7b5f5d93b9..e58fd6e26f 100644 --- a/test/files/jvm/innerClassAttribute/Classes_1.scala +++ b/test/files/jvm/innerClassAttribute/Classes_1.scala @@ -240,3 +240,40 @@ trait SI_9124 { val f1 = { new A { def f5 = 0 }; 1 } // encl class SI_9124, no encl meth private val f2 = { new A { def f6 = 0 }; 1 } // like above } + +trait ImplClassesAreTopLevel { + // all impl classes are top-level, so they don't appear in any InnerClass entry, and none of them have an EnclosingMethod attr + trait B1 { def f = 1 } + { trait B2 { def f = 1 }; new B2 {} } + val m = { + trait B3 { def f = 1 } + new B3 {} + } + def n = { + trait B4 { def f = 1 } + new B4 {} + } +} + +class SpecializedClassesAreTopLevel { + // all specialized classes are top-level + class A[@specialized(Int) T]; new A[Int] + + object T { + class B[@specialized(Int) T]; new B[Int] + } + + // these crash the compiler, SI-7625 + + // { class B[@specialized(Int) T]; new B[Int] } + + // val m: Object = { + // class C[@specialized(Int) T] + // new C[Int] + // } + + // def n: Object = { + // class D[@specialized(Int) T] + // new D[Int] + // } +} diff --git a/test/files/jvm/innerClassAttribute/Test.scala b/test/files/jvm/innerClassAttribute/Test.scala index 5dacd2d830..16e5d40052 100644 --- a/test/files/jvm/innerClassAttribute/Test.scala +++ b/test/files/jvm/innerClassAttribute/Test.scala @@ -18,6 +18,14 @@ object Test extends BytecodeTest { def ownInnerClassNode(n: String) = innerClassNodes(n).filter(_.name == n).head + def testInner(cls: String, fs: (InnerClassNode => Unit)*) = { + val ns = innerClassNodes(cls) + assert(ns.length == fs.length, ns) + (ns zip fs.toList) foreach { case (n, f) => f(n) } + } + + + final case class EnclosingMethod(name: String, descriptor: String, outerClass: String) def enclosingMethod(className: String) = { val n = loadClassNode(className) @@ -318,12 +326,11 @@ object Test extends BytecodeTest { } def testA24() { - val List(defsCls, abs, conc, defsApi, defsApiImpl) = innerClassNodes("A24$DefinitionsClass") + val List(defsCls, abs, conc, defsApi) = innerClassNodes("A24$DefinitionsClass") assertMember(defsCls, "A24", "DefinitionsClass") assertMember(abs, "A24$DefinitionsClass", "Abs$") assertMember(conc, "A24$DefinitionsClass", "Conc$") assertMember(defsApi, "A24Base", "DefinitionsApi", flags = publicAbstractInterface) - assertMember(defsApiImpl, "A24Base", "DefinitionsApi$class", flags = Flags.ACC_PUBLIC | Flags.ACC_ABSTRACT) } def testSI_9105() { @@ -423,6 +430,82 @@ object Test extends BytecodeTest { assertMember(ownInnerClassNode("SI_9124$O$"), "SI_9124", "O$") } + def testImplClassesTopLevel() { + val classes = List( + "ImplClassesAreTopLevel$$anon$14", + "ImplClassesAreTopLevel$$anon$15", + "ImplClassesAreTopLevel$$anon$16", + "ImplClassesAreTopLevel$B1$class", + "ImplClassesAreTopLevel$B1", + "ImplClassesAreTopLevel$B2$1$class", + "ImplClassesAreTopLevel$B2$1", + "ImplClassesAreTopLevel$B3$1$class", + "ImplClassesAreTopLevel$B3$1", + "ImplClassesAreTopLevel$B4$class", + "ImplClassesAreTopLevel$B4$1", + "ImplClassesAreTopLevel$class", + "ImplClassesAreTopLevel") + + classes.filter(_.endsWith("$class")).foreach(assertNoEnclosingMethod) + classes.flatMap(innerClassNodes).foreach(icn => assert(!icn.name.endsWith("$class"), icn)) + + assertNoEnclosingMethod("ImplClassesAreTopLevel$B1") // member, no encl meth attr + + // no encl meth, but encl class + List("ImplClassesAreTopLevel$B2$1", "ImplClassesAreTopLevel$B3$1", + "ImplClassesAreTopLevel$$anon$14", "ImplClassesAreTopLevel$$anon$15").foreach(assertEnclosingMethod(_, "ImplClassesAreTopLevel", null, null)) + + // encl meth n + List("ImplClassesAreTopLevel$B4$1", "ImplClassesAreTopLevel$$anon$16").foreach(assertEnclosingMethod(_, "ImplClassesAreTopLevel", "n", "()Ljava/lang/Object;")) + + val an14 = assertAnonymous(_: InnerClassNode, "ImplClassesAreTopLevel$$anon$14") + val an15 = assertAnonymous(_: InnerClassNode, "ImplClassesAreTopLevel$$anon$15") + val an16 = assertAnonymous(_: InnerClassNode, "ImplClassesAreTopLevel$$anon$16") + val b1 = assertMember(_: InnerClassNode, "ImplClassesAreTopLevel", "B1", flags = publicAbstractInterface) + val b2 = assertLocal(_ : InnerClassNode, "ImplClassesAreTopLevel$B2$1", "B2$1", flags = publicAbstractInterface) + val b3 = assertLocal(_ : InnerClassNode, "ImplClassesAreTopLevel$B3$1", "B3$1", flags = publicAbstractInterface) + val b4 = assertLocal(_ : InnerClassNode, "ImplClassesAreTopLevel$B4$1", "B4$1", flags = publicAbstractInterface) + + testInner("ImplClassesAreTopLevel$$anon$14", an14, b3) + testInner("ImplClassesAreTopLevel$$anon$15", an15, b2) + testInner("ImplClassesAreTopLevel$$anon$16", an16, b4) + + testInner("ImplClassesAreTopLevel$B1$class", b1) + testInner("ImplClassesAreTopLevel$B2$1$class", b2) + testInner("ImplClassesAreTopLevel$B3$1$class", b3) + testInner("ImplClassesAreTopLevel$B4$class", b4) + + testInner("ImplClassesAreTopLevel$B1", b1) + testInner("ImplClassesAreTopLevel$B2$1", b2) + testInner("ImplClassesAreTopLevel$B3$1", b3) + testInner("ImplClassesAreTopLevel$B4$1", b4) + + testInner("ImplClassesAreTopLevel$class", an14, an15, an16) + testInner("ImplClassesAreTopLevel", an14, an15, an16, b1, b2, b3, b4) + } + + def testSpecializedClassesTopLevel() { + val cls = List( + "SpecializedClassesAreTopLevel$A$mcI$sp", + "SpecializedClassesAreTopLevel$A", + "SpecializedClassesAreTopLevel$T$", + "SpecializedClassesAreTopLevel$T$B$mcI$sp", + "SpecializedClassesAreTopLevel$T$B", + "SpecializedClassesAreTopLevel") + + // all classes are members, no local (can't test local, they crash in specialize) + cls.foreach(assertNoEnclosingMethod) + cls.flatMap(innerClassNodes).foreach(icn => assert(!icn.name.endsWith("$sp"), icn)) + + val a = assertMember(_: InnerClassNode, "SpecializedClassesAreTopLevel", "A") + val t = assertMember(_: InnerClassNode, "SpecializedClassesAreTopLevel", "T$") + val b = assertMember(_: InnerClassNode, "SpecializedClassesAreTopLevel$T$", "B", Some("SpecializedClassesAreTopLevel$T$B")) + + List("SpecializedClassesAreTopLevel$A$mcI$sp", "SpecializedClassesAreTopLevel$A").foreach(testInner(_, a)) + testInner("SpecializedClassesAreTopLevel", a, t) + List("SpecializedClassesAreTopLevel$T$", "SpecializedClassesAreTopLevel$T$B$mcI$sp", "SpecializedClassesAreTopLevel$T$B").foreach(testInner(_, t, b)) + } + def show(): Unit = { testA1() testA2() @@ -448,5 +531,7 @@ object Test extends BytecodeTest { testA24() testSI_9105() testSI_9124() + testImplClassesTopLevel() + testSpecializedClassesTopLevel() } } |