diff options
author | Lukas Rytz <lukas.rytz@typesafe.com> | 2016-04-20 15:49:05 +0200 |
---|---|---|
committer | Lukas Rytz <lukas.rytz@typesafe.com> | 2016-04-20 15:49:05 +0200 |
commit | d24e298cbd955101a6b4342602b32ed37643dfb0 (patch) | |
tree | fb001a420a6fde6593ee263ca47169ace2fa638c /test/junit | |
parent | 3c398f0af9b8bf18f292f1b514a2af453d36cdd8 (diff) | |
parent | b61c35c38931be3b690bce92d5db9647802b1b34 (diff) | |
download | scala-d24e298cbd955101a6b4342602b32ed37643dfb0.tar.gz scala-d24e298cbd955101a6b4342602b32ed37643dfb0.tar.bz2 scala-d24e298cbd955101a6b4342602b32ed37643dfb0.zip |
Merge pull request #5096 from lrytz/traitParents
Ensure ClassBTypes constructed from symbol and classfile are identical
Diffstat (limited to 'test/junit')
7 files changed, 313 insertions, 56 deletions
diff --git a/test/junit/scala/issues/BytecodeTest.scala b/test/junit/scala/issues/BytecodeTest.scala index 18f8b44391..cf5c7f9ec3 100644 --- a/test/junit/scala/issues/BytecodeTest.scala +++ b/test/junit/scala/issues/BytecodeTest.scala @@ -3,11 +3,15 @@ package scala.issues import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.junit.Test + import scala.tools.asm.Opcodes._ import scala.tools.nsc.backend.jvm.AsmUtils import scala.tools.nsc.backend.jvm.CodeGenTools._ import org.junit.Assert._ + import scala.collection.JavaConverters._ +import scala.tools.asm.Opcodes +import scala.tools.asm.tree.ClassNode import scala.tools.partest.ASMConverters._ import scala.tools.testing.ClearAfterClass @@ -208,4 +212,267 @@ class BytecodeTest extends ClearAfterClass { Label(14), Op(ICONST_0), Label(17), Op(IRETURN))) } + + object forwarderTestUtils { + def findMethods(cls: ClassNode, name: String): List[Method] = cls.methods.iterator.asScala.find(_.name == name).map(convertMethod).toList + + import language.implicitConversions + implicit def s2c(s: Symbol)(implicit classes: Map[String, ClassNode]): ClassNode = classes(s.name) + + def checkForwarder(c: ClassNode, target: String) = { + val List(f) = findMethods(c, "f") + assertSameCode(f, List(VarOp(ALOAD, 0), Invoke(INVOKESPECIAL, target, "f", "()I", false), Op(IRETURN))) + } + } + + @Test + def traitMethodForwarders(): Unit = { + import forwarderTestUtils._ + val code = + """trait T1 { def f = 1 } + |trait T2 extends T1 { override def f = 2 } + |trait T3 { self: T1 => override def f = 3 } + | + |abstract class A1 { def f: Int } + |class A2 { def f: Int = 4 } + | + |trait T4 extends A1 { def f = 5 } + |trait T5 extends A2 { override def f = 6 } + | + |trait T6 { def f: Int } + |trait T7 extends T6 { abstract override def f = super.f + 1 } + | + |trait T8 { override def clone() = super.clone() } + | + |class A3 extends T1 { override def f = 7 } + | + |class C1 extends T1 + |class C2 extends T2 + |class C3 extends T1 with T2 + |class C4 extends T2 with T1 + |class C5 extends T1 with T3 + | + |// traits extending a class that defines f + |class C6 extends T4 + |class C7 extends T5 + |class C8 extends A1 with T4 + |class C9 extends A2 with T5 + | + |// T6: abstract f in trait + |class C10 extends T6 with T1 + |class C11 extends T6 with T2 + |abstract class C12 extends A1 with T6 + |class C13 extends A2 with T6 + |class C14 extends T4 with T6 + |class C15 extends T5 with T6 + | + |// superclass overrides a trait method + |class C16 extends A3 + |class C17 extends A3 with T1 + | + |// abstract override + |class C18 extends T6 { def f = 22 } + |class C19 extends C18 with T7 + | + |class C20 extends T8 + """.stripMargin + + implicit val classes = compileClasses(compiler)(code).map(c => (c.name, c)).toMap + + val noForwarder = List('C1, 'C2, 'C3, 'C4, 'C10, 'C11, 'C12, 'C13, 'C16, 'C17) + for (c <- noForwarder) assertEquals(findMethods(c, "f"), Nil) + + checkForwarder('C5, "T3") + checkForwarder('C6, "T4") + checkForwarder('C7, "T5") + checkForwarder('C8, "T4") + checkForwarder('C9, "T5") + checkForwarder('C14, "T4") + checkForwarder('C15, "T5") + assertSameSummary(getSingleMethod('C18, "f"), List(BIPUSH, IRETURN)) + checkForwarder('C19, "T7") + assertSameCode(getSingleMethod('C19, "T7$$super$f"), List(VarOp(ALOAD, 0), Invoke(INVOKESPECIAL, "C18", "f", "()I", false), Op(IRETURN))) + assertInvoke(getSingleMethod('C20, "clone"), "T8", "clone") // mixin forwarder + } + + @Test + def noTraitMethodForwardersForOverloads(): Unit = { + import forwarderTestUtils._ + val code = + """trait T1 { def f(x: Int) = 0 } + |trait T2 { def f(x: String) = 1 } + |class C extends T1 with T2 + """.stripMargin + val List(c, t1, t2) = compileClasses(compiler)(code) + assertEquals(findMethods(c, "f"), Nil) + } + + @Test + def traitMethodForwardersForJavaDefaultMethods(): Unit = { + import forwarderTestUtils._ + val j1 = ("interface J1 { int f(); }", "J1.java") + val j2 = ("interface J2 { default int f() { return 1; } }", "J2.java") + val j3 = ("interface J3 extends J1 { default int f() { return 2; } }", "J3.java") + val j4 = ("interface J4 extends J2 { default int f() { return 3; } }", "J4.java") + val code = + """trait T1 extends J2 { override def f = 4 } + |trait T2 { self: J2 => override def f = 5 } + | + |class K1 extends J2 + |class K2 extends J1 with J2 + |class K3 extends J2 with J1 + | + |class K4 extends J3 + |class K5 extends J3 with J1 + |class K6 extends J1 with J3 + | + |class K7 extends J4 + |class K8 extends J4 with J2 + |class K9 extends J2 with J4 + | + |class K10 extends T1 with J2 + |class K11 extends J2 with T1 + | + |class K12 extends J2 with T2 + """.stripMargin + implicit val classes = compileClasses(compiler)(code, List(j1, j2, j3, j4)).map(c => (c.name, c)).toMap + + val noForwarder = List('K1, 'K2, 'K3, 'K4, 'K5, 'K6, 'K7, 'K8, 'K9, 'K10, 'K11) + for (c <- noForwarder) assertEquals(findMethods(c, "f"), Nil) + + checkForwarder('K12, "T2") + } + + @Test + def invocationReceivers(): Unit = { + val List(c1, c2, t, u) = compileClasses(compiler)(invocationReceiversTestCode.definitions("Object")) + // mixin forwarder in C1 + assertSameCode(getSingleMethod(c1, "clone"), List(VarOp(ALOAD, 0), Invoke(INVOKESPECIAL, "T", "clone", "()Ljava/lang/Object;", false), Op(ARETURN))) + assertInvoke(getSingleMethod(c1, "f1"), "T", "clone") + assertInvoke(getSingleMethod(c1, "f2"), "T", "clone") + assertInvoke(getSingleMethod(c1, "f3"), "C1", "clone") + assertInvoke(getSingleMethod(c2, "f1"), "T", "clone") + assertInvoke(getSingleMethod(c2, "f2"), "T", "clone") + assertInvoke(getSingleMethod(c2, "f3"), "C1", "clone") + + val List(c1b, c2b, tb, ub) = compileClasses(compiler)(invocationReceiversTestCode.definitions("String")) + def ms(c: ClassNode, n: String) = c.methods.asScala.toList.filter(_.name == n) + assert(ms(tb, "clone").length == 1) + assert(ms(ub, "clone").isEmpty) + val List(c1Clone) = ms(c1b, "clone") + assertEquals(c1Clone.desc, "()Ljava/lang/Object;") + assert((c1Clone.access | Opcodes.ACC_BRIDGE) != 0) + assertSameCode(convertMethod(c1Clone), List(VarOp(ALOAD, 0), Invoke(INVOKEVIRTUAL, "C1", "clone", "()Ljava/lang/String;", false), Op(ARETURN))) + + def iv(m: Method) = getSingleMethod(c1b, "f1").instructions.collect({case i: Invoke => i}) + assertSameCode(iv(getSingleMethod(c1b, "f1")), List(Invoke(INVOKEINTERFACE, "T", "clone", "()Ljava/lang/String;", true))) + assertSameCode(iv(getSingleMethod(c1b, "f2")), List(Invoke(INVOKEINTERFACE, "T", "clone", "()Ljava/lang/String;", true))) + // invokeinterface T.clone in C1 is OK here because it is not an override of Object.clone (different siganture) + assertSameCode(iv(getSingleMethod(c1b, "f3")), List(Invoke(INVOKEINTERFACE, "T", "clone", "()Ljava/lang/String;", true))) + } + + @Test + def invocationReceiversProtected(): Unit = { + // http://lrytz.github.io/scala-aladdin-bugtracker/displayItem.do%3Fid=455.html / 9954eaf + // also https://issues.scala-lang.org/browse/SI-1430 / 0bea2ab (same but with interfaces) + val aC = + """package a; + |/*package private*/ abstract class A { + | public int f() { return 1; } + | public int t; + |} + """.stripMargin + val bC = + """package a; + |public class B extends A { } + """.stripMargin + val iC = + """package a; + |/*package private*/ interface I { int f(); } + """.stripMargin + val jC = + """package a; + |public interface J extends I { } + """.stripMargin + val cC = + """package b + |class C { + | def f1(b: a.B) = b.f + | def f2(b: a.B) = { b.t = b.t + 1 } + | def f3(j: a.J) = j.f + |} + """.stripMargin + val List(c) = compileClasses(compiler)(cC, javaCode = List((aC, "A.java"), (bC, "B.java"), (iC, "I.java"), (jC, "J.java"))) + assertInvoke(getSingleMethod(c, "f1"), "a/B", "f") // receiver needs to be B (A is not accessible in class C, package b) + println(getSingleMethod(c, "f2").instructions.stringLines) + assertInvoke(getSingleMethod(c, "f3"), "a/J", "f") // receiver needs to be J + } + + @Test + def specialInvocationReceivers(): Unit = { + val code = + """class C { + | def f1(a: Array[String]) = a.clone() + | def f2(a: Array[Int]) = a.hashCode() + | def f3(n: Nothing) = n.hashCode() + | def f4(n: Null) = n.toString() + | + |} + """.stripMargin + val List(c) = compileClasses(compiler)(code) + assertInvoke(getSingleMethod(c, "f1"), "[Ljava/lang/String;", "clone") // array descriptor as receiver + assertInvoke(getSingleMethod(c, "f2"), "java/lang/Object", "hashCode") // object receiver + assertInvoke(getSingleMethod(c, "f3"), "java/lang/Object", "hashCode") + assertInvoke(getSingleMethod(c, "f4"), "java/lang/Object", "toString") + } +} + +object invocationReceiversTestCode { + // if cloneType is more specific than Object (e.g., String), a bridge method is generated. + def definitions(cloneType: String) = + s"""trait T { override def clone(): $cloneType = "hi" } + |trait U extends T + |class C1 extends U with Cloneable { + | // The comments below are true when $cloneType is Object. + | // C1 gets a forwarder for clone that invokes T.clone. this is needed because JVM method + | // resolution always prefers class members, so it would resolve to Object.clone, even if + | // C1 is a subtype of the interface T which has an overriding default method for clone. + | + | // invokeinterface T.clone + | def f1 = (this: T).clone() + | + | // cannot invokeinterface U.clone (NoSuchMethodError). Object.clone would work here, but + | // not in the example in C2 (illegal access to protected). T.clone works in all cases and + | // resolves correctly. + | def f2 = (this: U).clone() + | + | // invokevirtual C1.clone() + | def f3 = (this: C1).clone() + |} + | + |class C2 { + | def f1(t: T) = t.clone() // invokeinterface T.clone + | def f2(t: U) = t.clone() // invokeinterface T.clone -- Object.clone would be illegal (protected, explained in C1) + | def f3(t: C1) = t.clone() // invokevirtual C1.clone -- Object.clone would be illegal + |} + """.stripMargin + + val runCode = + """ + |val r = new StringBuffer() + |val c1 = new C1 + |r.append(c1.f1) + |r.append(c1.f2) + |r.append(c1.f3) + |val t = new T { } + |val u = new U { } + |val c2 = new C2 + |r.append(c2.f1(t)) + |r.append(c2.f1(u)) + |r.append(c2.f1(c1)) + |r.append(c2.f2(u)) + |r.append(c2.f2(c1)) + |r.append(c2.f3(c1)) + |r.toString + """.stripMargin } diff --git a/test/junit/scala/issues/RunTest.scala b/test/junit/scala/issues/RunTest.scala index 2bc8008222..781f2ef343 100644 --- a/test/junit/scala/issues/RunTest.scala +++ b/test/junit/scala/issues/RunTest.scala @@ -140,4 +140,11 @@ class RunTest extends ClearAfterClass { val i = Integer.TYPE assertEquals(run[List[Class[_]]](code), List(i, i)) } + + @Test + def invocationReceivers(): Unit = { + import invocationReceiversTestCode._ + assertEquals(run[String](definitions("Object") + runCode), "hi" * 9) + assertEquals(run[String](definitions("String") + runCode), "hi" * 9) // bridge method for clone generated + } } diff --git a/test/junit/scala/reflect/internal/PrintersTest.scala b/test/junit/scala/reflect/internal/PrintersTest.scala index 9bfe6eecb8..2305e7ea50 100644 --- a/test/junit/scala/reflect/internal/PrintersTest.scala +++ b/test/junit/scala/reflect/internal/PrintersTest.scala @@ -8,14 +8,6 @@ import scala.reflect.runtime.{currentMirror=>cm} import org.junit.runner.RunWith import org.junit.runners.JUnit4 -@RunWith(classOf[JUnit4]) -class PrintersTest extends BasePrintTests - with ClassPrintTests - with TraitPrintTests - with ValAndDefPrintTests - with QuasiTreesPrintTests - with PackagePrintTests - object PrinterHelper { val toolbox = cm.mkToolBox() @@ -73,7 +65,8 @@ object PrinterHelper { import PrinterHelper._ -trait BasePrintTests { +@RunWith(classOf[JUnit4]) +class BasePrintTest { @Test def testIdent = assertTreeCode(Ident("*"))("*") @Test def testConstant1 = assertTreeCode(Literal(Constant("*")))("\"*\"") @@ -348,7 +341,8 @@ trait BasePrintTests { @Test def testImport4 = assertPrintedCode("import scala.collection._") } -trait ClassPrintTests { +@RunWith(classOf[JUnit4]) +class ClassPrintTest { @Test def testClass = assertPrintedCode("class *") @Test def testClassWithBody = assertPrintedCode(sm""" @@ -833,7 +827,8 @@ trait ClassPrintTests { |}""") } -trait TraitPrintTests { +@RunWith(classOf[JUnit4]) +class TraitPrintTest { @Test def testTrait = assertPrintedCode("trait *") @Test def testTraitWithBody = assertPrintedCode(sm""" @@ -953,7 +948,8 @@ trait TraitPrintTests { |}""") } -trait ValAndDefPrintTests { +@RunWith(classOf[JUnit4]) +class ValAndDefPrintTest { @Test def testVal1 = assertPrintedCode("val a: scala.Unit = ()") @Test def testVal2 = assertPrintedCode("val * : scala.Unit = ()") @@ -1093,7 +1089,8 @@ trait ValAndDefPrintTests { |}""", wrapCode = true) } -trait PackagePrintTests { +@RunWith(classOf[JUnit4]) +class PackagePrintTest { @Test def testPackage1 = assertPrintedCode(sm""" |package foo.bar { | @@ -1131,7 +1128,8 @@ trait PackagePrintTests { |}""", checkTypedTree = false) } -trait QuasiTreesPrintTests { +@RunWith(classOf[JUnit4]) +class QuasiTreesPrintTest { @Test def testQuasiIdent = assertTreeCode(q"*")("*") @Test def testQuasiVal = assertTreeCode(q"val * : Unit = null")("val * : Unit = null") diff --git a/test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala index 78dbab82f4..ab05c15e85 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala @@ -68,12 +68,12 @@ class NullnessAnalyzerTest extends ClearAfterClass { // So in the frame for `ALOAD 0`, the stack is still empty. val res = - """ L0: 0: NotNull - | LINENUMBER 1 L0: 0: NotNull - | ALOAD 0: 0: NotNull - |INVOKEVIRTUAL java/lang/Object.toString ()Ljava/lang/String;: 0: NotNull, 1: NotNull - | ARETURN: 0: NotNull, 1: Unknown1 - | L0: null""".stripMargin + """ L0: 0: NotNull + | LINENUMBER 1 L0: 0: NotNull + | ALOAD 0: 0: NotNull + |INVOKEVIRTUAL C.toString ()Ljava/lang/String;: 0: NotNull, 1: NotNull + | ARETURN: 0: NotNull, 1: Unknown1 + | L0: null""".stripMargin // println(showAllNullnessFrames(newNullnessAnalyzer(m), m)) assertEquals(showAllNullnessFrames(newNullnessAnalyzer(m), m), res) } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala index 1ce913006d..9e6a148dba 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala @@ -67,19 +67,7 @@ class BTypesFromClassfileTest { // there's a separate InlineInfoTest. val chk1 = sameBTypes(fromSym.superClass, fromClassfile.superClass, checked) - - // was: - // val chk2 = sameBTypes(fromSym.interfaces, fromClassfile.interfaces, chk1) - - // TODO: The new trait encoding emits redundant parents in the backend to avoid linkage errors in invokespecial - // Need to give this some more thought, maybe do it earlier so it is reflected in the Symbol's info, too. - val fromSymInterfaces = fromSym.interfaces - val fromClassFileInterfaces = fromClassfile.interfaces - val (matching, other) = fromClassFileInterfaces.partition(x => fromSymInterfaces.exists(_.internalName == x.internalName)) - val chk2 = sameBTypes(fromSym.interfaces, matching, chk1) - for (redundant <- other) { - assert(matching.exists(x => x.isSubtypeOf(redundant).orThrow), redundant) - } + val chk2 = sameBTypes(fromSym.interfaces, fromClassfile.interfaces, chk1) // The fromSym info has only member classes, no local or anonymous. The symbol is read from the // Scala pickle data and only member classes are created / entered. diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala index 9079ca248a..15665fca33 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -458,19 +458,6 @@ class InlinerTest extends ClearAfterClass { } @Test - def inlineMixinMethods(): Unit = { - val code = - """trait T { - | @inline final def f = 1 - |} - |class C extends T - """.stripMargin - val List(c, t) = compile(code) - // the static implementation method is inlined into the mixin, so there's no invocation in the mixin - assertNoInvoke(getSingleMethod(c, "f")) - } - - @Test def inlineTraitInherited(): Unit = { val code = """trait T { @@ -545,7 +532,7 @@ class InlinerTest extends ClearAfterClass { val List(c, oMirror, oModule, t) = compile(code, allowMessage = i => {count += 1; i.msg contains warn}) assert(count == 1, count) - assertNoInvoke(getSingleMethod(oModule, "f")) + assertNoInvoke(getSingleMethod(t, "f")) assertNoInvoke(getSingleMethod(c, "t1")) assertNoInvoke(getSingleMethod(c, "t2")) @@ -1497,10 +1484,12 @@ class InlinerTest extends ClearAfterClass { @Test def sd86(): Unit = { val code = - """trait T { @inline def f = 1 } // note that f is not final - |class C extends T + """trait T1 { @inline def f = 999 } + |trait T2 { self: T1 => @inline override def f = 1 } // note that f is not final + |class C extends T1 with T2 """.stripMargin - val List(c, t) = compile(code, allowMessage = _ => true) + val List(c, t1, t2) = compile(code, allowMessage = _ => true) + // the forwarder C.f is inlined, so there's no invocation assertSameSummary(getSingleMethod(c, "f"), List(ICONST_1, IRETURN)) } } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala index 4db2657c1b..e2d03d8c62 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala @@ -24,8 +24,7 @@ object ScalaInlineInfoTest extends ClearAfterClass.Clearable { @RunWith(classOf[JUnit4]) class ScalaInlineInfoTest extends ClearAfterClass { ClearAfterClass.stateToClear = ScalaInlineInfoTest - - val compiler = newCompiler() + val compiler = ScalaInlineInfoTest.compiler def inlineInfo(c: ClassNode): InlineInfo = c.attrs.asScala.collect({ case a: InlineInfoAttribute => a.inlineInfo }).head @@ -113,10 +112,7 @@ class ScalaInlineInfoTest extends ClearAfterClass { val infoC = inlineInfo(c) val expectC = InlineInfo(false, None, Map( "O()LT$O$;" -> MethodInlineInfo(true ,false,false), - "f1()I" -> MethodInlineInfo(false,false,false), - "f3()I" -> MethodInlineInfo(false,false,false), - "f4()Ljava/lang/String;" -> MethodInlineInfo(false,true ,false), - "f5()I" -> MethodInlineInfo(true ,false,false), + "O$lzycompute()LT$O$;" -> MethodInlineInfo(true, false,false), "f6()I" -> MethodInlineInfo(false,false,false), "x1()I" -> MethodInlineInfo(false,false,false), "T$_setter_$x1_$eq(I)V" -> MethodInlineInfo(false,false,false), @@ -171,4 +167,16 @@ class ScalaInlineInfoTest extends ClearAfterClass { ("U",None))) } + + @Test + def lzyComputeInlineInfo(): Unit = { + val code = "class C { object O }" + val List(c, om) = compileClasses(compiler)(code) + val infoC = inlineInfo(c) + val expected = Map( + "<init>()V" -> MethodInlineInfo(false,false,false), + "O$lzycompute()LC$O$;" -> MethodInlineInfo(true,false,false), + "O()LC$O$;" -> MethodInlineInfo(true,false,false)) + assert(infoC.methodInfos == expected, mapDiff(infoC.methodInfos, expected)) + } } |