import scala.tools.partest.BytecodeTest import scala.tools.asm import asm.tree.{ClassNode, InnerClassNode} import asm.{Opcodes => Flags} import scala.collection.JavaConverters._ object Test extends BytecodeTest { def assertSame(a: Any, b: Any) = { assert(a == b, s"\na: $a\nb: $b") } val publicStatic = Flags.ACC_PUBLIC | Flags.ACC_STATIC val publicAbstractInterface = Flags.ACC_PUBLIC | Flags.ACC_ABSTRACT | Flags.ACC_INTERFACE def innerClassNodes(className: String): List[InnerClassNode] = { loadClassNode(className).innerClasses.asScala.toList.sortBy(_.name) } final case class EnclosingMethod(name: String, descriptor: String, outerClass: String) def enclosingMethod(className: String) = { val n = loadClassNode(className) EnclosingMethod(n.outerMethod, n.outerMethodDesc, n.outerClass) } def assertMember(node: InnerClassNode, outer: String, inner: String, name: Option[String] = None, flags: Int = Flags.ACC_PUBLIC) = { assertSame(node.name, name.getOrElse(s"$outer$$$inner")) assertSame(node.outerName, outer) assertSame(node.innerName, inner) assertSame(node.access, flags) } def assertAnonymous(node: InnerClassNode, name: String, flags: Int = Flags.ACC_PUBLIC | Flags.ACC_FINAL) = { assertSame(node.name, name) assertSame(node.outerName, null) assertSame(node.innerName, null) assertSame(node.access, flags) } def assertLocal(node: InnerClassNode, name: String, inner: String, flags: Int = Flags.ACC_PUBLIC) = { assertSame(node.name, name) assertSame(node.outerName, null) assertSame(node.innerName, inner) assertSame(node.access, flags) } def assertEnclosingMethod(className: String, outerClass: String, name: String, descriptor: String) = { val encl = enclosingMethod(className) assertSame(encl.outerClass, outerClass) assertSame(encl.name, name) assertSame(encl.descriptor, descriptor) } def assertNoEnclosingMethod(className: String) = { assertSame(enclosingMethod(className).outerClass, null) } def printInnerClassNodes(className: String) = { for (n <- innerClassNodes(className)) { println(s"${n.name} / ${n.outerName} / ${n.innerName} / ${n.access}") } } def printEnclosingMethod(className: String) = { val e = enclosingMethod(className) println(s"${e.outerClass} / ${e.name} / ${e.descriptor}") } def lambdaClass(anonfunName: String, lambdaName: String): String = { if (classpath.findClass(anonfunName).isDefined) anonfunName else lambdaName } def testA1() = { val List(b1) = innerClassNodes("A1") assertMember(b1, "A1", "B") val List(b2) = innerClassNodes("A1$B") assertMember(b2, "A1", "B") } def testA2() = { val List(b1) = innerClassNodes("A2") assertMember(b1, "A2", "B$") val List(b2) = innerClassNodes("A2$B$") assertMember(b2, "A2", "B$") } def testA3() = { // 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() = { println("-- A4 --") printInnerClassNodes("A4") val fun = lambdaClass("A4$$anonfun$f$1", "A4$lambda$$f$1") printInnerClassNodes(fun) printEnclosingMethod(fun) } def testA5() = { val List(b1) = innerClassNodes("A5") assertLocal(b1, "A5$B$2$", "B$2$") val List(b2) = innerClassNodes("A5$B$2$") assertLocal(b2, "A5$B$2$", "B$2$") assertEnclosingMethod( "A5$B$2$", "A5", "f", "()Ljava/lang/Object;") } def testA6() = { val List(tt1) = innerClassNodes("A6") assertMember(tt1, "A6", "TT", flags = publicAbstractInterface) val List() = innerClassNodes("A6$class") val List(tt2) = innerClassNodes("A6$TT") assertMember(tt2, "A6", "TT", flags = publicAbstractInterface) } def testA7() = { val List() = innerClassNodes("A7") } def testA8() = { val List(tt) = innerClassNodes("A8") assertMember(tt, "A6", "TT", flags = publicAbstractInterface) } def testA10() = { val List() = innerClassNodes("A10") } def testA11() = { val List(ann) = innerClassNodes("A11") // in the java class file, the INNERCLASS attribute has more flags (public | static | abstract | interface | annotation) // the scala compiler has its own interpretation of java annotations ant their flags.. it only emits publicStatic. assertMember(ann, "JavaAnnot_1", "Ann", flags = publicStatic) } def testA13() = { val List(b, c) = innerClassNodes("A13") assertMember(b, "A12", "B$", flags = publicStatic) assertMember(c, "A12$B$", "C", name = Some("A12$B$C"), flags = publicStatic) } def testA14() = { val List(anon, k) = innerClassNodes("A14") assertLocal(k, "A14$K$1", "K$1") assertEnclosingMethod( "A14$K$1", "A14", "f", "()Ljava/lang/Object;") assertAnonymous(anon, "A14$$anon$1") assertEnclosingMethod( "A14$$anon$1", "A14", "g", "()V") } def testA15() = { // 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") assertMember(c, "A15$B$3", "C") assertEnclosingMethod( "A15$B$3", "A15$", "f", "()V") assertNoEnclosingMethod("A15$B$3$C") } def testA16() = { val List(anon1, anon2, anon3, u, v) = innerClassNodes("A16") assertAnonymous(anon1, "A16$$anon$2") assertAnonymous(anon2, "A16$$anon$3") assertAnonymous(anon3, "A16$$anon$4") assertLocal(u, "A16$U$1", "U$1") assertLocal(v, "A16$V$1", "V$1") assertEnclosingMethod( "A16$$anon$2", "A16", null, null) assertEnclosingMethod( "A16$$anon$3", "A16", null, null) assertEnclosingMethod( "A16$$anon$4", "A16", null, null) assertEnclosingMethod( "A16$U$1", "A16", null, null) assertEnclosingMethod( "A16$V$1", "A16", null, null) } def testA17() = { val List(b, c) = innerClassNodes("A17$B$") assertMember(b, "A17", "B$") assertMember(c, "A17$B$", "C", name = Some("A17$B$C")) // not static, has an outer pointer. } def testA18() = { val List(anon1, anon2, a, b) = innerClassNodes("A18") assertAnonymous(anon1, "A18$$anon$5") assertAnonymous(anon2, "A18$$anon$6") assertLocal(a, "A18$A$1", "A$1") assertLocal(b, "A18$B$4", "B$4") assertEnclosingMethod( "A18$$anon$5", "A18", "g$1", "()V") assertEnclosingMethod( "A18$$anon$6", "A18", "g$1", "()V") assertEnclosingMethod( "A18$A$1", "A18", "g$1", "()V") assertEnclosingMethod( "A18$B$4", "A18", "g$1", "()V") } def testA19() = { println("-- A19 --") printInnerClassNodes("A19") val fun1 = lambdaClass("A19$$anonfun$1", "A19$lambda$1") val fun2 = lambdaClass("A19$$anonfun$2", "A19$lambda$2") val fun3 = lambdaClass("A19$$anonfun$3", "A19$lambda$3") printInnerClassNodes(fun1) printInnerClassNodes(fun2) printInnerClassNodes(fun3) printEnclosingMethod(fun1) printEnclosingMethod(fun2) printEnclosingMethod(fun3) } def testA20() = { println("-- A20 --") 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") println("fun1: attribute for itself and the two child closures `() => ()` and `() => () => 1`") printInnerClassNodes(fun1) println("fun2 () => (): itself and the outer closure") printInnerClassNodes(fun2) println("fun3 () => () => (): itself, the outer closure and its child closure") printInnerClassNodes(fun3) println("fun4: () => 1: itself and the two outer closures") printInnerClassNodes(fun4) println("enclosing: nested closures have the apply method of the outer closure") printEnclosingMethod(fun1) printEnclosingMethod(fun2) printEnclosingMethod(fun3) printEnclosingMethod(fun4) } def testA21() = { val List(i1c, i2c, i3c, j1) = innerClassNodes("A21") assertMember(i1c, "A21", "I1") assertMember(i2c, "A21", "I2", flags = publicStatic) assertMember(i3c, "A21", "I3$", flags = publicStatic) assertLocal(j1, "A21$J1$1", "J1$1") 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! val List(i3x, j2x) = innerClassNodes("A21$I3$J2") assertMember(j2x, "A21$I3$", "J2", name = Some("A21$I3$J2"), flags = publicStatic) assertNoEnclosingMethod("A21$I3$J2") assertEnclosingMethod("A21$J3$1", "A21$", "g", "()V") assertEnclosingMethod("A21$J4$1", "A21$", null, null) assertEnclosingMethod("A21$J5$1", "A21$", null, null) } def testA22() = { val List(cc) = innerClassNodes("A22$C") assertMember(cc, "A22", "C") val List(cm, d) = innerClassNodes("A22$C$") assertMember(cm, "A22", "C$") assertMember(d, "A22$C$", "D", name = Some("A22$C$D")) } def testA23() { val List(c, d, e, f, g) = innerClassNodes("A23") assertMember(c, "Java_A_1", "C", flags = publicStatic) assertMember(d, "Java_A_1$C", "D", flags = publicStatic) assertMember(e, "Java_A_1$C", "E") assertMember(f, "Java_A_1", "F") assertMember(g, "Java_A_1$F", "G") } def testA24() { val List(defsCls, abs, conc, defsApi, defsApiImpl) = 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 show(): Unit = { testA1() testA2() testA3() testA4() testA5() testA6() testA7() testA8() testA10() testA11() testA13() testA14() testA15() testA16() testA17() testA18() testA19() testA20() testA21() testA22() testA23() testA24() } }