summaryrefslogtreecommitdiff
path: root/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala
diff options
context:
space:
mode:
authorLukas Rytz <lukas.rytz@gmail.com>2015-01-17 15:13:01 +0100
committerLukas Rytz <lukas.rytz@gmail.com>2015-03-11 12:53:33 -0700
commitff67161f946c515a3b0a719ce80531fa14a06a8f (patch)
tree5abee81250e7ecc12f5d08502af518c844fb4afa /test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala
parent42054a1bebcc2155f773787ffda781b497d4178b (diff)
downloadscala-ff67161f946c515a3b0a719ce80531fa14a06a8f.tar.gz
scala-ff67161f946c515a3b0a719ce80531fa14a06a8f.tar.bz2
scala-ff67161f946c515a3b0a719ce80531fa14a06a8f.zip
Find instructions that would cause an IllegalAccessError when inlined
Some instructions would cause an IllegalAccessError if they are inlined into a different class. Based on Miguel's implementation in 6efc0528c6.
Diffstat (limited to 'test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala')
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala190
1 files changed, 190 insertions, 0 deletions
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala
new file mode 100644
index 0000000000..406b8da570
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala
@@ -0,0 +1,190 @@
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+import scala.tools.asm.Opcodes._
+import org.junit.Assert._
+
+import scala.tools.asm.tree._
+import scala.tools.testing.AssertUtil._
+
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+import AsmUtils._
+
+import scala.collection.convert.decorateAsScala._
+
+@RunWith(classOf[JUnit4])
+class InlinerIllegalAccessTest {
+ val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none")
+ import compiler.genBCode.bTypes._
+
+ def addToRepo(cls: List[ClassNode]): Unit = for (c <- cls) byteCodeRepository.classes(c.name) = (c, ByteCodeRepository.Classfile)
+ def assertEmpty(ins: Option[AbstractInsnNode]) = for (i <- ins) throw new AssertionError(textify(i))
+
+ @Test
+ def typeAccessible(): Unit = {
+ val code =
+ """package a {
+ | private class C { // the Scala compiler makes all classes public
+ | def f1 = new C // NEW a/C
+ | def f2 = new Array[C](0) // ANEWARRAY a/C
+ | def f3 = new Array[Array[C]](0) // ANEWARRAY [La/C;
+ | }
+ | class D
+ |}
+ |package b {
+ | class E
+ |}
+ """.stripMargin
+
+ val allClasses = compileClasses(compiler)(code)
+ val List(cClass, dClass, eClass) = allClasses
+ assert(cClass.name == "a/C" && dClass.name == "a/D" && eClass.name == "b/E", s"${cClass.name}, ${dClass.name}, ${eClass.name}")
+ addToRepo(allClasses) // they are not on the compiler's classpath, so we add them manually to the code repo
+
+ val methods = cClass.methods.asScala.filter(_.name(0) == 'f').toList
+
+ def check(classNode: ClassNode, test: Option[AbstractInsnNode] => Unit) = {
+ for (m <- methods)
+ test(inliner.findIllegalAccess(m.instructions, classBTypeFromParsedClassfile(classNode.name)))
+ }
+
+ check(cClass, assertEmpty)
+ check(dClass, assertEmpty)
+ check(eClass, assertEmpty) // C is public, so accessible in E
+
+ byteCodeRepository.classes.clear()
+ classBTypeFromInternalName.clear()
+
+ cClass.access &= ~ACC_PUBLIC // ftw
+ addToRepo(allClasses)
+
+ // private classes can be accessed from the same package
+ check(cClass, assertEmpty)
+ check(dClass, assertEmpty) // accessing a private class in the same package is OK
+ check(eClass, {
+ case Some(ti: TypeInsnNode) if Set("a/C", "[La/C;")(ti.desc) => ()
+ // MatchError otherwise
+ })
+ }
+
+ @Test
+ def memberAccessible(): Unit = {
+ val code =
+ """package a {
+ | class C {
+ | /*public*/ def a = 0
+ | /*default*/ def b = 0
+ | protected def c = 0
+ | private def d = 0
+ |
+ | /*public static*/ def e = 0
+ | /*default static*/ def f = 0
+ | protected /*static*/ def g = 0
+ | private /*static*/ def h = 0
+ |
+ | def raC = a
+ | def rbC = b
+ | def rcC = c
+ | def rdC = d
+ | def reC = e
+ | def rfC = f
+ | def rgC = g
+ | def rhC = h
+ | }
+ |
+ | class D extends C {
+ | def rbD = b // 1: default access b, accessed in D, declared in C. can be inlined into any class in the same package as C.
+ | def rcD = c // 2: protected c, accessed in D. can be inlined into C, D or E, but not into F (F and D are unrelated).
+ |
+ | def rfD = f // 1
+ | def rgD = g // 2
+ | }
+ | class E extends D
+ |
+ | class F extends C
+ |
+ | class G
+ |}
+ |
+ |package b {
+ | class H extends a.C
+ | class I
+ |}
+ """.stripMargin
+
+ val allClasses = compileClasses(compiler)(code)
+ val List(cCl, dCl, eCl, fCl, gCl, hCl, iCl) = allClasses
+ addToRepo(allClasses)
+
+ // set flags that Scala scala doesn't (default access, static) - a hacky way to test all access modes.
+ val names = ('a' to 'h').map(_.toString).toSet
+ val List(a, b, c, d, e, f, g, h) = cCl.methods.asScala.toList.filter(m => names(m.name))
+
+ def checkAccess(a: MethodNode, expected: Int): Unit = {
+ assert((a.access & (ACC_STATIC | ACC_PUBLIC | ACC_PROTECTED | ACC_PRIVATE)) == expected, s"${a.name}, ${a.access}")
+ }
+
+ checkAccess(a, ACC_PUBLIC)
+ b.access &= ~ACC_PUBLIC; checkAccess(b, 0) // make it default access
+ c.access &= ~ACC_PUBLIC; c.access |= ACC_PROTECTED; checkAccess(c, ACC_PROTECTED) // make it protected - scalac actually never emits PROTECTED in bytecode, see javaFlags in BTypesFromSymbols
+ checkAccess(d, ACC_PRIVATE)
+
+ e.access |= ACC_STATIC; checkAccess(e, ACC_STATIC | ACC_PUBLIC)
+ f.access &= ~ACC_PUBLIC; f.access |= ACC_STATIC; checkAccess(f, ACC_STATIC)
+ g.access &= ~ACC_PUBLIC; g.access |= (ACC_STATIC | ACC_PROTECTED); checkAccess(g, ACC_STATIC | ACC_PROTECTED)
+ h.access |= ACC_STATIC; checkAccess(h, ACC_STATIC | ACC_PRIVATE)
+
+ val List(raC, rbC, rcC, rdC, reC, rfC, rgC, rhC) = cCl.methods.asScala.toList.filter(_.name(0) == 'r').sortBy(_.name)
+
+ val List(rbD, rcD, rfD, rgD) = dCl.methods.asScala.toList.filter(_.name(0) == 'r').sortBy(_.name)
+
+ def check(method: MethodNode, dest: ClassNode, test: Option[AbstractInsnNode] => Unit): Unit = {
+ test(inliner.findIllegalAccess(method.instructions, classBTypeFromParsedClassfile(dest.name)))
+ }
+
+ val cOrDOwner = (_: Option[AbstractInsnNode] @unchecked) match {
+ case Some(mi: MethodInsnNode) if Set("a/C", "a/D")(mi.owner) => ()
+ // MatchError otherwise
+ }
+
+ // PUBLIC
+
+ // public methods allowed everywhere
+ for (m <- Set(raC, reC); c <- allClasses) check(m, c, assertEmpty)
+
+ // DEFAULT ACCESS
+
+ // default access OK in same package
+ for (m <- Set(rbC, rfC, rbD, rfD); c <- allClasses) {
+ if (c.name startsWith "a/") check(m, c, assertEmpty)
+ else check(m, c, cOrDOwner)
+ }
+
+ // PROTECTED
+
+ // protected accessed in same class, or protected static accessed in subclass(rgD).
+ // can be inlined to subclasses, and classes in the same package (gCl)
+ for (m <- Set(rcC, rgC, rgD); c <- Set(cCl, dCl, eCl, fCl, gCl, hCl)) check(m, c, assertEmpty)
+
+ // protected in non-subclass and different package
+ for (m <- Set(rcC, rgC)) check(m, iCl, cOrDOwner)
+
+ // non-static protected accessed in subclass (rcD). can be inlined to related class, or classes in the same package
+ for (c <- Set(cCl, dCl, eCl, fCl, gCl)) check(rcD, c, assertEmpty)
+
+ // rcD cannot be inlined into non-related classes, if the declaration and destination are not in the same package
+ for (c <- Set(hCl, iCl)) check(rcD, c, cOrDOwner)
+
+ // PRIVATE
+
+ // privated method accesses can only be inlined in the same class
+ for (m <- Set(rdC, rhC)) check(m, cCl, assertEmpty)
+ for (m <- Set(rdC, rhC); c <- allClasses.tail) check(m, c, cOrDOwner)
+ }
+}