From 0bcc871e3f4c4331fb53ec0e7087669589a607d6 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 21 Jan 2015 19:57:45 +0100 Subject: SI-9105 Fix EnclosingMethod for classes defined in lambdas This change fixes both GenASM and GenBCode, except for the change to renaming in LamdaLift mentioned below. The reason for an inconsistent EnclosingMethod attribute was the symbol owner chain. Initially, closure class symbols don't exist, they are only created in UnCurry (delambdafy:inline). So walking the originalOwner of a definition does not yield closure classes. The commit also fixes uses of isAnonymousClass, isAnonymousFunction and isDelambdafyFunction in two ways: 1. by phase-travelling to an early phase. after flatten, the name includes the name of outer classes, so the properties may become accidentally true (they check for a substring in the name) 2. by ensuring that the (destructive) renames during LambdaLift don't make the above properties accidentally true. This was in fact the cause for SI-8900. --- test/files/jvm/innerClassAttribute.check | 30 ++++----- test/files/jvm/innerClassAttribute/Classes_1.scala | 38 +++++++++++ test/files/jvm/innerClassAttribute/Test.scala | 78 ++++++++++++++++++++-- test/files/jvm/t9105.check | 18 +++++ test/files/jvm/t9105.scala | 22 ++++++ 5 files changed, 167 insertions(+), 19 deletions(-) create mode 100644 test/files/jvm/t9105.check create mode 100644 test/files/jvm/t9105.scala (limited to 'test/files/jvm') diff --git a/test/files/jvm/innerClassAttribute.check b/test/files/jvm/innerClassAttribute.check index 20518aa49e..8395ff0b09 100644 --- a/test/files/jvm/innerClassAttribute.check +++ b/test/files/jvm/innerClassAttribute.check @@ -14,27 +14,27 @@ A19 / null / null A19 / null / null A19 / null / null -- A20 -- -A20$$anonfun$4 / null / null / 17 +A20$$anonfun$6 / null / null / 17 fun1: attribute for itself and the two child closures `() => ()` and `() => () => 1` -A20$$anonfun$4 / null / null / 17 -A20$$anonfun$4$$anonfun$apply$1 / null / null / 17 -A20$$anonfun$4$$anonfun$apply$3 / null / null / 17 +A20$$anonfun$6 / null / null / 17 +A20$$anonfun$6$$anonfun$apply$1 / null / null / 17 +A20$$anonfun$6$$anonfun$apply$3 / null / null / 17 fun2 () => (): itself and the outer closure -A20$$anonfun$4 / null / null / 17 -A20$$anonfun$4$$anonfun$apply$1 / null / null / 17 +A20$$anonfun$6 / null / null / 17 +A20$$anonfun$6$$anonfun$apply$1 / null / null / 17 fun3 () => () => (): itself, the outer closure and its child closure -A20$$anonfun$4 / null / null / 17 -A20$$anonfun$4$$anonfun$apply$3 / null / null / 17 -A20$$anonfun$4$$anonfun$apply$3$$anonfun$apply$2 / null / null / 17 +A20$$anonfun$6 / null / null / 17 +A20$$anonfun$6$$anonfun$apply$3 / null / null / 17 +A20$$anonfun$6$$anonfun$apply$3$$anonfun$apply$2 / null / null / 17 fun4: () => 1: itself and the two outer closures -A20$$anonfun$4 / null / null / 17 -A20$$anonfun$4$$anonfun$apply$3 / null / null / 17 -A20$$anonfun$4$$anonfun$apply$3$$anonfun$apply$2 / null / null / 17 +A20$$anonfun$6 / null / null / 17 +A20$$anonfun$6$$anonfun$apply$3 / null / null / 17 +A20$$anonfun$6$$anonfun$apply$3$$anonfun$apply$2 / null / null / 17 enclosing: nested closures have the apply method of the outer closure A20 / null / null -A20$$anonfun$4 / apply / ()Lscala/Function0; -A20$$anonfun$4 / apply / ()Lscala/Function0; -A20$$anonfun$4$$anonfun$apply$3 / apply / ()Lscala/Function0; +A20$$anonfun$6 / apply / ()Lscala/Function0; +A20$$anonfun$6 / apply / ()Lscala/Function0; +A20$$anonfun$6$$anonfun$apply$3 / apply / ()Lscala/Function0; #partest -Ydelambdafy:method -- A4 -- null / null / null diff --git a/test/files/jvm/innerClassAttribute/Classes_1.scala b/test/files/jvm/innerClassAttribute/Classes_1.scala index 9c3ea7f013..a2ade70c18 100644 --- a/test/files/jvm/innerClassAttribute/Classes_1.scala +++ b/test/files/jvm/innerClassAttribute/Classes_1.scala @@ -185,3 +185,41 @@ trait A24 extends A24Base { override object Conc extends A24Sym } } + +class SI_9105 { + // the EnclosingMethod attributes depend on the delambdafy strategy (inline vs method) + + // outerClass-inline enclMeth-inline outerClass-method enclMeth-method + val fun = () => { + class A // closure null (*) SI_9105 null + def m: Object = { class B; new B } // closure m$1 SI_9105 m$1 + val f: Object = { class C; new C } // closure null (*) SI_9105 null + } + def met = () => { + class D // closure null (*) SI_9105 met + def m: Object = { class E; new E } // closure m$1 SI_9105 m$1 + val f: Object = { class F; new F } // closure null (*) SI_9105 met + } + + // (*) the originalOwner chain of A (similar for D) is: SI_9105.fun.$anonfun-value.A + // we can get to the anonfun-class (created by uncurry), but not to the apply method. + // + // for C and F, the originalOwner chain is fun.$anonfun-value.f.C. at later phases, the rawowner of f is + // an apply$sp method of the closure class. we could use that as enclosing method, but it would be unsystematic + // (A / D don't have an encl meth either), and also strange to use the $sp, which is a compilation artifact. + // So using `null` looks more like the situation in the source code: C / F are nested classes of the anon-fun, and + // there's no method in between. + + def byName[T](op: => T) = 0 + + val bnV = byName { + class G // closure null (*) SI_9105 null + def m: Object = { class H; new H } // closure m$1 SI_9105 m$1 + val f: Object = { class I; new I } // closure null (*) SI_9105 null + } + def bnM = byName { + class J // closure null (*) SI_9105 bnM + def m: Object = { class K; new K } // closure m$1 SI_9105 m$1 + val f: Object = { class L; new L } // closure null (*) SI_9105 bnM + } +} diff --git a/test/files/jvm/innerClassAttribute/Test.scala b/test/files/jvm/innerClassAttribute/Test.scala index 3820048cb4..30dc412ff7 100644 --- a/test/files/jvm/innerClassAttribute/Test.scala +++ b/test/files/jvm/innerClassAttribute/Test.scala @@ -16,6 +16,8 @@ object Test extends BytecodeTest { loadClassNode(className).innerClasses.asScala.toList.sortBy(_.name) } + def ownInnerClassNode(n: String) = innerClassNodes(n).filter(_.name == n).head + final case class EnclosingMethod(name: String, descriptor: String, outerClass: String) def enclosingMethod(className: String) = { val n = loadClassNode(className) @@ -256,10 +258,10 @@ object Test extends BytecodeTest { printInnerClassNodes("A20") - val fun1 = lambdaClass("A20$$anonfun$4", "A20$lambda$1") - val fun2 = lambdaClass("A20$$anonfun$4$$anonfun$apply$1", "A20$lambda$$$nestedInAnonfun$5$1") - val fun3 = lambdaClass("A20$$anonfun$4$$anonfun$apply$3", "A20$lambda$$$nestedInAnonfun$5$2") - val fun4 = lambdaClass("A20$$anonfun$4$$anonfun$apply$3$$anonfun$apply$2", "A20$lambda$$$nestedInAnonfun$7$1") + val fun1 = lambdaClass("A20$$anonfun$6", "A20$lambda$1") + val fun2 = lambdaClass("A20$$anonfun$6$$anonfun$apply$1", "A20$lambda$$$nestedInAnonfun$5$1") + val fun3 = lambdaClass("A20$$anonfun$6$$anonfun$apply$3", "A20$lambda$$$nestedInAnonfun$5$2") + val fun4 = lambdaClass("A20$$anonfun$6$$anonfun$apply$3$$anonfun$apply$2", "A20$lambda$$$nestedInAnonfun$7$1") println("fun1: attribute for itself and the two child closures `() => ()` and `() => () => 1`") printInnerClassNodes(fun1) @@ -324,6 +326,73 @@ object Test extends BytecodeTest { assertMember(defsApiImpl, "A24Base", "DefinitionsApi$class", flags = Flags.ACC_PUBLIC | Flags.ACC_ABSTRACT) } + def testSI_9105() { + val isDelambdafyMethod = classpath.findClass("SI_9105$lambda$1").isDefined + if (isDelambdafyMethod) { + assertEnclosingMethod ("SI_9105$A$2" , "SI_9105", null , null) + assertEnclosingMethod ("SI_9105$B$5" , "SI_9105", "m$1", "()Ljava/lang/Object;") + assertEnclosingMethod ("SI_9105$C$1" , "SI_9105", null , null) + assertEnclosingMethod ("SI_9105$D$1" , "SI_9105", "met", "()Lscala/Function0;") + assertEnclosingMethod ("SI_9105$E$1" , "SI_9105", "m$3", "()Ljava/lang/Object;") + assertEnclosingMethod ("SI_9105$F$1" , "SI_9105", "met", "()Lscala/Function0;") + assertNoEnclosingMethod("SI_9105$lambda$$met$1") + assertNoEnclosingMethod("SI_9105$lambda$1") + assertNoEnclosingMethod("SI_9105") + + assertLocal(innerClassNodes("SI_9105$A$2").head, "SI_9105$A$2", "A$2") + assertLocal(innerClassNodes("SI_9105$B$5").head, "SI_9105$B$5", "B$5") + assertLocal(innerClassNodes("SI_9105$C$1").head, "SI_9105$C$1", "C$1") + assertLocal(innerClassNodes("SI_9105$D$1").head, "SI_9105$D$1", "D$1") + assertLocal(innerClassNodes("SI_9105$E$1").head, "SI_9105$E$1", "E$1") + assertLocal(innerClassNodes("SI_9105$F$1").head, "SI_9105$F$1", "F$1") + + // by-name + assertEnclosingMethod("SI_9105$G$1", "SI_9105", null , null) + assertEnclosingMethod("SI_9105$H$1", "SI_9105", "m$2", "()Ljava/lang/Object;") + assertEnclosingMethod("SI_9105$I$1", "SI_9105", null , null) + assertEnclosingMethod("SI_9105$J$1", "SI_9105", "bnM", "()I") + assertEnclosingMethod("SI_9105$K$2", "SI_9105", "m$4", "()Ljava/lang/Object;") + assertEnclosingMethod("SI_9105$L$1", "SI_9105", "bnM", "()I") + + assert(innerClassNodes("SI_9105$lambda$$met$1").isEmpty) + assert(innerClassNodes("SI_9105$lambda$1").isEmpty) + assert(innerClassNodes("SI_9105").length == 12) // the 12 local classes + } else { + // comment in innerClassAttribute/Classes_1.scala explains the difference between A / C and D / F. + assertEnclosingMethod ("SI_9105$$anonfun$4$A$2" , "SI_9105$$anonfun$4" , null , null) + assertEnclosingMethod ("SI_9105$$anonfun$4$B$5" , "SI_9105$$anonfun$4" , "m$1" , "()Ljava/lang/Object;") + assertEnclosingMethod ("SI_9105$$anonfun$4$C$1" , "SI_9105$$anonfun$4" , null , null) + assertEnclosingMethod ("SI_9105$$anonfun$met$1$D$1", "SI_9105$$anonfun$met$1", null , null) + assertEnclosingMethod ("SI_9105$$anonfun$met$1$E$1", "SI_9105$$anonfun$met$1", "m$3" , "()Ljava/lang/Object;") + assertEnclosingMethod ("SI_9105$$anonfun$met$1$F$1", "SI_9105$$anonfun$met$1", null , null) + assertEnclosingMethod ("SI_9105$$anonfun$4" , "SI_9105" , null , null) + assertEnclosingMethod ("SI_9105$$anonfun$met$1" , "SI_9105" , "met" , "()Lscala/Function0;") + assertNoEnclosingMethod("SI_9105") + + assertLocal(ownInnerClassNode("SI_9105$$anonfun$4$A$2"), "SI_9105$$anonfun$4$A$2" , "A$2") + assertLocal(ownInnerClassNode("SI_9105$$anonfun$4$B$5"), "SI_9105$$anonfun$4$B$5" , "B$5") + assertLocal(ownInnerClassNode("SI_9105$$anonfun$4$C$1"), "SI_9105$$anonfun$4$C$1" , "C$1") + assertLocal(ownInnerClassNode("SI_9105$$anonfun$met$1$D$1"), "SI_9105$$anonfun$met$1$D$1", "D$1") + assertLocal(ownInnerClassNode("SI_9105$$anonfun$met$1$E$1"), "SI_9105$$anonfun$met$1$E$1", "E$1") + assertLocal(ownInnerClassNode("SI_9105$$anonfun$met$1$F$1"), "SI_9105$$anonfun$met$1$F$1", "F$1") + + // by-name + assertEnclosingMethod("SI_9105$$anonfun$5$G$1", "SI_9105$$anonfun$5", null, null) + assertEnclosingMethod("SI_9105$$anonfun$5$H$1", "SI_9105$$anonfun$5", "m$2", "()Ljava/lang/Object;") + assertEnclosingMethod("SI_9105$$anonfun$5$I$1", "SI_9105$$anonfun$5", null, null) + assertEnclosingMethod("SI_9105$$anonfun$bnM$1$J$1", "SI_9105$$anonfun$bnM$1", null, null) + assertEnclosingMethod("SI_9105$$anonfun$bnM$1$K$2", "SI_9105$$anonfun$bnM$1", "m$4", "()Ljava/lang/Object;") + assertEnclosingMethod("SI_9105$$anonfun$bnM$1$L$1", "SI_9105$$anonfun$bnM$1", null, null) + + assertAnonymous(ownInnerClassNode("SI_9105$$anonfun$4"), "SI_9105$$anonfun$4") + assertAnonymous(ownInnerClassNode("SI_9105$$anonfun$met$1"), "SI_9105$$anonfun$met$1") + + assert(innerClassNodes("SI_9105$$anonfun$4").length == 4) // itself and three of the local classes + assert(innerClassNodes("SI_9105$$anonfun$met$1").length == 4) // itself and three of the local classes + assert(innerClassNodes("SI_9105").length == 4) // the four anon funs + } + } + def show(): Unit = { testA1() testA2() @@ -347,5 +416,6 @@ object Test extends BytecodeTest { testA22() testA23() testA24() + testSI_9105() } } diff --git a/test/files/jvm/t9105.check b/test/files/jvm/t9105.check new file mode 100644 index 0000000000..34750833f1 --- /dev/null +++ b/test/files/jvm/t9105.check @@ -0,0 +1,18 @@ +#partest !-Ydelambdafy:method +(class C$$anonfun$1$A$1,class C$$anonfun$1,null) +(class C$$anonfun$1$B$1,class C$$anonfun$1,private final java.lang.Object C$$anonfun$1.m$1()) +(class C$$anonfun$1$C$1,class C$$anonfun$1,null) +(class C$$anonfun$1$$anonfun$2$D$1,class C$$anonfun$1$$anonfun$2,null) +(class C$$anonfun$met$1$E$1,class C$$anonfun$met$1,null) +(class C$$anonfun$met$1$F$1,class C$$anonfun$met$1,private final java.lang.Object C$$anonfun$met$1.m$2()) +(class C$$anonfun$met$1$G$1,class C$$anonfun$met$1,null) +(class C$$anonfun$met$1$$anonfun$3$H$1,class C$$anonfun$met$1$$anonfun$3,null) +#partest -Ydelambdafy:method +(class C$A$1,class C,null) +(class C$B$1,class C,private final java.lang.Object C.m$1()) +(class C$C$1,class C,null) +(class C$D$1,class C,null) +(class C$E$1,class C,public scala.Function0 C.met()) +(class C$F$1,class C,private final java.lang.Object C.m$2()) +(class C$G$1,class C,public scala.Function0 C.met()) +(class C$H$1,class C,public scala.Function0 C.met()) diff --git a/test/files/jvm/t9105.scala b/test/files/jvm/t9105.scala new file mode 100644 index 0000000000..636ee8a768 --- /dev/null +++ b/test/files/jvm/t9105.scala @@ -0,0 +1,22 @@ +class C { + val fun = () => { + class A + def m: Object = { class B; new B } + val f: Object = { class C; new C } + val g = () => { class D; new D } + List[Object](new A, m, f, g()) + } + def met = () => { + class E + def m: Object = { class F; new F } + val f: Object = { class G; new G } + val g = () => { class H; new H } + List[Object](new E, m, f, g()) + } +} + +object Test extends App { + val x = new C().fun.apply() ::: new C().met.apply() + val results = x.map(_.getClass).map(cls => (cls, cls.getEnclosingClass, cls.getEnclosingMethod)) + println(results.mkString("\n")) +} -- cgit v1.2.3 From 1437aa88241d5e99900a86b25c6ce385c2708570 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Mon, 2 Feb 2015 21:04:49 +0100 Subject: SI-9124 fix EnclosingMethod of classes nested in implOnly trait defs Private trait methods are not added to the generated interface, they end up only in the implementation class. For classes nested in such methods, the EnclosingMethod attribute was incorrect. Since the EnclosingMethod attribute expresses a source-level property, but the actual enclosing method does not exist in the bytecode, we set the enclosing method to null. --- .../tools/nsc/backend/jvm/BCodeAsmCommon.scala | 6 ++++- test/files/jvm/innerClassAttribute/Classes_1.scala | 17 ++++++++++++ test/files/jvm/innerClassAttribute/Test.scala | 31 ++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) (limited to 'test/files/jvm') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala index 90d2fcf4dc..09d78fdf41 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala @@ -128,7 +128,11 @@ final class BCodeAsmCommon[G <: Global](val global: G) { assert(classSym.isClass, classSym) def enclosingMethod(sym: Symbol): Option[Symbol] = { if (sym.isClass || sym == NoSymbol) None - else if (sym.isMethod) Some(sym) + else if (sym.isMethod) { + // SI-9124, some trait methods don't exist in the generated interface. see comment in BTypes. + if (sym.owner.isTrait && sym.isImplOnly) None + else Some(sym) + } else enclosingMethod(nextEnclosing(sym)) } enclosingMethod(nextEnclosing(classSym)) diff --git a/test/files/jvm/innerClassAttribute/Classes_1.scala b/test/files/jvm/innerClassAttribute/Classes_1.scala index a2ade70c18..7b5f5d93b9 100644 --- a/test/files/jvm/innerClassAttribute/Classes_1.scala +++ b/test/files/jvm/innerClassAttribute/Classes_1.scala @@ -223,3 +223,20 @@ class SI_9105 { val f: Object = { class L; new L } // closure null (*) SI_9105 bnM } } + +trait SI_9124 { + trait A // member class, no enclosing method attribute + + new A { def f1 = 0 } // nested class, enclosing class SI_9124, no encl meth + + def f = new A { def f2 = 0 } // enclosing method is f in the interface SI_9124 + + private def g = new A { def f3 = 0 } // only encl class (SI_9124), encl meth is null because the interface SI_9124 doesn't have a method g + + object O { // member, no encl meth attribute + new A { def f4 = 0 } // enclosing class is O$, no enclosing method + } + + 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 +} diff --git a/test/files/jvm/innerClassAttribute/Test.scala b/test/files/jvm/innerClassAttribute/Test.scala index 30dc412ff7..5dacd2d830 100644 --- a/test/files/jvm/innerClassAttribute/Test.scala +++ b/test/files/jvm/innerClassAttribute/Test.scala @@ -393,6 +393,36 @@ object Test extends BytecodeTest { } } + def testSI_9124() { + val classes: Map[String, String] = { + List("SI_9124$$anon$10", + "SI_9124$$anon$11", + "SI_9124$$anon$12", + "SI_9124$$anon$8", + "SI_9124$$anon$9", + "SI_9124$O$$anon$13").map({ name => + val node = loadClassNode(name) + val fMethod = node.methods.asScala.find(_.name.startsWith("f")).get.name + (fMethod, node.name) + }).toMap + } + + // println(classes) + + assertNoEnclosingMethod("SI_9124$A") + assertEnclosingMethod(classes("f1"), "SI_9124", null, null) + assertEnclosingMethod(classes("f2"), "SI_9124", "f", "()LSI_9124$A;") + assertEnclosingMethod(classes("f3"), "SI_9124", null, null) + assertEnclosingMethod(classes("f4"), "SI_9124$O$", null, null) + assertEnclosingMethod(classes("f5"), "SI_9124", null, null) + assertEnclosingMethod(classes("f6"), "SI_9124", null, null) + assertNoEnclosingMethod("SI_9124$O$") + + assertMember(ownInnerClassNode("SI_9124$A"), "SI_9124", "A", flags = publicAbstractInterface) + classes.values.foreach(n => assertAnonymous(ownInnerClassNode(n), n)) + assertMember(ownInnerClassNode("SI_9124$O$"), "SI_9124", "O$") + } + def show(): Unit = { testA1() testA2() @@ -417,5 +447,6 @@ object Test extends BytecodeTest { testA23() testA24() testSI_9105() + testSI_9124() } } -- cgit v1.2.3 From cd8f2f327106c7e2944afa7ac8b7675262626c1e Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Mon, 2 Feb 2015 15:37:24 +0100 Subject: Fix InnerClass/EnclosingMethod for trait impl and specialized classes Trait implementation classes and specialized classes are always considered top-level in terms of the InnerClass / EnclosingMethod attributes. These attributes describe source-level properties, and such classes are a compilation artifact. Note that the same is true for delambdafy:method closure classes (they are always top-level). --- .../tools/nsc/backend/jvm/BCodeAsmCommon.scala | 24 ++++-- .../scala/tools/nsc/backend/jvm/BCodeHelpers.scala | 4 +- .../scala/tools/nsc/backend/jvm/BTypes.scala | 22 ++++++ .../tools/nsc/backend/jvm/BTypesFromSymbols.scala | 52 +++++++------ .../scala/tools/nsc/backend/jvm/GenASM.scala | 27 ++++--- test/files/jvm/innerClassAttribute/Classes_1.scala | 37 +++++++++ test/files/jvm/innerClassAttribute/Test.scala | 89 +++++++++++++++++++++- 7 files changed, 214 insertions(+), 41 deletions(-) (limited to 'test/files/jvm') 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 @@ -21,6 +21,14 @@ final class BCodeAsmCommon[G <: Global](val global: G) { SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags | MACRO } + /** + * 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() } } -- cgit v1.2.3 From b2e22fa308b2f402d78dc3d3afc33256c1d3cbba Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Tue, 3 Feb 2015 11:29:08 +0100 Subject: Fix InnerClass / EnclosingMethod for closures nested in value classes Members of value classes are moved over to the companion object early. This change ensures that closure classes nested in value classes appear that way to Java reflection. This commit also changes the EnclosingMethod attribute for classes (and anonymous functions) nested in anonymous function bodies. Before, the enclosing method was in some cases the function's apply method. Not always though: () => { class C ... val a = { class D ...} } The class C used to be nested in the function's apply method, but not D, because the value definition for a was lifted out of the apply. After this commit, we uniformly set the enclosing method of classes nested in function bodies to `null`. This is consistent with the source-level view of the code. Note that under delambdafy:method, closures never appear as enclosing classes (this didn't change in this commit). --- .../tools/nsc/backend/jvm/BCodeAsmCommon.scala | 48 +++++++++++---- .../tools/nsc/backend/jvm/BTypesFromSymbols.scala | 37 ++++++++++-- .../scala/tools/nsc/backend/jvm/GenASM.scala | 30 ++++++++-- .../scala/tools/nsc/transform/UnCurry.scala | 4 ++ .../scala/tools/nsc/interactive/Global.scala | 2 +- src/reflect/scala/reflect/internal/Symbols.scala | 7 ++- test/files/jvm/innerClassAttribute.check | 10 ++-- test/files/jvm/innerClassAttribute/Classes_1.scala | 18 ++++++ test/files/jvm/innerClassAttribute/Test.scala | 69 +++++++++++++++++++--- test/files/jvm/javaReflection.check | 2 +- 10 files changed, 189 insertions(+), 38 deletions(-) (limited to 'test/files/jvm') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala index fb9804be7f..27827015c3 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala @@ -109,6 +109,15 @@ final class BCodeAsmCommon[G <: Global](val global: G) { } } + def nextEnclosingClass(sym: Symbol): Symbol = { + if (sym.isClass) sym + else nextEnclosingClass(nextEnclosing(sym)) + } + + def classOriginallyNestedInClass(nestedClass: Symbol, enclosingClass: Symbol) ={ + nextEnclosingClass(nextEnclosing(nestedClass)) == enclosingClass + } + /** * Returns the enclosing method for non-member classes. In the following example * @@ -134,12 +143,23 @@ final class BCodeAsmCommon[G <: Global](val global: G) { */ private def enclosingMethodForEnclosingMethodAttribute(classSym: Symbol): Option[Symbol] = { assert(classSym.isClass, classSym) + + def doesNotExist(method: Symbol) = { + // (1) SI-9124, some trait methods don't exist in the generated interface. see comment in BTypes. + // (2) Value classes. Member methods of value classes exist in the generated box class. However, + // nested methods lifted into a value class are moved to the companion object and don't exist + // in the value class itself. We can identify such nested methods: the initial enclosing class + // is a value class, but the current owner is some other class (the module class). + method.owner.isTrait && method.isImplOnly || { // (1) + val enclCls = nextEnclosingClass(method) + exitingPickler(enclCls.isDerivedValueClass) && method.owner != enclCls // (2) + } + } + def enclosingMethod(sym: Symbol): Option[Symbol] = { if (sym.isClass || sym == NoSymbol) None else if (sym.isMethod) { - // SI-9124, some trait methods don't exist in the generated interface. see comment in BTypes. - if (sym.owner.isTrait && sym.isImplOnly) None - else Some(sym) + if (doesNotExist(sym)) None else Some(sym) } else enclosingMethod(nextEnclosing(sym)) } @@ -152,11 +172,7 @@ final class BCodeAsmCommon[G <: Global](val global: G) { */ private def enclosingClassForEnclosingMethodAttribute(classSym: Symbol): Symbol = { assert(classSym.isClass, classSym) - def enclosingClass(sym: Symbol): Symbol = { - if (sym.isClass) sym - else enclosingClass(nextEnclosing(sym)) - } - val r = enclosingClass(nextEnclosing(classSym)) + val r = nextEnclosingClass(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 @@ -175,10 +191,20 @@ final class BCodeAsmCommon[G <: Global](val global: G) { def enclosingMethodAttribute(classSym: Symbol, classDesc: Symbol => String, methodDesc: Symbol => String): Option[EnclosingMethodEntry] = { // 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)})") + val enclosingClass = enclosingClassForEnclosingMethodAttribute(classSym) + val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym) match { + case some @ Some(m) => + if (m.owner != enclosingClass) { + // This should never happen. In case it does, it prevents emitting an invalid + // EnclosingMethod attribute: if the attribute specifies an enclosing method, + // it needs to exist in the specified enclosing class. + devWarning(s"the owner of the enclosing method ${m.locationString} should be the same as the enclosing class $enclosingClass") + None + } else some + case none => none + } Some(EnclosingMethodEntry( - classDesc(enclosingClassForEnclosingMethodAttribute(classSym)), + classDesc(enclosingClass), methodOpt.map(_.javaSimpleName.toString).orNull, methodOpt.map(methodDesc).orNull)) } else { diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index 52d5cafc9c..2af665d31c 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -125,11 +125,21 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { * nested classes, but NOT nested in C, that are used within C. */ val nestedClassSymbols = { + val linkedClass = exitingPickler(classSym.linkedClassOfClass) // linkedCoC does not work properly in late phases + // 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 = { - val nested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(classSym)) + val allNested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(classSym)) + val nested = { + // Classes nested in value classes are nested in the companion at this point. For InnerClass / + // EnclosingMethod, we use the value class as the outer class. So we remove nested classes + // from the companion that were originally nested in the value class. + if (exitingPickler(linkedClass.isDerivedValueClass)) allNested.filterNot(classOriginallyNestedInClass(_, linkedClass)) + else allNested + } + 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. @@ -158,13 +168,28 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { // 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)) + val javaCompatMembers = { + 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 + exitingPickler(memberClassesForInnerClassTable(linkedClass)) + else + Nil + } + + // Classes nested in value classes are nested in the companion at this point. For InnerClass / + // EnclosingMethod we use the value class as enclosing class. Here we search nested classes + // in the companion that were originally nested in the value class, and we add them as nested + // in the value class. + val valueClassCompanionMembers = { + if (linkedClass != NoSymbol && exitingPickler(classSym.isDerivedValueClass)) { + val moduleMemberClasses = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(linkedClass)) + moduleMemberClasses.filter(classOriginallyNestedInClass(_, classSym)) + } else + Nil + } + + javaCompatMembers ++ valueClassCompanionMembers } nestedClasses ++ companionModuleMembers diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index 0543929aef..707336e5de 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -700,30 +700,48 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => else innerSym.rawname + innerSym.moduleSuffix } + val linkedClass = exitingPickler(csym.linkedClassOfClass) // linkedCoC does not work properly in late phases + innerClassBuffer ++= { val members = exitingPickler(memberClassesForInnerClassTable(csym)) // lambdalift makes all classes (also local, anonymous) members of their enclosing class val allNested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(csym)) + val nested = { + // Classes nested in value classes are nested in the companion at this point. For InnerClass / + // EnclosingMethod, we use the value class as the outer class. So we remove nested classes + // from the companion that were originally nested in the value class. + if (exitingPickler(linkedClass.isDerivedValueClass)) allNested.filterNot(classOriginallyNestedInClass(_, linkedClass)) + else allNested + } - // 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. + // 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 + else if (isTopLevelModule(csym)) nested diff members + else nested } if (!considerAsTopLevelImplementationArtifact(csym)) { - // If this is a top-level non-impl class, add members of the companion object. + // If this is a top-level non-impl class, add members of the companion object. These are the + // classes for which we change the InnerClass entry to allow using them from Java. // 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)) } + + // Classes nested in value classes are nested in the companion at this point. For InnerClass / + // EnclosingMethod we use the value class as enclosing class. Here we search nested classes + // in the companion that were originally nested in the value class, and we add them as nested + // in the value class. + if (linkedClass != NoSymbol && exitingPickler(csym.isDerivedValueClass)) { + val moduleMemberClasses = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(linkedClass)) + innerClassBuffer ++= moduleMemberClasses.filter(classOriginallyNestedInClass(_, csym)) + } } val allInners: List[Symbol] = innerClassBuffer.toList filterNot deadCode.elidedClosures diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index 3544dc9966..e1cf53059a 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -225,6 +225,10 @@ abstract class UnCurry extends InfoTransform if (inlineFunctionExpansion || !canUseDelamdafyMethod) { val parents = addSerializable(abstractFunctionForFunctionType(fun.tpe)) val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag) addAnnotation SerialVersionUIDAnnotation + // The original owner is used in the backend for the EnclosingMethod attribute. If fun is + // nested in a value-class method, its owner was already changed to the extension method. + // Saving the original owner allows getting the source structure from the class symbol. + defineOriginalOwner(anonClass, fun.symbol.originalOwner) anonClass setInfo ClassInfoType(parents, newScope, anonClass) val applyMethodDef = mkMethod(anonClass, nme.apply) diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index 9b34a39e02..a3d0346f81 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -152,7 +152,7 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") // don't keep the original owner in presentation compiler runs // (the map will grow indefinitely, and the only use case is the backend) - override protected def saveOriginalOwner(sym: Symbol) { } + override def defineOriginalOwner(sym: Symbol, owner: Symbol): Unit = { } override def forInteractive = true override protected def synchronizeNames = true diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index d5fc52abbf..d23a102b28 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -66,14 +66,19 @@ trait Symbols extends api.Symbols { self: SymbolTable => // when under some flag. Define per-phase invariants for owner/owned relationships, // e.g. after flatten all classes are owned by package classes, there are lots and // lots of these to be declared (or more realistically, discovered.) + // could be private since 2.11.6, but left protected to avoid potential breakages (eg ensime) protected def saveOriginalOwner(sym: Symbol): Unit = { // some synthetic symbols have NoSymbol as owner initially if (sym.owner != NoSymbol) { if (originalOwnerMap contains sym) () - else originalOwnerMap(sym) = sym.rawowner + else defineOriginalOwner(sym, sym.rawowner) } } + def defineOriginalOwner(sym: Symbol, owner: Symbol): Unit = { + originalOwnerMap(sym) = owner + } + def symbolOf[T: WeakTypeTag]: TypeSymbol = weakTypeOf[T].typeSymbolDirect.asType abstract class SymbolContextApiImpl extends SymbolApi { diff --git a/test/files/jvm/innerClassAttribute.check b/test/files/jvm/innerClassAttribute.check index 8395ff0b09..bb532e4f36 100644 --- a/test/files/jvm/innerClassAttribute.check +++ b/test/files/jvm/innerClassAttribute.check @@ -30,11 +30,11 @@ fun4: () => 1: itself and the two outer closures A20$$anonfun$6 / null / null / 17 A20$$anonfun$6$$anonfun$apply$3 / null / null / 17 A20$$anonfun$6$$anonfun$apply$3$$anonfun$apply$2 / null / null / 17 -enclosing: nested closures have the apply method of the outer closure +enclosing: nested closures have outer class defined, but no outer method A20 / null / null -A20$$anonfun$6 / apply / ()Lscala/Function0; -A20$$anonfun$6 / apply / ()Lscala/Function0; -A20$$anonfun$6$$anonfun$apply$3 / apply / ()Lscala/Function0; +A20$$anonfun$6 / null / null +A20$$anonfun$6 / null / null +A20$$anonfun$6$$anonfun$apply$3 / null / null #partest -Ydelambdafy:method -- A4 -- null / null / null @@ -47,7 +47,7 @@ fun1: attribute for itself and the two child closures `() => ()` and `() => () = fun2 () => (): itself and the outer closure fun3 () => () => (): itself, the outer closure and its child closure fun4: () => 1: itself and the two outer closures -enclosing: nested closures have the apply method of the outer closure +enclosing: nested closures have outer class defined, but no outer method null / null / null null / null / null null / null / null diff --git a/test/files/jvm/innerClassAttribute/Classes_1.scala b/test/files/jvm/innerClassAttribute/Classes_1.scala index e58fd6e26f..fb1f32aa3d 100644 --- a/test/files/jvm/innerClassAttribute/Classes_1.scala +++ b/test/files/jvm/innerClassAttribute/Classes_1.scala @@ -277,3 +277,21 @@ class SpecializedClassesAreTopLevel { // new D[Int] // } } + +object NestedInValueClass { + // note that we can only test anonymous functions, nested classes are not allowed inside value classes + class A(val arg: String) extends AnyVal { + // A has InnerClass entries for the two closures (and for A and A$). not for B / C + def f = { + def g = List().map(x => (() => x)) // outer class A, no outer method (g is moved to the companion, doesn't exist in A) + g.map(x => (() => x)) // outer class A, outer method f + } + // statements and field declarations are not allowed in value classes + } + + object A { + // A$ has InnerClass entries for B, C, A, A$. Also for the closures above, because they are referenced in A$'s bytecode. + class B // member class of A$ + def f = { class C; new C } // outer class A$, outer method f + } +} diff --git a/test/files/jvm/innerClassAttribute/Test.scala b/test/files/jvm/innerClassAttribute/Test.scala index 16e5d40052..bc9aa2376a 100644 --- a/test/files/jvm/innerClassAttribute/Test.scala +++ b/test/files/jvm/innerClassAttribute/Test.scala @@ -225,7 +225,7 @@ object Test extends BytecodeTest { assertAnonymous(anon1, "A18$$anon$5") assertAnonymous(anon2, "A18$$anon$6") - assertLocal(a, "A18$A$1", "A$1") + assertLocal(a, "A18$A$2", "A$2") assertLocal(b, "A18$B$4", "B$4") assertEnclosingMethod( @@ -236,7 +236,7 @@ object Test extends BytecodeTest { "A18", "g$1", "()V") assertEnclosingMethod( - "A18$A$1", + "A18$A$2", "A18", "g$1", "()V") assertEnclosingMethod( "A18$B$4", @@ -280,7 +280,7 @@ object Test extends BytecodeTest { println("fun4: () => 1: itself and the two outer closures") printInnerClassNodes(fun4) - println("enclosing: nested closures have the apply method of the outer closure") + println("enclosing: nested closures have outer class defined, but no outer method") printEnclosingMethod(fun1) printEnclosingMethod(fun2) printEnclosingMethod(fun3) @@ -336,7 +336,7 @@ object Test extends BytecodeTest { def testSI_9105() { val isDelambdafyMethod = classpath.findClass("SI_9105$lambda$1").isDefined if (isDelambdafyMethod) { - assertEnclosingMethod ("SI_9105$A$2" , "SI_9105", null , null) + assertEnclosingMethod ("SI_9105$A$3" , "SI_9105", null , null) assertEnclosingMethod ("SI_9105$B$5" , "SI_9105", "m$1", "()Ljava/lang/Object;") assertEnclosingMethod ("SI_9105$C$1" , "SI_9105", null , null) assertEnclosingMethod ("SI_9105$D$1" , "SI_9105", "met", "()Lscala/Function0;") @@ -346,7 +346,7 @@ object Test extends BytecodeTest { assertNoEnclosingMethod("SI_9105$lambda$1") assertNoEnclosingMethod("SI_9105") - assertLocal(innerClassNodes("SI_9105$A$2").head, "SI_9105$A$2", "A$2") + assertLocal(innerClassNodes("SI_9105$A$3").head, "SI_9105$A$3", "A$3") assertLocal(innerClassNodes("SI_9105$B$5").head, "SI_9105$B$5", "B$5") assertLocal(innerClassNodes("SI_9105$C$1").head, "SI_9105$C$1", "C$1") assertLocal(innerClassNodes("SI_9105$D$1").head, "SI_9105$D$1", "D$1") @@ -366,7 +366,7 @@ object Test extends BytecodeTest { assert(innerClassNodes("SI_9105").length == 12) // the 12 local classes } else { // comment in innerClassAttribute/Classes_1.scala explains the difference between A / C and D / F. - assertEnclosingMethod ("SI_9105$$anonfun$4$A$2" , "SI_9105$$anonfun$4" , null , null) + assertEnclosingMethod ("SI_9105$$anonfun$4$A$3" , "SI_9105$$anonfun$4" , null , null) assertEnclosingMethod ("SI_9105$$anonfun$4$B$5" , "SI_9105$$anonfun$4" , "m$1" , "()Ljava/lang/Object;") assertEnclosingMethod ("SI_9105$$anonfun$4$C$1" , "SI_9105$$anonfun$4" , null , null) assertEnclosingMethod ("SI_9105$$anonfun$met$1$D$1", "SI_9105$$anonfun$met$1", null , null) @@ -376,7 +376,7 @@ object Test extends BytecodeTest { assertEnclosingMethod ("SI_9105$$anonfun$met$1" , "SI_9105" , "met" , "()Lscala/Function0;") assertNoEnclosingMethod("SI_9105") - assertLocal(ownInnerClassNode("SI_9105$$anonfun$4$A$2"), "SI_9105$$anonfun$4$A$2" , "A$2") + assertLocal(ownInnerClassNode("SI_9105$$anonfun$4$A$3"), "SI_9105$$anonfun$4$A$3" , "A$3") assertLocal(ownInnerClassNode("SI_9105$$anonfun$4$B$5"), "SI_9105$$anonfun$4$B$5" , "B$5") assertLocal(ownInnerClassNode("SI_9105$$anonfun$4$C$1"), "SI_9105$$anonfun$4$C$1" , "C$1") assertLocal(ownInnerClassNode("SI_9105$$anonfun$met$1$D$1"), "SI_9105$$anonfun$met$1$D$1", "D$1") @@ -506,6 +506,60 @@ object Test extends BytecodeTest { List("SpecializedClassesAreTopLevel$T$", "SpecializedClassesAreTopLevel$T$B$mcI$sp", "SpecializedClassesAreTopLevel$T$B").foreach(testInner(_, t, b)) } + def testNestedInValueClass() { + List( + "NestedInValueClass", + "NestedInValueClass$", + "NestedInValueClass$A", + "NestedInValueClass$A$", + "NestedInValueClass$A$B").foreach(assertNoEnclosingMethod) + + assertEnclosingMethod("NestedInValueClass$A$C$2", "NestedInValueClass$A$", "f", "()Ljava/lang/Object;") + + type I = InnerClassNode + val a = assertMember(_: I, "NestedInValueClass", "A", flags = publicStatic | Flags.ACC_FINAL) + val am = assertMember(_: I, "NestedInValueClass", "A$", flags = publicStatic) + val b = assertMember(_: I, "NestedInValueClass$A$", "B", Some("NestedInValueClass$A$B"), flags = publicStatic) + val c = assertLocal(_: I, "NestedInValueClass$A$C$2", "C$2") + + testInner("NestedInValueClass$") + testInner("NestedInValueClass", a, am) + testInner("NestedInValueClass$A$B", am, b) + testInner("NestedInValueClass$A$C$2", am, c) + + val isDelambdafyMethod = classpath.findClass("NestedInValueClass$A$lambda$$f$extension$1").isDefined + if (isDelambdafyMethod) { + List( + "NestedInValueClass$A$lambda$$g$2$1", + "NestedInValueClass$A$lambda$$f$extension$1", + "NestedInValueClass$A$lambda$$$nestedInAnonfun$13$1", + "NestedInValueClass$A$lambda$$$nestedInAnonfun$15$1").foreach(assertNoEnclosingMethod) + testInner("NestedInValueClass$A", a, am) + testInner("NestedInValueClass$A$", a, am, b, c) + testInner("NestedInValueClass$A$lambda$$g$2$1", am) + testInner("NestedInValueClass$A$lambda$$f$extension$1", am) + testInner("NestedInValueClass$A$lambda$$$nestedInAnonfun$13$1", am) + testInner("NestedInValueClass$A$lambda$$$nestedInAnonfun$15$1", am) + } else { + assertEnclosingMethod("NestedInValueClass$A$$anonfun$g$2$1" , "NestedInValueClass$A" , null, null) + assertEnclosingMethod("NestedInValueClass$A$$anonfun$g$2$1$$anonfun$apply$4" , "NestedInValueClass$A$$anonfun$g$2$1" , null, null) + assertEnclosingMethod("NestedInValueClass$A$$anonfun$f$extension$1" , "NestedInValueClass$A" , "f", "()Lscala/collection/immutable/List;") + assertEnclosingMethod("NestedInValueClass$A$$anonfun$f$extension$1$$anonfun$apply$5", "NestedInValueClass$A$$anonfun$f$extension$1", null, null) + + val gfun = assertAnonymous(_: I, "NestedInValueClass$A$$anonfun$g$2$1") + val ffun = assertAnonymous(_: I, "NestedInValueClass$A$$anonfun$f$extension$1") + val gfunfun = assertAnonymous(_: I, "NestedInValueClass$A$$anonfun$g$2$1$$anonfun$apply$4") + val ffunfun = assertAnonymous(_: I, "NestedInValueClass$A$$anonfun$f$extension$1$$anonfun$apply$5") + + testInner("NestedInValueClass$A", a, am, ffun, gfun) + testInner("NestedInValueClass$A$", a, am, ffun, gfun, b, c) + testInner("NestedInValueClass$A$$anonfun$g$2$1", a, am, gfun, gfunfun) + testInner("NestedInValueClass$A$$anonfun$g$2$1$$anonfun$apply$4", am, gfun, gfunfun) + testInner("NestedInValueClass$A$$anonfun$f$extension$1", a, am, ffun, ffunfun) + testInner("NestedInValueClass$A$$anonfun$f$extension$1$$anonfun$apply$5", am, ffun, ffunfun) + } + } + def show(): Unit = { testA1() testA2() @@ -533,5 +587,6 @@ object Test extends BytecodeTest { testSI_9124() testImplClassesTopLevel() testSpecializedClassesTopLevel() + testNestedInValueClass() } } diff --git a/test/files/jvm/javaReflection.check b/test/files/jvm/javaReflection.check index d40599507d..8180ecff8a 100644 --- a/test/files/jvm/javaReflection.check +++ b/test/files/jvm/javaReflection.check @@ -5,7 +5,7 @@ A$$anonfun$$lessinit$greater$1 / null (canon) / $anonfun$$lessinit$greater$1 (si - properties : true (local) / false (member) A$$anonfun$$lessinit$greater$1$$anonfun$apply$1 / null (canon) / $anonfun$apply$1 (simple) - declared cls: List() -- enclosing : null (declaring cls) / class A$$anonfun$$lessinit$greater$1 (cls) / null (constr) / public final scala.Function0 A$$anonfun$$lessinit$greater$1.apply() (meth) +- enclosing : null (declaring cls) / class A$$anonfun$$lessinit$greater$1 (cls) / null (constr) / null (meth) - properties : true (local) / false (member) A$$anonfun$2 / null (canon) / $anonfun$2 (simple) - declared cls: List() -- cgit v1.2.3 From c283ce145a7b34f18deeeac5c3de2b102169e074 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Tue, 3 Feb 2015 23:00:51 +0100 Subject: Test java reflection on scala library / reflect / compiler jars Run a number of Java reflection operations on all classes in scala library / reflect / compiler. The test is based on a draft by Jason. --- build-ant-macros.xml | 2 + .../jvm/innerClassEnclMethodJavaReflection.scala | 65 ++++++++++++++++++++++ versions.properties | 2 +- 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 test/files/jvm/innerClassEnclMethodJavaReflection.scala (limited to 'test/files/jvm') diff --git a/build-ant-macros.xml b/build-ant-macros.xml index 609f106d09..259d6a6eb6 100644 --- a/build-ant-macros.xml +++ b/build-ant-macros.xml @@ -751,6 +751,7 @@ + @@ -759,6 +760,7 @@ kinds="@{kinds}" colors="@{colors}" scalacOpts="@{scalacOpts}" + javaOpts="@{javaOpts}" compilationpath="@{pcp}"/> diff --git a/test/files/jvm/innerClassEnclMethodJavaReflection.scala b/test/files/jvm/innerClassEnclMethodJavaReflection.scala new file mode 100644 index 0000000000..ee39cb43bf --- /dev/null +++ b/test/files/jvm/innerClassEnclMethodJavaReflection.scala @@ -0,0 +1,65 @@ +import scala.reflect.io._ +import java.net.URLClassLoader + +object Test extends App { + val jarsOrDirectories = Set("partest.lib", "partest.reflect", "partest.comp") map sys.props + + object AllowedMissingClass { + // Some classes in scala-compiler.jar have references to jline / ant classes, which seem to be + // not on the classpath. We just skip over those classes. + // PENDING: for now we also allow missing $anonfun classes: the optimizer may eliminate some closures + // that are refferred to in EnclosingClass attributes. SI-9136 + val allowedMissingPackages = Set("jline", "org.apache.tools.ant", "$anonfun") + + def ok(t: Throwable) = { + allowedMissingPackages.exists(p => t.getMessage.replace('/', '.').contains(p)) + } + + def unapply(t: Throwable): Option[Throwable] = t match { + case _: NoClassDefFoundError | _: ClassNotFoundException | _: TypeNotPresentException if ok(t) => Some(t) + case _ => None + } + } + + jarsOrDirectories foreach testClasses + + def testClasses(jarOrDirectory: String): Unit = { + val classPath = AbstractFile.getDirectory(new java.io.File(jarOrDirectory)) + + def flatten(f: AbstractFile): Iterator[AbstractFile] = + if (f.isClassContainer) f.iterator.flatMap(flatten) + else Iterator(f) + + val classFullNames = flatten(classPath).filter(_.hasExtension("class")).map(_.path.replace("/", ".").replaceAll(".class$", "")) + + // it seems that Class objects can only be GC'd together with their class loader + // (http://stackoverflow.com/questions/2433261/when-and-how-are-classes-garbage-collected-in-java) + // if we just use the same class loader for the entire test (Class.forName), we run out of PermGen + // even with that, we still neeed a PermGen of 90M or so, the default 64 is not enough. I tried + // using one class loader per 100 classes, but that didn't help, the classes didn't get GC'd. + val classLoader = new URLClassLoader(Array(classPath.toURL)) + + val faulty = new collection.mutable.ListBuffer[(String, Throwable)] + + def tryGetClass(name: String) = try { + Some[Class[_]](classLoader.loadClass(name)) + } catch { + case AllowedMissingClass(_) => None + } + + for (name <- classFullNames; cls <- tryGetClass(name)) { + try { + cls.getEnclosingMethod + cls.getEnclosingClass + cls.getEnclosingConstructor + cls.getDeclaredClasses + } catch { + case AllowedMissingClass(_) => + case t: Throwable => faulty += ((name, t)) + } + } + + if (faulty.nonEmpty) + println(faulty.toList mkString "\n") + } +} diff --git a/versions.properties b/versions.properties index 88271da4eb..64b5e51f32 100644 --- a/versions.properties +++ b/versions.properties @@ -27,7 +27,7 @@ actors-migration.version.number=1.1.0 jline.version=2.12 # external modules, used internally (not shipped) -partest.version.number=1.0.3 +partest.version.number=1.0.5 scalacheck.version.number=1.11.4 # TODO: modularize the compiler -- cgit v1.2.3